Php static data type

Возвращаемый тип self и static в интерфейсах PHP

Очень популярная история в PHP, когда метод объекта возвращает сам объект или новый экземпляр того же самого объекта. В этом случае мы указываем тип результата self и всё отлично работает. Например:

final class Car < public function __construct( private ?string $color = null, ) < >public function withColor(string $color): self < $new = clone $this; $new->color = $color; return $new; > >

С финальными классами это действительно гарантированно работает. Но в случае с интерфейсами (а также с не финальными классами, абстрактными классами и трейтами, но в этой статьей рассмотрим только интерфейсы) появляется не очевидная на первый взгляд проблема.

Проблема с self

Допустим у нас есть два интерфейса. Объекты, которые их реализуют, позволяют указать цвет и имя соответственно:

interface ColorableInterface < public function setColor(string $color): self; >interface NameableInterface

И мы решили сделать некий конфигуратор для объектов, реализующих оба интерфейса:

final class Configurator < public function configure( ColorableInterface&NameableInterface $object, string $color, string $name ): void < $object->setColor($color)->setName($name); > >

И вроде бы всё хорошо, но если прогнать этот код, к примеру, через статический анализатор Psalm, то мы получим ошибку «Method ColorableInterface::setName does not exist» («Метод ColorableInterface::setName не существует»).

И действительно, Psalm не ошибся. Интерфейс ColorableInterface гарантирует, что метод setColor() вернёт self , то есть объект, реализующий ColorableInterface , и ничего более, а метода setName() в интерфейсе ColorableInterface нет.

Пример объекта, реализующего оба интерфейса, но при этом не готового для использования в конфигураторе из предыдущего примера:

final class CustomColor implements ColorableInterface < private string $color = ''; public function setColor(string $color): self < $this->color = $color; return $this; > > final class CustomName implements NameableInterface < private string $name = ''; public function setName(string $name): self < $this->name = $name; return $this; > > final class StrangeObject implements ColorableInterface, NameableInterface < public function setColor(string $color): ColorableInterface < return new CustomColor(); >public function setName(string $name): NameableInterface < return new CustomName(); >>

Возвращаемый тип static

В PHP 8.0 появилась возможность указать в качестве результата выполнения метода тип static , который на уровне языка гарантирует, что метод вернёт экземпляр того же самого класса, в котором он вызван.

Теперь мы можем переписать интерфейсы следующим образом:

interface ColorableInterface < public function setColor(string $color): static; >interface NameableInterface

И это решит описанную выше проблему.

Когда использовать static?

Как правило, static используется в следующих ситуациях.

Текучие интерфейсы

Текучий интерфейс — структурный шаблон проектирования, позволяющий создавать более читабельный код. Фактически, это возможность вызова методов «цепочкой».

Неизменяемые классы

Неизменяемый (иммутабельный) класс — это класс, который после создания не меняет своего состояния, то есть он не содержит сеттеров и публичных изменяемых свойств. Но такие классы могут содержать методы (обычно они имеют префикс with ), позволяющие получить клон объекта с изменённым состоянием. Как раз для таких методов в интерфейсах необходимо использовать тип static .

interface ReadableInterface

Порождающие статические методы

Статические методы, позволяющие создать экземпляр объекта текущего класса.

Читайте также:  Html link img and text

Интерфейсы для «одиночек»

Интерфейс, определяющий метод для реализации шаблона проектирования «Одиночка» (метод предоставляющий доступ к единственному экземпляру объекта в приложении и запрещающий повторное создание этого объекта).

Заключение

Таким образом, при проектировании интерфейсов в PHP мы можем сразу заложить, что ожидаем увидеть в качестве результата выполнения метода:

  • self — любой объект, который реализует данный интерфейс;
  • static — экземпляр объекта того же класса, что и объект в котором вызван метод.

Источник

Php static data type

While waiting for native support for typed arrays, here are a couple of alternative ways to ensure strong typing of arrays by abusing variadic functions. The performance of these methods is a mystery to the writer and so the responsibility of benchmarking them falls unto the reader.

PHP 5.6 added the splat operator (. ) which is used to unpack arrays to be used as function arguments. PHP 7.0 added scalar type hints. Latter versions of PHP have further improved the type system. With these additions and improvements, it is possible to have a decent support for typed arrays.

function typeArrayNullInt (? int . $arg ): void >

function doSomething (array $ints ): void (function (? int . $arg ) <>)(. $ints );
// Alternatively,
( fn (? int . $arg ) => $arg )(. $ints );
// Or to avoid cluttering memory with too many closures
typeArrayNullInt (. $ints );

function doSomethingElse (? int . $ints ): void /* . */
>

$ints = [ 1 , 2 , 3 , 4 , null ];
doSomething ( $ints );
doSomethingElse (. $ints );
?>

