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 ...
Related
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 ...
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.
After trying several attempts at this for some reason i get the error Access to undeclared static property when i try to make an object out my class.
My class:
final class repo {
var $b;
/**
* #var \Guzzle\Http\Client
*/
protected $client;
function repo($myvar)
{
static::$b = $myvar;
$this->client = $b;
}
}
Me making an object:
$myobj = new repo("test");
You should declare $b as static variable.
Also note that method as a class name is deprecated now see the details here
final class repo {
public static $b;
/**
* #var \Guzzle\Http\Client
*/
protected $client;
function repo($myvar)
{
static::$b = $myvar;
$this->client = static::$b;
}
}
The declaration var $b; is PHP 4. PHP 5 allows it and it is equivalent to public $b;.
However, it is deprecated and if you use a proper error reporting (error_reporting(E_ALL); during development) you get a warning about it. You should use the PHP 5 visibility kewords instead.
Also, the declaration function repo($myvar) is a PHP 4 constructor style, also accepted but deprecated. You should use the PHP 5 __constructor() syntax.
You access $b as static::$b and this is not compatible with its declaration (equivalent, as I said above, with public $b). If you want it to be a class property (this is what static does) you have to declare it as a class property (i.e. public static $b).
Putting everything together, the proper way to write your class is:
final class repo {
// public static members are global variables; avoid making them public
/** #var \Guzzle\Http\Client */
private static $b;
// since the class is final, "protected" is the same as "private"
/** #var \Guzzle\Http\Client */
protected $client;
// PHP 5 constructor. public to allow the class to be instantiated.
// $myvar is probably a \Guzzle\Http\Client object
public __construct(\Guzzle\Http\Client $myvar)
{
static::$b = $myvar;
// $this->b probably works but static::$b is more clear
// because $b is a class property not an instance property
$this->client = static::$b;
}
}
Try this
final class repo {
public $b;
/**
* #var \Guzzle\Http\Client
*/
protected $client;
function repo($myvar)
{
$this->b = $myvar;
$this->client = $this->b;
}
}
Note: static::/self:: is used on static functions.
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 ...
How do I mark a method as "returns an instance of the current class" in my phpDoc?
In the following example my IDE (Netbeans) will see that setSomething always returns a foo object.
But that's not true if I extent the object - it'll return $this, which in the second example is a bar object not a foo object.
class foo {
protected $_value = null;
/**
* Set something
*
* #param string $value the value
* #return foo
*/
public function setSomething($value) {
$this->_value = $value;
return $this;
}
}
$foo = new foo();
$out = $foo->setSomething();
So fine - setSomething returns a foo - but in the following example, it returns a bar..:
class bar extends foo {
public function someOtherMethod(){}
}
$bar = new bar();
$out = $bar->setSomething();
$out->someOtherMethod(); // <-- Here, Netbeans will think $out
// is a foo, so doesn't see this other
// method in $out's code-completion
... it'd be great to solve this as for me, code completion is a massive speed-boost.
Anyone got a clever trick, or even better, a proper way to document this with phpDoc?
Update:
As of Netbeans 7.4, the IDE supports #return self, static, and this (http://wiki.netbeans.org/NewAndNoteworthyNB74#Editor_2).
class foo {
protected $_value = null;
/**
* Set something
*
* #param string $value the value
* #return this
*/
public function setSomething($value) {
$this->_value = $value;
return $this;
}
}
class bar extends foo {
public function someOtherMethod(){}
}
Previous Answer:
We have a similar issue with a record iterator's current() method. Since the iterator is extended for many different classes, it doesn't make sense to have a #return $class associated with it. We've used #satrun77's Option 2 before, but I've used #method with some success in Netbeans.
class foo {
protected $_value = null;
/**
* Set something
*
* #param string $value the value
* #return foo
*/
public function setSomething($value) {
$this->_value = $value;
return $this;
}
}
/**
* #method bar setSomething($value)
*/
class bar extends foo {
public function someOtherMethod(){}
}
Thought I'd revisit this Q as I came across a couple of things.
Currently "return $this" isn't supported, but there is a PhpDoc request to add exactly that in v1.5:
http://pear.php.net/bugs/bug.php?id=16223
There's also a request for it in Eclipse PDT:
https://bugs.eclipse.org/bugs/show_bug.cgi?id=276082
Both are relatively old requests. I'm not going to get too excited about this being implemented any time soon, but here goes to hoping :) In the meantime, it seems there is no proper solution to this problem.
!SOLVED! - upgrade to netbeans 9.0 (stable as of July 2018?)
I have been after this for over a year and finally have an open source solution! :)
class Input extends BasicHtml
{
public function someOnlyInputFunc()
{
}
}
class Table extends BasicHtml
{
public function tableOnlyFunc()
{
}
}
abstract class BasicHtml
{
/**
*
* #param array $arrayForNow
* #return $this
*/
public function setStyle( array $arrayForNow )
{
return $this;
}
}
/////driver
$table = new Table();
$input = new Input();
$input->setStyle(array())->//now shows only Input + baseHtml functions
$table->setStyle(array())-> //now shows only Table + baseHtml functions
///note - in 8.0.2 version it shows blank obj drop downs on exact same code.
This also works with traits. As of 11/1/2018 9.0 comes as a big zip (no clean installer for windows, mac?) and you will have to search for adding the php plugings etc BUT IT DOES WORK! Took me about an hour to get it all set. I also have my old 8.x installed and running along side the new 9.0 without issue...so far (just don't run them both at same time). Plugin tip: https://www.reddit.com/r/PHP/comments/9gtaaw/how_to_run_netbeans_9_with_php_support/
Here is 3 work around:
(These are just work around. classes must not be designed and implemented to sue the behavior of an IDE)
Option 1:
make the method someOtherMethod abstract or empty method in foo class
class foo implements ifoo {
protected $_value = null;
/**
* Set something
*
* #param string $value the value
* #return ifoo
*/
public function setSomething($value) {
$this->_value = $value;
return $this;
}
// abstract method or create empty method if you want the method to be
// to be optional
abstract function someOtherMethod();
}
Option 2:
Override the method setSomething in bar class
class bar extends foo {
/**
*
* #param <type> $value
* #return bar
*/
public function setSomething($value) {
return parent::setSomething($value);
}
public function someOtherMethod(){}
}
Option 3:
Use interface
interface ifoo {
public function someOtherMethod(){}
}
class foo {
protected $_value = null;
/**
* Set something
*
* #param string $value the value
* #return ifoo
*/
public function setSomething($value) {
$this->_value = $value;
return $this;
}
}
class bar extends foo implements ifoo {
public function someOtherMethod(){}
}
phpDoc syntax allows for multiple types to be defined by separating them with a | character for the #return tag. When you extend the class foo with class bar you should write a new phpDoc tag that has the proper class for its #return.
If a function returns either foo or bar then you would use #return foo|bar.
However in your case just define #return bar for the overridden function.
Take care.