Propriedades abstratas do PHP


126

Existe alguma maneira de definir propriedades de classe abstrata no PHP?

abstract class Foo_Abstract {
    abstract public $tablename;
}

class Foo extends Foo_Abstract {
    //Foo must 'implement' $property
    public $tablename = 'users';   
}

Respostas:


154

Não existe uma definição de propriedade.

Você só pode declarar propriedades porque elas são contêineres de dados reservados na memória na inicialização.

Uma função, por outro lado, pode ser declarada (tipos, nome, parâmetros) sem ser definida (falta o corpo da função) e, portanto, pode ser abstrata.

"Resumo" indica apenas que algo foi declarado mas não definido e, portanto, antes de usá-lo, você precisa defini-lo ou ele se torna inútil.


58
Não há razão óbvia para que a palavra "abstrato" não possa ser usada em propriedades estáticas - mas com um significado ligeiramente diferente. Por exemplo, isso pode indicar que uma subclasse precisa fornecer um valor para a propriedade.
Frodeborli

2
No TypeScript, existem propriedades abstratas e acessadores . É triste que no php seja impossível.
Decлья Зеленько

52

Não, não há como impor isso ao compilador, você precisaria usar verificações em tempo de execução (digamos, no construtor) para a $tablenamevariável, por exemplo:

class Foo_Abstract {
  public final function __construct(/*whatever*/) {
    if(!isset($this->tablename))
      throw new LogicException(get_class($this) . ' must have a $tablename');
  }
}

Para impor isso a todas as classes derivadas de Foo_Abstract, você teria que criar o construtor de Foo_Abstract final, impedindo a substituição.

Você pode declarar um getter abstrato:

abstract class Foo_Abstract {
  abstract public function get_tablename();
}

class Foo extends Foo_Abstract {
  protected $tablename = 'tablename';
  public function get_tablename() {
    return $this->tablename;
  }
}

Bom recurso, eu gosto de como você implementa propriedades abstratas.
Mathieu Dumoulin

4
Isso exigiria que você finalizasse o construtor na classe base abstrata.
1/11

3
Alguma explicação: se você fizer a verificação dentro do construtor e se for obrigatória, precisará garantir que ela seja executada em todas as instâncias da instância. Portanto, você precisa impedir que ele seja removido, por exemplo, estendendo a classe e substituindo o construtor. A palavra-chave final você permitiria.
hakre

1
Eu gosto da solução "abstract getter". Quando você declara uma função em um resumo de classe, deve declarar a própria classe abstrata. Isso significa que a classe não pode ser usada, a menos que seja estendida e totalmente implementada. Ao estender essa classe, você deve fornecer uma implementação para a função "getter". Isso significa que você também deve criar uma propriedade relacionada dentro da classe de extensão, porque a função deve ter algo para retornar. Seguindo esse padrão, você obtém o mesmo resultado que se declarasse uma propriedade abstrata, também é uma abordagem limpa e clara. É assim que é realmente feito.
Salivan

1
O uso de um getter abstrato também permite implementá-lo gerando um valor, em vez de retornar um valor constante, quando faz sentido fazê-lo. Uma propriedade abstrata não permitiria fazer isso, especialmente uma propriedade estática.
1113 Tobia

27

Dependendo do contexto da propriedade, se desejar forçar a declaração de uma propriedade de objeto abstrato em um objeto filho, gosto de usar uma constante com a staticpalavra - chave para a propriedade nos métodos construtor de objeto abstrato ou métodos setter / getter. Opcionalmente, você pode usar finalpara impedir que o método seja substituído em classes estendidas.

Fora isso, o objeto filho substitui a propriedade e os métodos do objeto pai, se redefinidos. Por exemplo, se uma propriedade é declarada comoprotected no pai e redefinida como publicno filho, a propriedade resultante é pública. No entanto, se a propriedade for declarada privatenos pais, ela permanecerá privatee não estará disponível para a criança.

http://www.php.net//manual/en/language.oop5.static.php

abstract class AbstractFoo
{
    public $bar;

    final public function __construct()
    {
       $this->bar = static::BAR;
    }
}

class Foo extends AbstractFoo
{
    //const BAR = 'foobar';
}

$foo = new Foo; //Fatal Error: Undefined class constant 'BAR' (uncomment const BAR = 'foobar';)
echo $foo->bar;

4
Solução mais elegante aqui
Jannie Theunissen

24

Como afirmado acima, não existe uma definição exata. No entanto, eu uso essa solução alternativa simples para forçar a classe filho a definir a propriedade "abstrata":

abstract class Father 
{
  public $name;
  abstract protected function setName(); // now every child class must declare this 
                                      // function and thus declare the property

  public function __construct() 
  {
    $this->setName();
  }
}

class Son extends Father
{
  protected function setName()
  {
    $this->name = "son";
  }

  function __construct(){
    parent::__construct();
  }
}

Elegante, mas não resolve o problema das staticpropriedades.
`` #

1
Eu não acho que você pode ter privado para métodos abstratos.
Zorji 13/01/15

@ Phate01 como eu o entendo, no próprio comentário que afirma the only "safe" methods to have in a constructor are private and/or final ones, não é minha solução alternativa nesse caso? im usando privates nele
ulkas

