Propiedades abstractas de PHP


¿Hay alguna manera de definir propiedades de clase abstractas en PHP?

abstract class Foo_Abstract {
    abstract public $tablename;
}

class Foo extends Foo_Abstract {
    //Foo must 'implement' $property
    public $tablename = 'users';   
}
Author: Tamás Pap, 2011-10-03

8 answers

No existe tal cosa como definir una propiedad.

Solo puede declarar propiedades porque son contenedores de datos reservados en memoria al inicializar.

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

"Abstract" solo indica que algo fue declarado pero no definido y por lo tanto antes de usarlo, necesita definirlo o se vuelve inútil.

 127
Author: Mathieu Dumoulin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-07-03 12:48:01

No, no hay manera de hacer cumplir eso con el compilador, tendría que usar comprobaciones en tiempo de ejecución (por ejemplo, en el constructor) para la variable $tablename, 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 sobreescritura.

Podría declarar un getter abstracto en su lugar:

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

class Foo extends Foo_Abstract {
  protected $tablename = 'tablename';
  public function get_tablename() {
    return $this->tablename;
  }
}
 42
Author: connec,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-10-03 18:13:55

Como se indicó anteriormente, no existe tal definición exacta. Yo, sin embargo, uso esta solución simple para forzar a la clase hija 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();
  }
}
 20
Author: ulkas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-03-02 08:28:42

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 palabra clave static para la propiedad en los métodos constructor de objeto abstracto o setter/getter.

Aparte de eso, el objeto hijo anula la propiedad del objeto padre y los métodos si se redefine. Por ejemplo, si una propiedad es declarada como protected en el padre y redefinida como public en el hijo, la propiedad resultante es pública. Sin embargo, si la propiedad se declara private en el padre, permanecerá private y no estará disponible para el hijo.

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

abstract class AbstractFoo
{
    public $bar;

    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;
 13
Author: fyrye,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-07-15 13:54:20

Como podrías haber averiguado simplemente probando tu código:

Error fatal: Las propiedades no se pueden declarar abstractas en... en la línea 3

No, no lo hay. Las propiedades no se pueden declarar abstractas en PHP.

Sin embargo, puede implementar una función getter/setter abstract, esto podría ser lo que está buscando.

Las propiedades no están implementadas (especialmente las propiedades públicas), solo existen (o no):

$foo = new Foo;
$foo->publicProperty = 'Bar';
 5
Author: hakre,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-10-03 12:31:23

Me he hecho la misma pregunta hoy, y me gustaría añadir mis dos centavos.

La razón por la que nos gustaría abstract properties es para asegurarnos de que las subclases las definen y lanzan excepciones cuando no lo hacen. En mi caso específico, necesitaba algo que pudiera funcionar con static ally.

, 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 puedes ver, en A no defino $prop, pero lo uso en un static getter. 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, así que obtengo excepciones:

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

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

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

Defino getProp() como final para evitar que algún tipo inteligente (también conocido como yo mismo en 6 meses) esté tentado a hacer{[16]]}

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

D::getProp();
// => no exception...
 5
Author: Marco Pallante,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-07-15 13:48:16

La necesidad de propiedades abstractas puede indicar problemas de diseño. Mientras que muchas de las respuestas implementan una especie de Patrón de método de plantilla y funciona, siempre se ve 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 abstract es indicarlo como algo imprescindible. Bueno, un valor imprescindible (en este caso) es una dependencia requerida, por lo que debe pasarse al constructor durante instanciación :

class Table
{
    private $name;

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

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

Entonces si realmente quieres una clase con nombre más concreta puedes heredar así:

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

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

 2
Author: sevavietl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-04-13 19:23:26

Si el valor tablename nunca cambiará durante la vida útil del objeto, lo 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 'users' se especifica y se devuelve directamente en getTablename() en la implementación de la clase hija. La función imita una propiedad "readonly".

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

 1
Author: ck.tan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-08 14:43:31