Por que o PHP 5.2 + não permite métodos abstratos de classe estática?

121

Depois de ativar avisos estritos no PHP 5.2, vi uma carga de avisos estritos sobre padrões de um projeto que foi originalmente escrito sem avisos estritos:

Padrões estritos : Função estática Program :: getSelectSQL () não deve ser abstrato em Program.class.inc

A função em questão pertence a um programa de classe pai abstrato e é declarada estática abstrata porque deve ser implementada em suas classes filho, como TVProgram.

Encontrei referências a essa alteração aqui :

Funções de classe estática abstratas descartadas. Devido a uma supervisão, o PHP 5.0.xe 5.1.x permitiram funções estáticas abstratas nas classes. A partir do PHP 5.2.x, somente interfaces podem tê-los.

Minha pergunta é: alguém pode explicar de uma maneira clara por que não deveria haver uma função estática abstrata no PHP?

Artem Russakovskii
fonte
12
Novos leitores devem notar que esta restrição irracional foi removida no PHP 7.
Mark Amery

Respostas:

76

métodos estáticos pertencem à classe que os declarou. Ao estender a classe, você pode criar um método estático com o mesmo nome, mas na verdade não está implementando um método abstrato estático.

O mesmo vale para estender qualquer classe com métodos estáticos. Se você estender essa classe e criar um método estático da mesma assinatura, na verdade você não estará substituindo o método estático da superclasse

