Wpisz podpowiedź do właściwości w PHP 7?

80

Czy php 7 obsługuje podpowiedzi typu dla właściwości klas?

Mam na myśli, nie tylko dla ustawiających / pobierających, ale dla samej właściwości.

Coś jak:

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

$fooInstance = new Foo();
$fooInstance->bar = new NotBar(); //Error
CarlosCarucce
źródło
1
Nie, że jestem świadomy. Jednak ogólnie rzecz biorąc, wszelkie ograniczenia dotyczące wartości właściwości powinny i tak być realizowane za pośrednictwem metody ustawiającej. Ponieważ program ustawiający może łatwo mieć wskazówkę dotyczącą typu dla argumentu „wartość”, możesz zaczynać.
Niet the Dark Absol
Wiele dostępnych frameworków używa chronionych atrybutów (głównie dla kontrolerów). Szczególnie w takich przypadkach byłoby to bardzo przydatne.
CarlosCarucce

Odpowiedzi:

134

PHP 7.4 będzie obsługiwać wpisane właściwości, takie jak:

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

PHP 7.3 i wcześniejsze nie obsługują tego, ale jest kilka alternatyw.

Możesz utworzyć własność prywatną, która jest dostępna tylko poprzez metody pobierające i ustawiające, które mają deklaracje typu:

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

Możesz również utworzyć właściwość publiczną i użyć docblock, aby przekazać informacje o typie osobom czytającym kod i używającym IDE, ale nie zapewnia to sprawdzania typu w czasie wykonywania:

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

I rzeczywiście, możesz łączyć metody pobierające i ustawiające oraz blok dokumentów.

Jeśli jesteś bardziej ryzykowny, można zrobić właściwość fałszywe z __get, __set, __isseti __unsetmagicznych metod i sprawdzić typy siebie. Nie jestem jednak pewien, czy bym to polecił.

Andrea
źródło
Brzmi naprawde dobrze. Nie mogę się doczekać, aby zobaczyć, co pojawi się w kolejnych wydaniach!
CarlosCarucce
Innym ważnym problemem jest obsługa odwołań, które tak naprawdę nie współdziałają dobrze z deklaracjami typu i mogą być wyłączone dla takich właściwości. Nawet bez problemów z wydajnością, niemożność zrobienia, powiedzenia array_push($this->foo, $bar)lub sort($this->foobar)byłaby wielka sprawa.
Andrea
Jak będzie działać przymus typów? Na przykład: (new Person())->dateOfBirth = '2001-01-01';... Pod warunkiem, declare(strict_types=0);że tak jest. Czy rzuci, czy użyje DateTimeImmutablekonstruktora? A jeśli tak jest, jaki rodzaj błędu zostanie zgłoszony, jeśli ciąg jest nieprawidłową datą? TypeError?
lmerino
@Imerino Nie ma niejawnej konwersji do DateTime (Immutable) i nigdy nie było
Andrea
12

7.4+:

Dobra wiadomość, że zostanie zaimplementowana w nowych wydaniach, jak wskazał @Andrea. Po prostu zostawię to rozwiązanie na wypadek, gdyby ktoś chciał z niego skorzystać przed 7.4


7,3 lub mniej

Na podstawie powiadomień, które nadal otrzymuję z tego wątku, uważam, że wiele osób miało / ma ten sam problem, co ja. Moim rozwiązaniem w tym przypadku było połączenie seterów + __setmetody magicznej wewnątrz cechy w celu zasymulowania tego zachowania. Oto ona:

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

A oto demonstracja:

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

Wyjaśnienie

Przede wszystkim zdefiniuj bar jako własność prywatną, aby PHP rzutowało __set automatycznie .

__setsprawdzi, czy w bieżącym obiekcie ( method_exists($this, $setter)) jest zadeklarowany setter . W przeciwnym razie ustawi swoją wartość tak, jak zwykle.

Zadeklaruj metodę ustawiającą (setBar), która odbiera argument ze wskazówką o typie ( setBar(Bar $bar)).

Dopóki PHP wykryje, że coś, co nie jest Barinstancją, jest przekazywane do ustawiającego, automatycznie wywoła błąd krytyczny: Uncaught TypeError: Argument 1 przekazany do Foo :: setBar () musi być instancją Bar, podana instancja NotBar

CarlosCarucce
źródło
4

Edycja dla PHP 7.4:

Od PHP 7.4 możesz wpisywać atrybuty ( Dokumentacja / Wiki ), co oznacza, że ​​możesz:

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

Według wiki wszystkie dopuszczalne wartości to:

  • bool, int, float, string, array, object
  • iterowalne
  • ja, rodzic
  • dowolna nazwa klasy lub interfejsu
  • ? type // gdzie „typ” może oznaczać dowolne z powyższych

PHP <7,4

W rzeczywistości nie jest to możliwe i masz tylko 4 sposoby, aby to faktycznie zasymulować:

  • Wartości domyślne
  • Dekoratory w blokach komentarzy
  • Wartości domyślne w konstruktorze
  • Getters i setters

Połączyłem je wszystkie tutaj

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

Zwróć uwagę, że w rzeczywistości możesz wpisać zwrot jako? Bar, ponieważ php 7.1 (dopuszcza wartość null), ponieważ może mieć wartość null (niedostępne w php7.0.)

Możesz również wpisać zwrot jako nieważny, ponieważ php7.1

Bruno Guignard
źródło
1

Możesz użyć settera

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);

Wynik:

TypeError: Argument 1 passed to Foo::setBar() must be an instance of Bar, instance of Foo given, called in ...
Richard
źródło