Type Hinting abstract class singleton - php

How would one Type Hint a static singleton-returning method on an abstract class, which returns instances of the extending called classes?
For example, let's look at the following code:
<?php
abstract class Foo {
/** #return Foo */
public function init() {
static $instance;
if ( is_null($instance) ) {
$class = get_called_class();
$instance = new $class();
}
return $instance;
}
}
class Bar extends Foo {
public $name = "Bar name";
}
class Baz extends Foo {
public $age = 42;
}
My intention is for tools such as PhpStorm to understand that Bar::init() returns an object of type Bar and that Baz::init() returns an object of type Baz. Thus, for example, objects created from the Baz::init() method would auto-complete the name property but not the age property.
Obviously the current type hint #return Foo is wrong as the method will never return an object instance of an abstract class.

So #return static will work in this case for PHPStorm. It is the easiest option and will provide what you are looking for.
Optionally you can use the #method annotation against the class although this is very manual and needs to be done for each class. There is another strange thing with this method in PHPStorm where if you navigate to the init() method (ctrl+click or w/e) it will navigate to this annotation first. However this is how that looks:
/**
* #method Bar init()
*/
class Baz extends Foo
{
}
Where optionally as a final resort--and I really don't think you will need it but its here for completeness. Extend the method and add your return annotation as you would a normal method.
/**
* #return Baz
*/
public function init()
{
return parent::init();
}

You could try this:
abstract class Foo {
/** #return static */
public function init() {
static $instance;
if ( is_null($instance) ) {
$class = get_called_class();
$instance = new $class();
}
return $instance;
}
}
This will probably require PhpStorm to be set up with PHP 5.3+ type hints to work.

Related

How to assign types in php without causing parse errors? [duplicate]

Does php 7 support type hinting for class properties?
I mean, not just for setters/getters but for the property itself.
Something like:
class Foo {
/**
*
* #var Bar
*/
public $bar : Bar;
}
$fooInstance = new Foo();
$fooInstance->bar = new NotBar(); //Error
PHP 7.4 will support typed properties like so:
class Person
{
public string $name;
public DateTimeImmutable $dateOfBirth;
}
PHP 7.3 and earlier do not support this, but there are some alternatives.
You can make a private property which is accessible only through getters and setters which have type declarations:
class Person
{
private $name;
public function getName(): string {
return $this->name;
}
public function setName(string $newName) {
$this->name = $newName;
}
}
You can also make a public property and use a docblock to provide type information to people reading the code and using an IDE, but this provides no runtime type-checking:
class Person
{
/**
* #var string
*/
public $name;
}
And indeed, you can combine getters and setters and a docblock.
If you're more adventurous, you could make a fake property with the __get, __set, __isset and __unset magic methods, and check the types yourself. I'm not sure if I'd recommend it, though.
7.4+:
Good news that it will be implemented in the new releases, as #Andrea pointed out.
I will just leave this solution here in case someone wants to use it prior to 7.4
7.3 or less
Based on the notifications I still receive from this thread, I believe that many people out there had/is having the same issue that I had. My solution for this case was combining setters + __set magic method inside a trait in order to simulate this behaviour.
Here it is:
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;
}
}
}
And here is the demonstration:
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
Explanation
First of all, define bar as a private property so PHP will cast __set automagically.
__set will check if there is some setter declared in the current object (method_exists($this, $setter)). Otherwise it will only set its value as it normally would.
Declare a setter method (setBar) that receives a type-hinted argument (setBar(Bar $bar)).
As long as PHP detects that something that is not Bar instance is being passed to the setter, it will automaticaly trigger a Fatal Error: Uncaught TypeError: Argument 1 passed to Foo::setBar() must be an instance of Bar, instance of NotBar given
Edit for PHP 7.4 :
Since PHP 7.4 you can type attributes (Documentation / Wiki) which means you can do :
class Foo
{
protected ?Bar $bar;
public int $id;
...
}
According to wiki, all acceptable values are :
bool, int, float, string, array, object
iterable
self, parent
any class or interface name
?type // where "type" may be any of the above
PHP < 7.4
It is actually not possible and you only have 4 ways to actually simulate it :
Default values
Decorators in comment blocks
Default values in constructor
Getters and setters
I combined all of them here
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;
}
}
Note that you actually can type the return as ?Bar since php 7.1 (nullable) because it could be null (not available in php7.0.)
You also can type the return as void since php7.1
You can use 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);
Output:
TypeError: Argument 1 passed to Foo::setBar() must be an instance of Bar, instance of Foo given, called in ...

PHP PSR-12 is this a syntax error in example? [duplicate]