EDIT (16 de setembro de 2009)
Atualização sobre isso. Executando o PHP 5.3, vejo que a estática abstrata está de volta, para o bem ou para o mal. (consulte http://php.net/lsb para mais informações)

A CORREÇÃO (por philfreo)
abstract staticainda não é permitida no PHP 5.3, o LSB é relacionado, mas diferente.

Jonathan Fingland
fonte
3
OK, e se eu quisesse reforçar a necessidade da função getSelectSQL () em todos os filhos que estendem minha classe abstrata? getSelectSQL () na classe pai não possui um motivo válido para existir. Qual é o melhor plano de ação? A razão pela qual escolhi a estática abstrata é que o código não seria compilado até que eu implementasse getSelectSQL () em todos os filhos.
Artem Russakovskii
1
Provavelmente, você deve redesenhar as coisas para que getSelectSQL () seja um resumo / instância / método. Dessa forma, / instance / de cada filho terá esse método.
Matthew Flaschen
7
A estática abstrata ainda não é permitida no PHP 5.3. Ligações estáticas tardias não têm nada a ver com isso. Veja também stackoverflow.com/questions/2859633
Artefacto
40
Na minha opinião, esse aviso estrito é simplesmente estúpido, pois o PHP tem "ligação estática tardia", o que naturalmente fornece uma idéia do uso de métodos estáticos como se a própria classe tivesse sido um objeto (como, por exemplo, em ruby). O que leva à sobrecarga de método estático e abstract staticpode ser útil nesse caso.
Dmitry
4
Esta resposta está vagamente errada. "ainda não permitido" significa apenas que você receberá um aviso de nível E_STRICT, pelo menos no 5.3+ você será perfeitamente bem-vindo ao criar funções estáticas abstratas, implementá-las em classes estendidas e depois consultá-las através da palavra-chave static ::. Obviamente, a versão estática da classe pai ainda está lá e não pode ser chamada diretamente (via self :: ou static :: dentro dessa classe), pois é abstrata e com erro fatal como se você chamasse uma função abstrata não estática regular. Funcionalmente, isso é útil, concordo com os sentimentos de @dmitry nesse sentido.
ahoffner
79

É uma história longa e triste.

Quando o PHP 5.2 introduziu esse aviso pela primeira vez, ligações estáticas tardias ainda não estavam no idioma. Caso você não esteja familiarizado com ligações estáticas tardias, observe que códigos como este não funcionam da maneira que você pode esperar:

<?php

abstract class ParentClass {
    static function foo() {
        echo "I'm gonna do bar()";
        self::bar();
    }

    abstract static function bar();
}

class ChildClass extends ParentClass {
    static function bar() {
        echo "Hello, World!";
    }
}

ChildClass::foo();

Deixando de lado o aviso de modo estrito, o código acima não funciona. A self::bar()chamada in foo()refere-se explicitamente ao bar()método de ParentClass, mesmo quando foo()é chamado como método de ChildClass. Se você tentar executar esse código com o modo estrito desativado, verá " Erro fatal do PHP: não é possível chamar o método abstrato ParentClass :: bar () ".

Dado isso, métodos estáticos abstratos no PHP 5.2 foram inúteis. O objetivo de usar um método abstrato é que você pode escrever um código que chame o método sem saber para qual implementação ele estará chamando - e, em seguida, fornecer implementações diferentes em diferentes classes filho. Mas como o PHP 5.2 não oferece uma maneira limpa de escrever um método de uma classe pai que chame um método estático da classe filho na qual é chamado, esse uso de métodos estáticos abstratos não é possível. Portanto, qualquer uso do abstract staticPHP 5.2 é um código incorreto, provavelmente inspirado por um mal-entendido de como a selfpalavra - chave funciona. Era inteiramente razoável lançar um aviso sobre isso.

Mas, em seguida, surgiu o PHP 5.3, com a capacidade de se referir à classe na qual um método foi chamado através da staticpalavra - chave (diferente da selfpalavra - chave, que sempre se refere à classe na qual o método foi definido ). Se você mudar self::bar()para static::bar()no meu exemplo acima, ele funciona bem no PHP 5.3 e acima. Você pode ler mais sobre selfvs staticem Nova auto vs. nova estática .

Com a palavra-chave estática adicionada, o argumento claro para ter abstract staticemitido um aviso desapareceu. O principal objetivo das ligações estáticas tardias era permitir que os métodos definidos em uma classe pai chamassem métodos estáticos que seriam definidos nas classes filho; permitir métodos estáticos abstratos parece razoável e consistente, dada a existência de ligações estáticas tardias.

Acho que você ainda pode defender o aviso. Por exemplo, você poderia argumentar que, como o PHP permite chamar métodos estáticos de classes abstratas, no meu exemplo acima (mesmo depois de corrigi-lo substituindo selfpor static), você está expondo um método público ParentClass::foo()que está quebrado e que realmente não deseja expor. Usar uma classe não estática - ou seja, tornar todos os métodos de instância de métodos e tornar filhos de ParentClasstodos singletons ou algo assim - resolveria esse problema, pois ParentClass, sendo abstrato, não pode ser instanciado e, portanto, seus métodos de instância não podem ser chamado. Eu acho que esse argumento é fraco (porque eu acho que exporParentClass::foo() não é grande coisa e usar singletons em vez de classes estáticas é muitas vezes desnecessariamente detalhado e feio), mas você pode discordar razoavelmente - é uma chamada um tanto subjetiva.

Então, com base nesse argumento, os desenvolvedores do PHP mantiveram o aviso na linguagem, certo?

Uh, não exatamente .

O relatório de bug 53081 do PHP, vinculado acima, pedia que o aviso fosse eliminado, pois a adição da static::foo()construção havia tornado métodos estáticos abstratos razoáveis ​​e úteis. Rasmus Lerdorf (criador do PHP) começa rotulando a solicitação como falsa e passa por uma longa cadeia de raciocínios ruins para tentar justificar o aviso. Então, finalmente, essa troca ocorre:

Giorgio

eu sei mas:

abstract class cA
{
      //static function A(){self::B();} error, undefined method
      static function A(){static::B();} // good
      abstract static function B();
}

class cB extends cA
{
    static function B(){echo "ok";}
}

cB::A();

Rasmus

Certo, é exatamente assim que deve funcionar.

Giorgio

mas não é permitido :(

Rasmus

O que não é permitido?

abstract class cA {
      static function A(){static::B();}
      abstract static function B();
}

class cB extends cA {
    static function B(){echo "ok";}
}

cB::A();

Isso funciona bem. Você obviamente não pode chamar self :: B (), mas estático :: B () está bom.

A afirmação de Rasmus de que o código em seu exemplo "funciona bem" é falsa; como você sabe, lança um aviso de modo estrito. Eu acho que ele estava testando sem o modo estrito ativado. Independentemente disso, um Rasmus confuso deixou o pedido erroneamente fechado como "falso".

E é por isso que o aviso ainda está no idioma. Essa pode não ser uma explicação totalmente satisfatória - você provavelmente veio aqui esperando que houvesse uma justificativa racional para o aviso. Infelizmente, no mundo real, às vezes as escolhas nascem de erros mundanos e raciocínio ruim, e não de tomada de decisão racional. Este é simplesmente um daqueles momentos.

Felizmente, o estimado Nikita Popov removeu o aviso da linguagem no PHP 7 como parte do PHP RFC: Reclassifique os avisos E_STRICT . Por fim, a sanidade prevaleceu e, uma vez lançado o PHP 7, todos podemos usá-lo com alegria abstract staticsem receber esse aviso bobo.

Mark Amery
fonte
70

Existe uma solução muito simples para esse problema, que na verdade faz sentido do ponto de vista do design. Como Jonathan escreveu:

O mesmo vale para estender qualquer classe com métodos estáticos. Se você estender essa classe e criar um método estático da mesma assinatura, na verdade você não estará substituindo o método estático da superclasse

Portanto, como uma solução alternativa, você pode fazer o seguinte:

<?php
abstract class MyFoo implements iMyFoo {

    public static final function factory($type, $someData) {
        // don't forget checking and do whatever else you would
        // like to do inside a factory method
        $class = get_called_class()."_".$type;
        $inst = $class::getInstance($someData);
        return $inst;
    }
}


interface iMyFoo {
    static function factory($type, $someData);
    static function getInstance();
    function getSomeData();
}
?>

E agora você reforça que qualquer classe de subclasse MyFoo implementa um método estático getInstance e um método público getSomeData. E se você não subclassificar o MyFoo, ainda poderá implementar o iMyFoo para criar uma classe com funcionalidade semelhante.

Wouter Van Vliet
fonte
2
É possível com esse padrão tornar a função protegida . Quando faço isso, significa que as classes que estendem o MyFoo lançam avisos de que getInstance deve ser público. E você não pode colocar protegido em uma definição de interface.
Artfulrobot
3
algumas vezes static::podem ser úteis.
costurado 13/03/14
3
Não funciona com características. Se apenas traços poderia ter abstract staticmétodos, sem PHP reclamando ....
Rudie
1
O uso de uma interface é provavelmente a melhor solução aqui. +1.
Juan Carlos Coto
Isso é realmente muito elegante devido à sua simplicidade. 1
G. Stewart
12

Eu sei que isso é velho, mas ....

Por que não lançar uma exceção no método estático da classe pai, dessa forma, se você não a substituir, a exceção será causada.

Petah
fonte
1
Isso não ajuda, a exceção aconteceria na chamada do método estático - ao mesmo tempo em que um erro 'método não existe' seria exibido se você não o substituísse.
BT
3
@ BT eu quis dizer, não declare o método abstrato, implemente-o, mas apenas lance uma exceção quando for chamado, o que significa que ele não será lançado se for substituído.
Petah
Esta parece ser a solução mais elegante.
Alex S
Melhor ver algo assim em tempo de compilação do que em tempo de execução. É mais fácil encontrar um problema antes que eles estejam em produção, porque você só precisa carregar o arquivo, não executar o código para descobrir se é ruim ou não
#
4

Eu argumentaria que uma classe / interface abstrata poderia ser vista como um contrato entre programadores. Ele lida mais com a aparência / comportamento das coisas e não implementa a funcionalidade real. Como visto no php5.0 e 5.1.x, não é uma lei natural que impede os desenvolvedores de php de fazê-lo, mas o desejo de acompanhar outros padrões de design de OO em outros idiomas. Basicamente, essas idéias tentam impedir comportamentos inesperados, se alguém já estiver familiarizado com outros idiomas.

merkuro
fonte
Embora não relacionados ao PHP aqui é outra boa explicação: stackoverflow.com/questions/3284/...
merkuro
3
Meu Deus! As duas pessoas que votaram contra você estão totalmente doidas! Esta é a resposta mais perspicaz neste tópico.
Theodore R. Smith
5
@ TheodoreR.Smith Insightful? Ele contém erros e é pouco coerente. A afirmação de que as classes abstratas "não implementam a funcionalidade real" não é necessariamente verdadeira, e esse é o objetivo delas existirem além das interfaces. Na alegação de que "não é uma lei natural que impede os desenvolvedores de php de fazê-lo" , não tenho idéia do que seja "isso" . E na alegação de que "basicamente essas idéias tentam impedir comportamentos inesperados" , não tenho idéia do que são "essas idéias" ou o potencial "comportamento inesperado". Qualquer que seja o insight que você extraiu disso, está perdido para mim.
Mark Amery
2

Não vejo motivo para proibir funções abstratas estáticas. O melhor argumento de que não há razão para proibi-los é que eles sejam permitidos em Java. As perguntas são: - São tecnicamente viáveis? - Sim, já que existiam no PHP 5.2 e eles existem em Java. Então, nós PODEMOS fazê-lo. Devemos fazê-lo? - Eles fazem sentido? Sim. Faz sentido implementar uma parte de uma classe e deixar outra parte de uma classe para o usuário. Faz sentido em funções não estáticas, por que não deveria fazer sentido para funções estáticas? Um uso de funções estáticas são as classes em que não deve haver mais de uma instância (singletons). Por exemplo, um mecanismo de criptografia. Ele não precisa existir em várias instâncias e há razões para evitar isso - por exemplo, você precisa proteger apenas uma parte da memória contra invasores. Portanto, faz todo sentido implementar uma parte do mecanismo e deixar o algoritmo de criptografia para o usuário. Este é apenas um exemplo. Se você está acostumado a usar funções estáticas, encontrará muito mais.

user2964021
fonte
3
Seu ponto de vista sobre métodos estáticos abstratos existentes no 5.2 é muito enganador. Primeiramente, o aviso de modo estrito que os proibia foi introduzido no 5.2; você faz parecer que no 5.2 eles foram permitidos. Em segundo lugar, no 5.2 eles não podiam ser facilmente usados "para implementar uma parte de uma classe e deixar outra parte de uma classe para o usuário" porque as Ligações estáticas tardias ainda não existiam.
Mark Amery
0

No php 5.4+, use a característica:

trait StaticExample {
    public static function instance () {
    return new self;
    }
}

e em sua classe, coloque no início:

use StaticExample;
kamil
fonte
1
Consegui colocar abstract public static function get_table_name();uma característica e usá-la na minha classe abstrata sem mais avisos E_STRICT! Isso ainda impunha a definição do método estático nas crianças, como eu esperava. Fantástico!
Programster
-1

Examine os problemas do PHP 'Late Static Binding'. Se você estiver colocando métodos estáticos em classes abstratas, provavelmente o encontrará mais cedo ou mais tarde. Faz sentido que os avisos estritos estejam dizendo para você evitar o uso de recursos de idioma corrompido.

Sean McSomething
fonte
4
Eu acho que ele quer dizer os avisos de "Rigorosos Padrões".
Jacob Hume
2
Quais "problemas" você afirma que as ligações estáticas tardias têm? Você afirma que eles estão "quebrados", o que é uma afirmação ousada, feita aqui sem evidências ou explicações. O recurso sempre funcionou bem para mim, e acho que este post é um disparate.
Mark Amery