Interfaces PHP 7, sugestão de tipo de retorno e self

89

ATUALIZAÇÃO : o PHP 7.4 agora oferece suporte a covariância e contravariância que aborda a principal questão levantada nesta questão.


Eu tive um problema com o uso de dicas de tipo de retorno no PHP 7. Meu entendimento é que a sugestão : selfsignifica que você pretende que uma classe de implementação retorne a si mesma. Portanto, usei : selfem minhas interfaces para indicar isso, mas quando tentei realmente implementar a interface, obtive erros de compatibilidade.

A seguir está uma demonstração simples do problema que encontrei:

interface iFoo
{
    public function bar (string $baz) : self;
}

class Foo implements iFoo
{

    public function bar (string $baz) : self
    {
        echo $baz . PHP_EOL;
        return $this;
    }
}

(new Foo ()) -> bar ("Fred") 
    -> bar ("Wilma") 
    -> bar ("Barney") 
    -> bar ("Betty");

A saída esperada era:

Fred Wilma Barney Betty

O que eu realmente consigo é:

Erro fatal do PHP: Declaração de Foo :: bar (int $ baz): Foo deve ser compatível com iFoo :: bar (int $ baz): iFoo em test.php na linha 7

O fato é que Foo é uma implementação do iFoo, então pelo que eu posso dizer, a implementação deve ser perfeitamente compatível com a interface fornecida. Eu poderia provavelmente corrigir esse problema alterando a interface ou a classe de implementação (ou ambas) para retornar a dica da interface pelo nome em vez de usar self, mas meu entendimento é que semanticamente selfsignifica "retornar a instância da classe na qual você acabou de chamar o método " Portanto, alterá-lo para a interface significaria, em teoria, que eu poderia retornar qualquer instância de algo que implementa a interface quando minha intenção é que a instância invocada seja o que será retornado.

Isso é um descuido no PHP ou é uma decisão de design deliberada? Se for o primeiro, há alguma chance de vê-lo corrigido no PHP 7.1? Se não, qual é a maneira correta de retornar a dica de que sua interface espera que você retorne a instância na qual acabou de chamar o método para encadeamento?

