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

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


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


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


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 ...
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.