Propiedades abstractas de PHP

126

¿Hay alguna forma de definir propiedades de clase abstracta en PHP?

abstract class Foo_Abstract {
    abstract public $tablename;
}

class Foo extends Foo_Abstract {
    //Foo must 'implement' $property
    public $tablename = 'users';   
}
Tamás Pap
fuente

Respuestas:

154

No existe tal cosa como definir una propiedad.

Solo puede declarar propiedades porque son contenedores de datos reservados en la memoria en la inicialización.

Por otro lado, una función puede declararse (tipos, nombre, parámetros) sin definirse (falta el cuerpo de la función) y, por lo tanto, puede hacerse abstracta.

"Resumen" solo indica que algo se declaró pero no se definió y, por lo tanto, antes de usarlo, debe definirlo o se volverá inútil.

Mathieu Dumoulin
fuente
59
No hay una razón obvia para que la palabra 'abstracto' no pueda usarse en propiedades estáticas , pero con un significado ligeramente diferente. Por ejemplo, podría indicar que una subclase tiene que proporcionar un valor para la propiedad.
frodeborli
2
En TypeScript hay propiedades abstractas y accesores . Es triste que en php sea imposible.
Илья Зеленько
52

No, no hay forma de aplicar eso con el compilador, tendría que usar verificaciones en tiempo de ejecución (por ejemplo, en el constructor) para la $tablenamevariable, por ejemplo:

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

Para hacer cumplir esto para todas las clases derivadas de Foo_Abstract, tendría que hacer el constructor de Foo_Abstract final, evitando la anulación.

En su lugar, podría declarar un captador abstracto:

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

class Foo extends Foo_Abstract {
  protected $tablename = 'tablename';
  public function get_tablename() {
    return $this->tablename;
  }
}
Connec
fuente
Buena característica, me gusta cómo implementa propiedades abstractas.
Mathieu Dumoulin
44
Esto requeriría que el constructor sea final en la clase base abstracta.
Hakre
3
Alguna explicación: si realiza la verificación dentro del constructor y si debe ser obligatorio, debe asegurarse de que se ejecute en cada instancia de instancia. Por lo tanto, debe evitar que se elimine, por ejemplo, extendiendo la clase y reemplazando el constructor. La palabra clave final permitiría hacerlo.
Hakre
1
Me gusta la solución "getter abstracto". Cuando declara una función en un resumen de clase, debe declarar la clase en sí misma abstracta. Esto significa que la clase no se puede usar a menos que se extienda y se implemente completamente. Al extender esa clase, debe proporcionar una implementación para la función "getter". Esto significa que también debe crear una propiedad relacionada dentro de la clase extendida, porque la función debe tener algo que devolver. Siguiendo este patrón, obtiene el mismo resultado que si declarara una propiedad abstracta, también es un enfoque limpio y claro. Así es como se hace realmente.
Salivan
1
El uso de un captador abstracto también le permite implementarlo generando un valor, en lugar de devolver un valor constante, cuando tiene sentido hacerlo. Una propiedad abstracta no le permitiría hacerlo, especialmente una propiedad estática.
Tobia
27

Dependiendo del contexto de la propiedad si quiero forzar la declaración de una propiedad de objeto abstracto en un objeto hijo, me gusta usar una constante con la staticpalabra clave para la propiedad en el constructor de objetos abstractos o métodos setter / getter. Opcionalmente, puede usar finalpara evitar que el método se anule en clases extendidas.

Aparte de eso, el objeto secundario anula la propiedad y los métodos del objeto principal si se redefine. Por ejemplo, si una propiedad se declara como protecteden el padre y se redefine como publicen el hijo, la propiedad resultante es pública. Sin embargo, si la propiedad se declara privateen el padre, permanecerá privatey no estará disponible para el niño.

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;
fyrye
fuente
44
La solución más elegante aquí
Jannie Theunissen
24