Does php 7 support type hinting for class properties?
I mean, not just for setters/getters but for the property itself.
Something like:
class Foo {
/**
*
* #var Bar
*/
public $bar : Bar;
}
$fooInstance = new Foo();
$fooInstance->bar = new NotBar(); //Error
PHP 7.4 will support typed properties like so:
class Person
{
public string $name;
public DateTimeImmutable $dateOfBirth;
}
PHP 7.3 and earlier do not support this, but there are some alternatives.
You can make a private property which is accessible only through getters and setters which have type declarations:
class Person
{
private $name;
public function getName(): string {
return $this->name;
}
public function setName(string $newName) {
$this->name = $newName;
}
}
You can also make a public property and use a docblock to provide type information to people reading the code and using an IDE, but this provides no runtime type-checking:
class Person
{
/**
* #var string
*/
public $name;
}
And indeed, you can combine getters and setters and a docblock.
If you're more adventurous, you could make a fake property with the __get, __set, __isset and __unset magic methods, and check the types yourself. I'm not sure if I'd recommend it, though.
7.4+:
Good news that it will be implemented in the new releases, as #Andrea pointed out.
I will just leave this solution here in case someone wants to use it prior to 7.4
7.3 or less
Based on the notifications I still receive from this thread, I believe that many people out there had/is having the same issue that I had. My solution for this case was combining setters + __set magic method inside a trait in order to simulate this behaviour.
Here it is:
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;
}
}
}
And here is the demonstration:
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
Explanation
First of all, define bar as a private property so PHP will cast __set automagically.
__set will check if there is some setter declared in the current object (method_exists($this, $setter)). Otherwise it will only set its value as it normally would.
Declare a setter method (setBar) that receives a type-hinted argument (setBar(Bar $bar)).
As long as PHP detects that something that is not Bar instance is being passed to the setter, it will automaticaly trigger a Fatal Error: Uncaught TypeError: Argument 1 passed to Foo::setBar() must be an instance of Bar, instance of NotBar given
Edit for PHP 7.4 :
Since PHP 7.4 you can type attributes (Documentation / Wiki) which means you can do :
class Foo
{
protected ?Bar $bar;
public int $id;
...
}
According to wiki, all acceptable values are :
bool, int, float, string, array, object
iterable
self, parent
any class or interface name
?type // where "type" may be any of the above
PHP < 7.4
It is actually not possible and you only have 4 ways to actually simulate it :
Default values
Decorators in comment blocks
Default values in constructor
Getters and setters
I combined all of them here
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;
}
}
Note that you actually can type the return as ?Bar since php 7.1 (nullable) because it could be null (not available in php7.0.)
You also can type the return as void since php7.1
You can use 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);
Output:
TypeError: Argument 1 passed to Foo::setBar() must be an instance of Bar, instance of Foo given, called in ...

PHPDoc: document parent class method that always yields itself or descendant class?

Consider this code:
class ParentClass {
public static function generate($className = __CLASS__){
if(!$className) return new self();
else return new $className();
}
}
class ChildClass extends ParentClass {
/**
* #param string $className
* #return ChildClass
*/
public static function generate($className = __CLASS__) {
return parent::generate($className);
}
}
var_dump($ChildClass::generate()); // object(ChildClass)[1]
ChildClass::generate() returns an instance of ChildClass wherever I use it because I never provide a $className argument. Problem is that my IDE gives me a warning about the parent::generate() call not matching the documented return type:
I would like to make this warning go away by adding documentation to the parent method. I could do:
#return ParentClass | ChildClass
Adding this to the parent method works but that's not practical because there are many dozen children classes, and there could be many more in the future. I have tried both of the following:
#return static
#return $className
but that hasn't made the warning go away. Is there a PHPDoc approved way to indicate that the calling child class will always be returned? Or -- more accurately -- that a class of type $className will be returned? If not, is there a way that works even with just my IDE? (PhpStorm 2017.2)
Update
#gogaz comment below got me to think: it would be enough if a PHPDoc #return could indicate something like self | descendants
You can document parent class method and then just inherit that on a child class method.
class ParentClass
{
/**
* #param string $className
* #return ChildClass
*/
public static function generate($className = __CLASS__){
if(!$className) return new self();
else return new $className();
}
}
class ChildClass extends ParentClass
{
/**
* #inheritdoc
*/
public static function generate($className = __CLASS__) {
return parent::generate($className);
}
}
I hope that this will solve your problem.
UPDATE:
Another way to do this is by using interface that all classes will implement, so you could expect that your generate method will return some class that implements that interface.
/**
* ...
* #return MyCustomInterface
*/
If this doesn't solve your issue... Then, you can set return type as "mixed", this will suppress warning and it won't lie :) because your generate method can return any class you specify via argument...
/**
* ...
* #return mixed
*/
I've run across something similar (PHPStorm is more anal than PHP is on the subject). Unless there's some reason you HAVE to have it be specific to the child class, I would do this
class ParentClass {
/**
* #param string $className
* #return ParentClass
*/
public static function generate($className = __CLASS__){
if(!$className) return new self();
else return new $className();
}
}
Remember, your ChildClass is still an instance of ParentClass