Both methods work with all type declarations. The key idea here is to have the functions throw a runtime error if they encounter a typing violation. The typing method used in doSomethingElse is cleaner of the two but it disallows having any other parameters after the variadic parameter. It also requires the call site to be aware of this typing implementation and unpack the array. The method used in doSomething is messier but it does not require the call site to be aware of the typing method as the unpacking is performed within the function. It is also less ambiguous as the doSomethingElse would also accept n individual parameters where as doSomething only accepts an array. doSomething’s method is also easier to strip away if native typed array support is ever added to PHP. Both of these methods only work for input parameters. An array return value type check would need to take place at the call site.

Читайте также:  Include in header or cpp file

If strict_types is not enabled, it may be desirable to return the coerced scalar values from the type check function (e.g. floats and strings become integers) to ensure proper typing.

same data type and same value but first function declare as a argument type declaration and return int(7)
and second fucntion declare as a return type declaration but return int(8).

function argument_type_declaration(int $a, int $b) return $a+$b;
>

function return_type_declaration($a,$b) :int return $a+$b;
>

Источник

PHP RFC: Static return type

The static special class name in PHP refers to the class a method was actually called on, even if the method is inherited. This is known as “late static binding” (LSB). This RFC proposes to make static also usable as a return type (next to the already usable self and parent types).

There are a number of typical use-cases where static return types appear (currently in the form of @return static ).

One are named constructors:

class Test { public function createFromWhatever($whatever): static { return new static($whatever); } }

Here we want to specify that XXX::createFromWhatever() will always create an instance of XXX , not of some parent class.

Another are withXXX() style interfaces for mutating immutable objects:

class Test { public function withWhatever($whatever): static { $clone = clone $this; $clone->whatever = $whatever; return $clone; } }

Here we want to specify that $foobar->withWhatever() will return a new object of class get_class($foobar) , not of some parent class.

Finally, the likely most common use case are fluent methods:

class Test { public function doWhatever(): static { // Do whatever. return $this; } }

Here we actually have a stronger contract than in the previous two cases, in that we require not just an object of the same class to be returned, but exactly the same object. However, from the type system perspective, the important property we need is that the return value is an instance of the same class, not a parent class.

Proposal

Allowed positions

The static type is only allowed inside return types, where it may also appear as part of a complex type expression, such as ?static or static|array .

To understand why static cannot be used as a parameter type (apart from the fact that this just makes little sense from a practical perspective), consider the following example:

class A { public function test(static $a) {} } class B extends A {} function call_with_new_a(A $a) { $a->test(new A); } call_with_new_a(new B);

Under the Liskov substitution principle (LSP), we should be able to substitute class B anywhere class A is expected. However, in this example passing B instead of A will throw a TypeError , because B::test() does not accept a A as a parameter.

Читайте также:  Creating loop in java

More generally, static is only sound in covariant contexts, which at present are only return types.

For property types, we have the additional problem that the static type conflicts with the static modifier:

class A { // Is this an untyped static property, // or an instance property of type static? public static $a; }

For this reason, we disallow static types in properties/parameters already at the grammar level, rather than emitting a nicer error message in the compiler.

Variance and Subtyping

For the purpose of variance checks, static is considered a subtype of self . That is, the following inheritance is legal:

class A { public function test(): self {} } class B extends A { public function test(): static {} } class C extends B {}

When considering just class B , replacing a self type with a static type results in identical behavior. However, the return value of C::test() is further restricted relative to a self type. For this reason static is considered a subtype of self .

The converse replacement shown in the following is not legal:

class A { public function test(): static {} } class B extends A { public function test(): self {} } class C extends B { // To spell out the inherited signature: public function test(): B {} }

In this case, the effective return type of C::test() is B , even though the original type on A::test() would have required it to be C . This violates covariance/LSP.

It should be noted that self here refers to the resolved type of the current class, it does not have to be spelled as self in particular. For example, the following is also legal:

class A { public function test(): A {} } class B extends A {} class C extends B { public function test(): static {} }

Here, self is C , which is a subtype of A , making the replacement with static legal.

Reflection

While internally the static type is treated as a special builtin type, it will be reported as a class type in reflection, for symmetry with self and parent .

class Test { public function method(): static {} } $rm = new ReflectionMethod(Test::class, 'method'); $rt = $rm->getReturnType(); var_dump($rt->isBuiltin()); // false var_dump($rt->getName()); // "static"

Backward Incompatible Changes

There are no backwards incompatible changes in this proposal.

Future Scope

For the fluent method example above, many projects will use a @return $this annotation, rather than @return static . We could in principle also support this syntax natively:

class Test { public function doWhatever(): $this { // Do whatever. return $this; } }

However, $this is not a real type, and it is unclear what the advantage of specifying $this rather than static would be from a type system level perspective.

Vote

Voting started 2020-01-28 and ends 2020-02-11.

Источник

Оцените статью