4
Parece bom, mas não força uma classe filho a ser definida $name. Você pode implementar a setName()função sem que ela realmente esteja configurada $name.
JohnWE

3
Eu acho que usar em getNamevez de $namefunciona melhor. abstract class Father { abstract protected function getName(); public function foo(){ echo $this->getName();} }
Hamid

7

Fiz a mesma pergunta hoje e gostaria de adicionar meus dois centavos.

A razão pela qual gostaríamos de abstractpropriedades é garantir que as subclasses as definam e gerem exceções quando não o fazem. No meu caso específico, eu precisava de algo que pudesse funcionar com um staticaliado.

Idealmente, eu gostaria de algo assim:

abstract class A {
    abstract protected static $prop;
}

class B extends A {
    protected static $prop = 'B prop'; // $prop defined, B loads successfully
}

class C extends A {
    // throws an exception when loading C for the first time because $prop
    // is not defined.
}

Eu terminei com esta implementação

abstract class A
{
    // no $prop definition in A!

    public static final function getProp()
    {
        return static::$prop;
    }
}

class B extends A
{
    protected static $prop = 'B prop';
}

class C extends A
{
}

Como você pode ver, em AEu não defino $prop, mas eu o uso em um staticgetter. Portanto, o código a seguir funciona

B::getProp();
// => 'B prop'

$b = new B();
$b->getProp();
// => 'B prop'

Por Coutro lado, eu não defino $prop, então recebo exceções:

C::getProp();
// => Exception!

$c = new C();
$c->getProp();
// => Exception!

Devo ligar para o getProp()  método para obter a exceção e não posso obtê-lo no carregamento de classe, mas é bastante próximo do comportamento desejado, pelo menos no meu caso.

Defino getProp()como finalevitar que um cara esperto (eu mesmo em 6 meses) fique tentado a fazer

class D extends A {
    public static function getProp() {
        // really smart
    }
}

D::getProp();
// => no exception...

Este é um truque muito engenhoso. Espero que isso não precise ser feito no futuro.
precisa saber é o seguinte

6

Como você poderia ter descoberto testando seu código:

Erro fatal: as propriedades não podem ser declaradas abstratas em ... na linha 3

Não, não há. As propriedades não podem ser declaradas abstratas no PHP.

No entanto, você pode implementar um resumo da função getter / setter, isso pode ser o que você está procurando.

As propriedades não são implementadas (especialmente propriedades públicas), elas simplesmente existem (ou não):

$foo = new Foo;
$foo->publicProperty = 'Bar';

6

A necessidade de propriedades abstratas pode indicar problemas de design. Enquanto muitas das respostas implementam o tipo de padrão do método Template e funcionem, sempre parece meio estranho.

Vamos dar uma olhada no exemplo original:

abstract class Foo_Abstract {
    abstract public $tablename;
}

class Foo extends Foo_Abstract {
    //Foo must 'implement' $property
    public $tablename = 'users';   
}

Marcar algo abstracté indicá-lo como algo indispensável. Bem, um valor obrigatório (neste caso) é uma dependência necessária, portanto deve ser passado ao construtor durante a instanciação :

class Table
{
    private $name;

    public function __construct(string $name)
    {
        $this->name = $name;
    }

    public function name(): string
    {
        return $this->name;
    }
}

Então, se você realmente deseja uma classe nomeada mais concreta, pode herdar da seguinte maneira:

final class UsersTable extends Table
{
    public function __construct()
    {
        parent::__construct('users');
    }
}

Isso pode ser útil se você usar o contêiner DI e precisar passar tabelas diferentes para objetos diferentes.


3

O PHP 7 facilita bastante a criação de "propriedades" abstratas. Assim como acima, você as cria criando funções abstratas, mas com o PHP 7 você pode definir o tipo de retorno para essa função, o que facilita muito as coisas ao criar uma classe base que qualquer pessoa pode estender.

<?php

abstract class FooBase {

  abstract public function FooProp(): string;
  abstract public function BarProp(): BarClass;

  public function foo() {
    return $this->FooProp();
  }

  public function bar() {
    return $this->BarProp()->name();
  }

}

class BarClass {

  public function name() {
    return 'Bar!';
  }

}

class FooClass extends FooBase {

  public function FooProp(): string {
    return 'Foo!';
  }

  public function BarProp(): BarClass {
    // This would not work:
    // return 'not working';
    // But this will!
    return new BarClass();
  }

}

$test = new FooClass();
echo $test->foo() . PHP_EOL;
echo $test->bar() . PHP_EOL;

1

se o valor do nome da tabela nunca for alterado durante a vida útil do objeto, a seguir será uma implementação simples, porém segura.

abstract class Foo_Abstract {
    abstract protected function getTablename();

    public function showTableName()
    {
        echo 'my table name is '.$this->getTablename();
    }
}

class Foo extends Foo_Abstract {
    //Foo must 'implement' getTablename()
    protected function getTablename()
    {
        return 'users';
    }
}

a chave aqui é que o valor da string 'users' é especificado e retornado diretamente em getTablename () na implementação da classe filho. A função imita uma propriedade "somente leitura".

Isso é bastante semelhante a uma solução publicada anteriormente, na qual usa uma variável adicional. Também gosto da solução de Marco, embora possa ser um pouco mais complicada.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.