PHPDoc for "casted" return type

I have some kind of factory where a method returns one of multiple possible classes but they all inherit the same parent class. The factory method returns always the same class when it gets the same parameter. So when I receive a class from the factory method I know the subclass beside the common parent class.
The problem is that PhpStorm shows me a warning when I try to set the #return type to the child class.
Here an example:
abstract class Base {}
class A extends Base {}
class B extends Base {}
class T {
/**
* #param string $class
* #return Base
*/
public function returnBase($class)
{
switch ($class) {
case 'A':
return new A();
break;
// ... more cases ...
default:
return new B();
}
}
/**
* #return A
*/
public function test()
{
return $this->returnBase('A'); // Warning: "Return value is expected to be 'A', 'Base' returned"
}
}
I know in this example I could set the return type of returnBase() to A|B, but in my actual code I have much more classes. I don't want to set the return type of the test() method to "Base" because the subclass might have unique methods/properties.
You can set a varibale type for each returnBase() method like this :
/**
* #return A
*/
public function test()
{
/** #var A $a */
$a = $this->returnBase('A');
return $a;
}
Accroding to this article about PHP’s Garbage Collection, a redundant variable doesn't have any impact on memory consumtion.

Type hinting for properties in PHP 7?

Does php 7 support type hinting for class properties?
I mean, not just for setters/getters but for the property itself.
Something like:
class Foo {
/**
*
* #var Bar
*/
public $bar : Bar;
}
$fooInstance = new Foo();
$fooInstance->bar = new NotBar(); //Error
PHP 7.4 will support typed properties like so:
class Person
{
public string $name;
public DateTimeImmutable $dateOfBirth;
}
PHP 7.3 and earlier do not support this, but there are some alternatives.
You can make a private property which is accessible only through getters and setters which have type declarations:
class Person
{
private $name;
public function getName(): string {
return $this->name;
}
public function setName(string $newName) {
$this->name = $newName;
}
}
You can also make a public property and use a docblock to provide type information to people reading the code and using an IDE, but this provides no runtime type-checking:
class Person
{
/**
* #var string
*/
public $name;
}
And indeed, you can combine getters and setters and a docblock.
If you're more adventurous, you could make a fake property with the __get, __set, __isset and __unset magic methods, and check the types yourself. I'm not sure if I'd recommend it, though.
7.4+:
Good news that it will be implemented in the new releases, as #Andrea pointed out.
I will just leave this solution here in case someone wants to use it prior to 7.4
7.3 or less
Based on the notifications I still receive from this thread, I believe that many people out there had/is having the same issue that I had. My solution for this case was combining setters + __set magic method inside a trait in order to simulate this behaviour.
Here it is:
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;
}
}
}
And here is the demonstration:
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
Explanation
First of all, define bar as a private property so PHP will cast __set automagically.
__set will check if there is some setter declared in the current object (method_exists($this, $setter)). Otherwise it will only set its value as it normally would.
Declare a setter method (setBar) that receives a type-hinted argument (setBar(Bar $bar)).
As long as PHP detects that something that is not Bar instance is being passed to the setter, it will automaticaly trigger a Fatal Error: Uncaught TypeError: Argument 1 passed to Foo::setBar() must be an instance of Bar, instance of NotBar given
Edit for PHP 7.4 :
Since PHP 7.4 you can type attributes (Documentation / Wiki) which means you can do :
class Foo
{
protected ?Bar $bar;
public int $id;
...
}
According to wiki, all acceptable values are :
bool, int, float, string, array, object
iterable
self, parent
any class or interface name
?type // where "type" may be any of the above
PHP < 7.4
It is actually not possible and you only have 4 ways to actually simulate it :
Default values
Decorators in comment blocks
Default values in constructor
Getters and setters
I combined all of them here
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;
}
}
Note that you actually can type the return as ?Bar since php 7.1 (nullable) because it could be null (not available in php7.0.)
You also can type the return as void since php7.1
You can use 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);
Output:
TypeError: Argument 1 passed to Foo::setBar() must be an instance of Bar, instance of Foo given, called in ...

Categories