Como se indicó anteriormente, no existe tal definición exacta. Sin embargo, utilizo esta solución simple para forzar a la clase secundaria a definir la propiedad "abstracta":

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();
  }
}
ulkas
fuente
Elegante, pero no resuelve el problema de las staticpropiedades.
Robbert
1
No creo que puedas tener métodos privados para abstractos.
Zorji
@ Phate01 tal como lo entiendo, en el comentario en sí dice the only "safe" methods to have in a constructor are private and/or final ones, ¿no es mi solución este caso? estoy usando
privates
44
Esto se ve bien, pero no obliga a una clase secundaria a establecerse realmente $name. Puede implementar la setName()función sin que realmente se configure $name.
JohnWE
3
Creo que usar en getNamelugar de $namefunciona mejor. abstract class Father { abstract protected function getName(); public function foo(){ echo $this->getName();} }
Hamid
7

Hoy me hice la misma pregunta y me gustaría agregar mis dos centavos.

La razón por la que nos gustaría tener abstractpropiedades es para asegurarnos de que las subclases las definan y arrojen excepciones cuando no lo hacen. En mi caso específico, necesitaba algo que pudiera funcionar con staticaliado.

Idealmente me gustaría algo como esto:

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.
}

Terminé con esta implementación

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 puede ver, en ANo lo defino $prop, pero lo uso en un staticcaptador. Por lo tanto, el siguiente código funciona

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

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

En C, por otro lado, no defino $prop, por lo que obtengo excepciones:

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

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

Debo llamar al getProp() método para obtener la excepción y no puedo obtenerlo al cargar la clase, pero está bastante cerca del comportamiento deseado, al menos en mi caso.

Defino getProp()como finalevitar que algún tipo inteligente (también conocido como yo en 6 meses) tenga la tentación de hacer

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

D::getProp();
// => no exception...
Marco Pallante
fuente
Este es un truco muy ingenioso. Esperemos que esto no necesite hacerse en el futuro.
CMCDragonkai
6

Como podría haber descubierto simplemente probando su código:

Error grave: las propiedades no se pueden declarar abstractas en ... en la línea 3

No no hay. Las propiedades no pueden declararse abstractas en PHP.

Sin embargo, puede implementar un resumen de la función getter / setter, esto podría ser lo que está buscando.

Las propiedades no se implementan (especialmente las propiedades públicas), solo existen (o no):

$foo = new Foo;
$foo->publicProperty = 'Bar';
hakre
fuente
6

La necesidad de propiedades abstractas puede indicar problemas de diseño. Si bien muchas de las respuestas implementan un tipo de patrón de método de Plantilla y funciona, siempre parece un poco extraño.

Echemos un vistazo al ejemplo original:

abstract class Foo_Abstract {
    abstract public $tablename;
}

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

Marcar algo abstractes indicarlo como algo imprescindible. Bueno, un valor imprescindible (en este caso) es una dependencia requerida, por lo que debe pasarse al constructor durante la creación de instancias :

class Table
{
    private $name;

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

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

Entonces, si realmente desea una clase con nombre más concreta, puede heredar así:

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

Esto puede ser útil si usa el contenedor DI y tiene que pasar diferentes tablas para diferentes objetos.

sevavietl
fuente
3

PHP 7 lo hace bastante más fácil para hacer "propiedades" abstractas. Al igual que anteriormente, los creará creando funciones abstractas, pero con PHP 7 puede definir el tipo de retorno para esa función, lo que facilita mucho las cosas cuando construye una clase base que cualquiera puede ampliar.

<?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;
Dropa
fuente
1

si el valor del nombre de la tabla nunca cambiará durante la vida útil del objeto, la siguiente será una implementación simple pero 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';
    }
}

La clave aquí es que el valor de cadena 'usuarios' se especifica y se devuelve directamente en getTablename () en la implementación de la clase secundaria. La función imita una propiedad de "solo lectura".

Esto es bastante similar a una solución publicada anteriormente que utiliza una variable adicional. También me gusta la solución de Marco, aunque puede ser un poco más complicada.

ck.tan
fuente