GordonM
fonte
Eu acho que é um erro na sugestão de tipo de retorno do PHP, talvez você deva considerá-lo um bug ; mas é improvável que qualquer correção entre no PHP 7.1 neste estágio final
Mark Baker
Como a última versão beta do 7.1 foi colocada online há alguns dias, é muito improvável que qualquer correção entre no 7.1.
Charlotte Dunois
Sem interesse, onde você está lendo sua interpretação de como o selftipo de retorno deve funcionar?
Adam Cameron de
@Adam: parece que é lógico selfsignificar "Retorne a instância em que você chamou isso, e não alguma outra instância que implemente a mesma interface". Parece que me lembro de Java tinha um tipo de retorno semelhante (embora já faça um tempo desde que fiz qualquer programação Java)
GordonM
1
Oi Gordon. Bem, a menos que esteja documentado para funcionar em algum lugar, eu não contaria com o que poderia ser lógico ser realidade. TBH com a situação que eu descreveria, seria apenas o mais declarativo possível e usaria iFoo como o tipo de retorno. Existe uma situação em que isso não funcionará realmente? (Sei que isso é "conselho" / opinião em vez de "uma resposta".
Adam Cameron

Respostas:

94

selfnão se refere à instância, mas à classe atual. Não há como uma interface especificar que a mesma instância deve ser retornada - usar selfda maneira que você está tentando apenas forçaria que a instância retornada fosse da mesma classe.

Dito isso, as declarações de tipo de retorno em PHP devem ser invariáveis, enquanto o que você está tentando é covariante.

Seu uso de selfé equivalente a:

interface iFoo
{
    public function bar (string $baz) : iFoo;
}

class Foo implements iFoo
{

    public function bar (string $baz) : Foo  {...}
}

o que não é permitido.


O RFC de declarações de tipo de retorno tem o seguinte :

A imposição do tipo de retorno declarado durante a herança é invariável; isso significa que quando um subtipo substitui um método pai, o tipo de retorno do filho deve corresponder exatamente ao pai e não pode ser omitido. Se o pai não declarar um tipo de retorno, o filho poderá declarar um.

...

Este RFC propôs originalmente tipos de retorno covariante, mas foi alterado para invariante devido a alguns problemas. É possível adicionar tipos de retorno covariantes em algum ponto no futuro.


Por enquanto, pelo menos o melhor que você pode fazer é:

interface iFoo
{
    public function bar (string $baz) : iFoo;
}

class Foo implements iFoo
{

    public function bar (string $baz) : iFoo  {...}
}
user3942918
fonte
19
Dito isso, eu esperava que uma dica de tipo de retorno staticfuncionasse, mas nem mesmo é reconhecida
Mark Baker
Achei que seria esse o caso e é assim que vou resolver o problema. Eu, no entanto, teria preferido usar apenas: self, se possível, porque se uma classe está implementando uma interface, então um valor de retorno de self é implicitamente também um valor de retorno de uma instância da interface.
GordonM de
19
Paul, excluir os comentários que você excluiu aqui é prejudicial porque (A) perde informações importantes e (B) interrompe o fluxo da discussão em relação aos outros comentários. Não consigo ver nenhum motivo pelo qual seus comentários que dependem de Mark e Gordon precisaram ser excluídos. Na verdade, você está fazendo isso em todo lugar e precisa parar. Não há absolutamente nenhuma boa razão para voltar a uma pergunta de um ano e remover todos os seus comentários, destruindo completamente o fluxo da discussão. Na verdade, é prejudicial e perturbador.
Cody Gray
Há um prefácio importante para uma parte de sua citação vista aqui: "Os tipos de retorno covariante são considerados som de tipo e são usados ​​em muitas outras linguagens (C ++ e Java, mas não C #, eu acredito). Este RFC propôs originalmente tipos de retorno covariante, mas foi alterado para invariável devido a alguns problemas. ". Estou curioso para saber quais problemas o PHP teve. Sua escolha de design causa várias limitações que também causam problemas estranhos com características, tornando-os um tanto inúteis em muitos casos, a menos que você afrouxe as restrições de tipo de retorno (como visto em algumas respostas abaixo). Muito frustrante.
John Pancoast
1
@MarkBaker o tipo de retorno staticestá sendo adicionado ao PHP 8.
Ricardo Boss
16

Também pode ser uma solução, que você não defina explicitamente o tipo de retorno na Interface, apenas no PHPDoc e então você pode definir o tipo de retorno certo nas implementações:

interface iFoo
{
    public function bar (string $baz);
}

class Foo implements iFoo
{
    public function bar (string $baz) : Foo  {...}
}
Gabor
fonte
2
Ou em vez de Fooapenas usar self.
vez de
2

No caso, quando você quiser forçar da interface, esse método retornará objeto, mas o tipo de objeto não será o tipo de interface, mas a própria classe, então você pode escrever desta forma:

interface iFoo {
    public function bar (string $baz) : object;
}

class Foo implements iFoo {
    public function bar (string $baz) : self  {...}
}

Está funcionando desde o PHP 7.4.

em vez de
fonte
0

Este parece o comportamento esperado para mim.

Apenas altere seu Foo::barmétodo para retornar em iFoovez de selfe pronto.

Explicação:

selfconforme usado na interface significa "um objeto do tipo iFoo".
selfconforme usado na implementação significa "um objeto do tipo Foo".

Portanto, os tipos de retorno na interface e na implementação claramente não são os mesmos.

Um dos comentários menciona Java e se você teria esse problema. A resposta é sim, você teria o mesmo problema se Java permitisse que você escrevesse código assim - o que não é verdade. Como o Java requer que você use o nome do tipo em vez do selfatalho do PHP , você nunca verá isso de fato. (Veja aqui uma discussão sobre um problema semelhante em Java.)

Moshe Katz
fonte
Então self, declarar é semelhante a declarar MyClass::class?
peterchaula de
1
@Laser Sim, é.
Moshe Katz de
4
Mas se Foo implementa iFoo, então Foo é, por definição, do tipo iFoo
GordonM