¿Escribir sugerencias para propiedades en PHP 7?

80

¿PHP 7 admite sugerencias de tipo para propiedades de clase?

Quiero decir, no solo para setters / getters sino para la propiedad en sí.

Algo como:

class Foo {
    /**
     *
     * @var Bar
     */
    public $bar : Bar;
}

$fooInstance = new Foo();
$fooInstance->bar = new NotBar(); //Error
CarlosCarucce
fuente
1
No que yo supiese. Sin embargo, en términos generales, cualquier restricción sobre el valor de una propiedad debe hacerse a través de un establecedor de todos modos. Dado que el definidor puede tener fácilmente una sugerencia para el argumento "valor", está listo para comenzar.
Niet the Dark Absol
Muchos frameworks hacen uso de atributos protegidos (principalmente para controladores). Para esos casos en particular sería muy útil.
CarlosCarucce

Respuestas:

134

PHP 7.4 admitirá propiedades escritas así:

class Person
{
    public string $name;
    public DateTimeImmutable $dateOfBirth;
}

PHP 7.3 y versiones anteriores no lo admiten, pero existen algunas alternativas.

Puede crear una propiedad privada a la que solo se pueda acceder a través de getters y setters que tengan declaraciones de tipo:

class Person
{
    private $name;
    public function getName(): string {
        return $this->name;
    }
    public function setName(string $newName) {
        $this->name = $newName;
    }
}

También puede hacer una propiedad pública y usar un docblock para proporcionar información de tipo a las personas que leen el código y usan un IDE, pero esto no proporciona verificación de tipo en tiempo de ejecución:

class Person
{
    /**
      * @var string
      */
    public $name;
}

Y, de hecho, puede combinar getters y setters y un docblock.

Si usted es más aventurero, puede hacer que una propiedad falso con los __get, __set, __issety __unsetmágicos métodos , y comprobar los tipos de sí mismo. Sin embargo, no estoy seguro de si lo recomendaría.

Andrea
fuente
Suena muy bien. ¡No puedo esperar a ver qué viene en los próximos lanzamientos!
CarlosCarucce
Otro problema importante es el manejo de referencias, que realmente no interactúan bien con las declaraciones de tipos y es posible que deban deshabilitarse para tales propiedades. Incluso sin los problemas de rendimiento, no poder hacer, decir array_push($this->foo, $bar)o sort($this->foobar)sería un gran problema.
Andrea
¿Cómo funcionará la coerción de tipos? Por ejemplo: (new Person())->dateOfBirth = '2001-01-01';... Siempre declare(strict_types=0);que sea así. ¿Lanzará o usará el DateTimeImmutableconstructor? Y si ese es el caso, ¿qué tipo de error se producirá si la cadena es una fecha no válida? TypeError?
lmerino
@Imerino No hay conversión implícita a DateTime (inmutable) y nunca lo ha sido
Andrea
12

7.4+:

Buenas noticias que se implementará en los nuevos lanzamientos, como señaló @Andrea. Dejaré esta solución aquí en caso de que alguien quiera usarla antes de 7.4


7.3 o menos

Según las notificaciones que todavía recibo de este hilo, creo que muchas personas han tenido el mismo problema que yo. Mi solución para este caso fue combinar setters + __setmétodo mágico dentro de un rasgo para simular este comportamiento. Aquí está:

trait SettersTrait
{
    /**
     * @param $name
     * @param $value
     */
    public function __set($name, $value)
    {
        $setter = 'set'.$name;
        if (method_exists($this, $setter)) {
            $this->$setter($value);
        } else {
            $this->$name = $value;
        }
    }
}

Y aquí está la demostración:

class Bar {}
class NotBar {}

class Foo
{
    use SettersTrait; //It could be implemented within this class but I used it as a trait for more flexibility

    /**
     *
     * @var Bar
     */
    private $bar;

    /**
     * @param Bar $bar
     */
    protected function setBar(Bar $bar)
    {
        //(optional) Protected so it wont be called directly by external 'entities'
        $this->bar = $bar;
    }
}

$foo = new Foo();
$foo->bar = new NotBar(); //Error
//$foo->bar = new Bar(); //Success

Explicación

En primer lugar, defina barcomo una propiedad privada para que PHP se __set transmita automáticamente .

__setcomprobará si hay algún setter declarado en el objeto actual ( method_exists($this, $setter)). De lo contrario, solo establecerá su valor como lo haría normalmente.

Declare un método de establecimiento (setBar) que recibe un argumento con sugerencia de tipo ( setBar(Bar $bar)).

Siempre que PHP detecte que Barse está pasando algo que no es una instancia al establecedor, se activará automáticamente un Error fatal: Error de tipo no detectado: El argumento 1 pasado a Foo :: setBar () debe ser una instancia de Bar, instancia de NotBar dada

CarlosCarucce
fuente
4

Editar para PHP 7.4:

Desde PHP 7.4 puede escribir atributos ( Documentación / Wiki ), lo que significa que puede hacer:

    class Foo
{
    protected ?Bar $bar;
    public int $id;
    ...
}

Según wiki, todos los valores aceptables son:

  • bool, int, float, cadena, matriz, objeto
  • iterable
  • yo, padre
  • cualquier clase o nombre de interfaz
  • ? tipo // donde "tipo" puede ser cualquiera de los anteriores

PHP <7.4

En realidad, no es posible y solo tiene 4 formas de simularlo:

  • Valores predeterminados
  • Decoradores en bloques de comentarios
  • Valores predeterminados en el constructor
  • Getters y setters

Los combiné todos aquí

class Foo
{
    /**
     * @var Bar
     */
    protected $bar = null;

    /** 
    * Foo constructor
    * @param Bar $bar
    **/
    public function __construct(Bar $bar = null){
        $this->bar = $bar;
    }
    
    /**
    * @return Bar
    */
    public function getBar() : ?Bar{
        return $this->bar;
    }

    /**
    * @param Bar $bar
    */
    public function setBar(Bar $bar) {
        $this->bar = $bar;
    }
}

Tenga en cuenta que en realidad puede escribir el retorno como? Bar desde php 7.1 (anulable) porque podría ser nulo (no disponible en php7.0).

También puede escribir el retorno como vacío desde php7.1

Bruno Guignard
fuente
1

Puedes usar setter

class Bar {
    public $val;
}

class Foo {
    /**
     *
     * @var Bar
     */
    private $bar;

    /**
     * @return Bar
     */
    public function getBar()
    {
        return $this->bar;
    }

    /**
     * @param Bar $bar
     */
    public function setBar(Bar $bar)
    {
        $this->bar = $bar;
    }

}

$fooInstance = new Foo();
// $fooInstance->bar = new NotBar(); //Error
$fooInstance->setBar($fooInstance);

Salida:

TypeError: Argument 1 passed to Foo::setBar() must be an instance of Bar, instance of Foo given, called in ...
Ricardo
fuente