- PHPDocs Basics
- Methods and functions #
- Properties #
- Inline @var #
- Magic properties #
- Magic methods #
- Mixins #
- Combining PHPDoc types with native typehints #
- Variadic functions #
- Generics #
- Narrowing types after function call #
- Setting parameter type passed by reference #
- Change type of current object after calling a method #
- Deprecations #
- Impure functions #
- Prefixed tags #
- Classes named after internal PHP types #
- Readonly properties #
- Immutable classes #
- Request for Comments: mixin
- Use Cases
- Use Case #1 — From the traits example
- Use Case #2 — Commonly used events
- Use Case #3 — Real world billing information (i.e. telecom company)
- Use Case #4 – Development, allow dynamic mixins
- PHP.ini
- Proposal and Patch
- Notes
PHPDocs Basics
PHPDocs are a big part of what makes PHPStan work. PHP in its most recent versions can express a lot of things in the native typehints, but it still leaves a lot of room for PHPDocs to augment the information.
Valid PHPDocs start with /** . Variants starting only with /* or line comments // are not considered PHPDocs. [1]
Learn more about PHPDoc types » you can use in the tags described below.
Would you like to fix incorrect PHPDocs in 3 rd party code you have in /vendor? You can! Check out Stub Files »
Methods and functions #
This is how a valid PHPDoc above a function or a method can look like:
/** * @param Foo $param * @return Bar */ function foo($param) . >
Properties #
PHPDocs can be written above class properties to denote their type:
Inline @var #
Casting a type using an inline @var PHPDocs should be used only as a last resort. It can be used in a variable assignment like this:
/** @var Foo $foo */ $foo = createFoo();
This is usually done if the symbol on the right side of the = operator has a wrong type, or isn’t specific enough. Usage of inline @var is problematic for a couple of reasons:
- Because it might be used to correct wrong type information of the called symbol, PHPStan always trusts it. But if there’s a mistake in this annotation, the analysis of the code below might be wrong.
- The inline @var needs to be repeated above all usages of the symbol which leads to repetition in the codebase.
Instead, the type should be fixed at its source. If the called symbol comes from 3rd party code, you can correct it using a stub file. If the return type differs in each call based on the passed arguments, you can use generics, or write a dynamic return type extension instead.
If you really need to use an inline @var , consider an alternative — an assert() call, which can throw an exception at runtime, so the code execution doesn’t continue if the requirements aren’t met.
$foo = createFoo(); assert($foo instanceof Foo);
Magic properties #
For custom __get / __set methods logic, a @property PHPDoc tag can be placed above a class. If the property is only supposed to be read or written to, @property-read / @property-write variants can be used.
/** * @property int $foo * @property-read string $bar * @property-write \stdClass $baz */ class Foo . >
The @property tag can also be used to override wrong property type from a parent class.
Magic methods #
For custom __call methods logic, a @method PHPDoc tag can be placed above a class:
/** * @method int computeSum(int $a, int $b) * @method void doSomething() * @method static int staticMethod() * @method int doMagic(int $a, int $b = 123) */ class Foo . >
Mixins #
When a class delegates unknown method calls and property accesses to a different class using __call and __get / __set , we can describe the relationship using @mixin PHPDoc tag:
class A public function doA(): void > > /** * @mixin A */ class B public function doB(): void > public function __call($name, $arguments) (new A())->$name(. $arguments); > > $b = new B(); $b->doB(); $b->doA(); // works
/** * @template T * @mixin T */ class Delegatee /** @var T */ private $delegate; /** * @param T $delegate */ public function __construct($delegate) $this->delegate = $delegate; > public function __call($name, $arguments) return $this->delegate->$name(. $arguments); > > $d = new Delegatee(new \Exception('My message')); echo $d->getMessage(); // PHPStan knows the method is on Exception
Combining PHPDoc types with native typehints #
PHPDocs can also complement native typehints with additional information. The most common use-case is telling PHPStan what’s in an array:
/** * @param User[] $users */ function foo(array $users) . >
You can also use an alternative array syntax, or even specify the key type:
/** * @param array $users */ function foo(array $users) . >
Using @return static along with the self native typehint means that the method returns a child class (see example):
/** * @return static */ public function returnStatic(): self return $this; >
A narrower @return $this instead of @return static can also be used, and PHPStan will check if you’re really returning the same object instance and not just the child class.
Variadic functions #
This allows specifying functions or methods which have a variable amount of parameters (available since PHP 5.6).
Your code can look like this:
/** * @param string $arg * @param string . $additional */ function foo($arg, . $additional) >
Generics #
PHPDoc tags @template , @template-covariant , @extends , and @implements are reserved for generics. Learn more about generics », also check out Generics By Examples ».
Narrowing types after function call #
PHPDoc tags @phpstan-assert , @phpstan-assert-if-true , @phpstan-assert-if-false are used to inform PHPStan about type-narrowing happening inside called functions and methods. Learn more »
Setting parameter type passed by reference #
PHPDoc tag @param-out can be used to set a parameter type passed by reference:
/** * @param-out int $i */ function foo(mixed &$i): void $i = 5; > foo($a); \PHPStan\dumpType($a); // int
Change type of current object after calling a method #
PHPDoc tags @phpstan-self-out or @phpstan-this-out can be used to change the type of the current object after calling a method on it. This is useful for generic mutable objects.
/** * @template TValue */ class Collection // . /** * @template TItemValue * @param TItemValue $item * @phpstan-self-out self */ public function add($item): void // . > > /** @param Collection $c */ function foo(Collection $c, string $s): void $c->add($s); \PHPStan\dumpType($c); // Collection >
Deprecations #
Use @deprecated tag to mark declarations as deprecated:
/** @deprecated Optional description */ class Foo >
Install phpstan-deprecation-rules extension to have usages of deprecated symbols reported.
The @deprecated PHPDoc tag is inherited to implicitly mark overridden methods in child classes also as deprecated:
class Foo /** @deprecated */ public function doFoo(): void // . > > class Bar extends Foo public function doFoo(): void // . > > $bar = new Bar(); $bar->doFoo(); // Call to deprecated method doFoo() of class Bar.
To break the inheritance chain and un-mark Bar::doFoo() as deprecated, use @not-deprecated PHPDoc tag:
class Bar extends Foo /** @not-deprecated */ public function doFoo(): void // . > > $bar = new Bar(); $bar->doFoo(); // OK
Impure functions #
By default, PHPStan considers all functions that return a value to be pure. That means that a second call to the same function in the same scope will return the same narrowed type. If you have a function that may return different values on successive calls based on a global state like a random number generator, database, or time, then the function is impure. You can tell PHPStan about impure functions and methods with the @phpstan-impure tag:
/** @phpstan-impure */ function impureFunction(): bool return rand(0, 1) === 0 ? true : false; >
The @phpstan-pure tag is also available should you need it, for example if you’ve set rememberPossiblyImpureFunctionValues: false in your configuration file (available in PHPStan 1.8.0). See Config Reference for more details.
Prefixed tags #
Supported tags ( @var , @param , @return , and all generics-related ones) can be prefixed with @phpstan- :
/** * @phpstan-param Foo $param * @phpstan-return Bar */ function foo ($param) . >
This is useful in the context of advanced types and generics. IDEs and other PHP tools might not understand the advanced types that PHPStan takes advantage of. So you can leave the ordinary @param in the PHPDoc and add a @phpstan-param with an advanced type syntax.
Classes named after internal PHP types #
When having classes named like Resource , Double , Number (or Mixed until PHP 8), there is no possible way to distinguish between either the PHP internal type or the custom class to use in the PHPDoc. By default, PHPStan will consider the type as being the PHP internal type, which means some false-positives can appear.
/** * @param Resource $var */ public function foo(Resource $var): void . >
To make PHPStan understand the passed argument must be an instance of a Resource object, use a fully-qualified name in the PHPDoc. PHPStan will understand that as the object of My\Resource class.
/** * @param \My\Resource $var */ public function foo(Resource $var): void . >
Readonly properties #
PHPStan supports native PHP 8.1 readonly properties and validates their correct usage. For older PHP versions, PHPStan also understands the @readonly PHPDoc tag to apply similar rules.
class Foo /** @readonly */ public string $bar; > (new Foo())->bar = 'baz'; // @readonly property Foo::$bar is assigned outside of its declaring class.
This feature needs to be enabled via feature toggle by opting in to bleeding edge.
Immutable classes #
@immutable on the class can be used to make PHPStan treat every property of that class as being readonly.
/** @immutable */ class Foo public string $bar; > (new Foo())->bar = 'baz'; // @readonly property Foo::$bar is assigned outside of its declaring class.
This feature needs to be enabled via feature toggle by opting in to bleeding edge.
- Only the /** style comments are supported because they’re represented with different tokens ( T_DOC_COMMENT ) by the PHP parser and only this token type is supposed to represent a PHPDoc. ↩︎
Request for Comments: mixin
This is first draft of adding the ‘mixin’ keyword into the PHP language. It based around the ‘trait’ proposal where the primary advantage is to avoid code duplication and re-factoring of code.
For a good summary and detailed research on the advantages, see:
This proposal only introduces 1 keyword ‘mixin’ and the function class_mixin().
Hopefully, the use cases will speak for themselves.
Use Cases
The use cases should represent how the mixin feature is expected to be used, highlight both best-practices and “ugly hacks” or problems that could result from the syntax.
Use Case #1 — From the traits example
class ezcReflectionReturnInfo { function getReturnType() { /*1*/ } function getReturnDescription() { /*2*/ } } class ezcReflectionMethod extends ReflectionMethod mixin ezcReflectionReturnInfo { /* . */ } class ezcReflectionFunction extends ReflectionFunction mixin ezcReflectionReturnInfo { /* . */ }
Use Case #2 — Commonly used events
class eventDelete { public $isDeleted; function onDelete() { $this->isDeleted = true; // Log this. other work here? } } class eventSave { function save() { echo "save"; $this->onSave(); } function onSave() {} } class shoppingCart mixin eventSave, eventDelete { } $s = new shoppingCart; $s->save(); // calls eventSave->save(); class shoppingCart2 mixin eventSave, eventDelete { function save() { echo "called"; // mixin::save(); would call eventSave::save() } } $s = new shoppingCart2; $s->save(); // calls shoppingCart2::save(); "called"
Use Case #3 — Real world billing information (i.e. telecom company)
class SOME _Billing_Information { function getAccount() { // implement. } function getAddressBilling() { echo "mixin address"; } function setAddressBilling($address) { // implement. } } class Customer_Telecom extends Person mixin SOME_Billing_Information { } class Company_Telecom extends Company mixin SOME_Billing_Information { function getAddressBilling() { echo "I stay the same"; } } class Company_Rogers extends Company_Telecom { function getAddressBilling() { echo parent::getAddressBilling(); // "I stay the same" echo mixin::getAddressBilling(); // "mixin address" } function getAccount() { echo "different"; } }
The mixin clobbers any methods or properties into the given object.
It does not replace any exiting methods or properties defined in the class, primarily for language security. Any method or property that already exists in the class or parent/extended class cannot be changed unless using class_mixin() see below.
Use Case #4 – Development, allow dynamic mixins
// dynamic mixin: class_mixin("shoppingCart", array("eventsA", "eventsB")); // Same as class_mixin("shoppingCart", "eventsA"); class_mixin("shoppingCart", "eventsB"); // Not equivalent (order matters) class_mixin("shoppingCart", "eventsB"); class_mixin("shoppingCart", "eventsA"); // Taken from this post: // [[http://usrportage.de/archives/828-A-naive-approach-to-mixins-in-PHP.html]] class_mixin("PHPUnit_Framework_Assert", "MY_Assert_Methods");
PHP.ini
The default php.ini would contain ~:
mixin.security = true # default and CANNOT be changed using ini_set()
class_mixin('PHPUnit_Framework_Assert', 'MY_Assert_Methods'); // throws an E_ERROR
Dynamic mixins would be supported by editing php.ini:
class_mixin('PHPUnit_Framework_Assert', 'MY_Assert_Methods'); // OK $a = new PHPUnit_Framework_Assert; $a->myAssertType('okk', $obj);
Proposal and Patch
Learning how to hack PHP. Contributions welcome.
Notes
There was an interesting paper in the ACM (2006) about “intuitive OO design”.
The goal here is to keep OO intuitive and allow for multiple inheritance “PHP style”.