Phpdoc for variable "cast"? - php

base classes:
abstract class A
{
public function methodA() { echo __FUNCTION__.'<br>'; }
}
class B extends A
{
public function methodB() { echo __FUNCTION__.'<br>'; }
}
so far so good.
A factory which can create any of them:
class Factory
{
/**
* #return A
*/
public static function createAorB()
{
return new B();
}
}
phpdoc is right, since it say "return something comes from A".
Now the tricky part comes:
class OwnClass
{
/**
* #var A
*/
protected $var;
public function __construct()
{
$this->var = \Factory::createAorB();
}
}
class OwnClassB extends OwnClass
{
public function callMe()
{
$this->var->methodA();
$this->var->methodB(); // this will also run, but IDE wont autocomplete with this method
}
}
(new OwnClassB())->callMe();
this will run for sure, but check for the methodB() calling. IDE wont autocomplete with this method. How to tell that var is now B, not A with phpDoc?

You can override inherited properties for PHPStorm's autocompletion:
/**
* #property B $var
*/
class OwnClassB extends OwnClass
{
//...
}
However, I personally think you're trying to solve the wrong problem. If you have a Factory class that returns either A or B, you shouldn't type hint the return value to A. Instead, you should provide both:
class Factory
{
/**
* #return A|B
*/
public static function createAorB()
{
return new B();
}
}
Both options inform PHPStorm enough to let it show you both methods as autocomplete options, but the latter is accurate in terms of what the code actually does (well, not in this example, but given the function name I guess that's what your real-world implementation does).

Related

How can I get the method of an interface and its return type and param type from an external class?

I want to get int from an external class.
interface FooInterface
{
/**
* #return int
*/
public function getId(): int;
/**
* #param int $id
*/
public function setId(int $id);
}
I would like to know the return type of the getters to dynamically format the value.
I would like to know the parameter type of the setters to dynamically initialize it.
I need to do it this way because the properties are not initialized, because they are of an object type and their real value is in the interfaces.
class Foo extends FooAbstract implements FooInterface
{
use BuilderFoo;// here are the FooInterface methods
protected function getParamTypeValue(string $functionName)
{
$reflectionParams = (new ReflectionFunction($functionName))->getParameters();//local.ERROR: Function setId() does not exist
return $reflectionParams[0]->getType();
}
protected function getReturnTypeValue(string $functionName)
{
return (new ReflectionFunction($functionName))->getReturnType();//local.ERROR: Function getId() does not exist
}
}
But I get the error: "local.ERROR: Function getId() does not exist" and
"local.ERROR: Function setId() does not exist".
The only way to get the return type of a declared method inside an interface is checking that value after implementation of the Interface.
Interfaces are not instatiables are contracts that other classes has to satisfy.
so here is an example.
interface I{
public function test():int;
}
class TestClass implements I {
public function test():int{
return 5;
}
}
$obj= new TestClass;
gettype($obj->test()) //"integer"
I hope this answer your question.

Type-casting problem while building fluid interface

For example we have the following abstract class
<?php
class AbstractClass {
public function setParam(): AbstractClass {}
}
class ConcreteClass extends AbstractClass {
public function test():void {}
}
When you'll try to use it like this
<?php
(new ConcreteClass())->setParam()->test();
Then after setParam we will see only setParam method, because setParam returns AbstractClass. I tried to mark setParam inside AbsractClass with PHP-doc #return self, but it doesn't work.
Are there any solutions of this problem?
To solve this problem you can use #return static PHP-doc attribute
<?php
class A {
/** #return static */
public function methodA(): A;
}
class B {
/** #return static */
public function methodB(): B;
}
(new B())->methodB()->methodA()->methodB();
Everything in this example will be highlighted correctly.

PHPDoc different return type for extended classes

I have created my own DB - Model structure which is similar to Laravel. I have been facing with 2 problems.
I have a Model class which all of my models extend it. For example, my User class extends Model. I want to return that get() method return type of class which is extended.
Is this possible?
Class Model extends DB {
/**
* #return AnyClassThatExtended
*/
function get()
{
}
}
Class User extends Model {
function test() {
$user->get(); // I want it to return User type of object
}
}
You should use
private static $instance;
/**
* return static
*/
public function get() {
if (is_null(self::$instance)) {
self::$instance = new static();
}
return self::$instance;
}
because you are returning current class that you are at (if I understand correctly)
It's possible that PHPStorm does not recognize it

PhpStorm Trait method autocompletion does not work

I have a problem with an autocompletion in PhpStorm...
class Main
{
use Something;
/**
* #var SplObjectStorage
*/
private $container;
public function __construct()
{
$this->container = new SplObjectStorage();
}
public function addSth()
{
$this->add();
}
}
trait Something
{
public function add()
{
$this->container->attach(new stdClass());
}
}
$m = new Main();
$m->add();
var_dump($m);
Everything works fine but $this->container->attach(new stdClass()); throws that method attach is not found... Anyone can help? I think that properly configured PHPDoc should help.
The Trait has no way of knowing what type its $container is. In your example it is SplObjectStorage, but what if it isn't?
You need to place $container inside the Trait as well and declare it as SplObjectStorage. Then it should work. This way, you'll also be sure that whoever is declaring that Trait actually has a $container to have it work on.
trait Something {
/**
* #var SplObjectStorage
*/
private $container;
...
I suppose you can force the issue:
public function add()
{
/**
* #var $cont SplObjectStorage
*/
$cont = $this->container;
$cont->attach(new stdClass());
}
There's a couple of other ways to make this work.
Define $container inside the trait (as #Iserni suggested), but define the variable itself. This actually makes more sense to define it within the trait anyways, since the trait methods actually rely on it.
trait Something {
/** #var \SplObjectStorage */
protected $container;
public function add() {
$this->container->attach(new stdClass());
}
}
Pass it as an argument in your function
public function add(\SplObjectStorage $container) {
$container->attach(new stdClass());
}
PHPStorm has to have a way to refer back to the class to do things like autocomplete. Your trait cannot inherit its docs from the calling class. Your class, however, can inherit docs from the included trait.

How to unit test PHP traits

I want to know if there is a solution on how to unit-test a PHP trait.
I know we can test a class which is using the trait, but I was wondering if there are better approaches.
Thanks for any advice in advance :)
EDIT
One alternative is to use the Trait in the test class itself as I'm going to demonstrate bellow.
But I'm not that keen on this approach since there is no guaranty there are no similar method names between the trait, the class and also the PHPUnit_Framework_TestCase (in this example):
Here is an example trait:
trait IndexableTrait
{
/** #var int */
private $index;
/**
* #param $index
* #return $this
* #throw \InvalidArgumentException
*/
public function setIndex($index)
{
if (false === filter_var($index, FILTER_VALIDATE_INT)) {
throw new \InvalidArgumentException('$index must be integer.');
}
$this->index = $index;
return $this;
}
/**
* #return int|null
*/
public function getIndex()
{
return $this->index;
}
}
and its test:
class TheAboveTraitTest extends \PHPUnit_Framework_TestCase
{
use TheAboveTrait;
public function test_indexSetterAndGetter()
{
$this->setIndex(123);
$this->assertEquals(123, $this->getIndex());
}
public function test_indexIntValidation()
{
$this->setExpectedException(\Exception::class, '$index must be integer.');
$this->setIndex('bad index');
}
}
You can test a Trait using a similar to testing an Abstract Class' concrete methods.
PHPUnit has a method getMockForTrait which will return an object that uses the trait. Then you can test the traits functions.
Here is the example from the documentation:
<?php
trait AbstractTrait
{
public function concreteMethod()
{
return $this->abstractMethod();
}
public abstract function abstractMethod();
}
class TraitClassTest extends PHPUnit_Framework_TestCase
{
public function testConcreteMethod()
{
$mock = $this->getMockForTrait('AbstractTrait');
$mock->expects($this->any())
->method('abstractMethod')
->will($this->returnValue(TRUE));
$this->assertTrue($mock->concreteMethod());
}
}
?>
You can also use getObjectForTrait , then assert the actual result if you want.
class YourTraitTest extends TestCase
{
public function testGetQueueConfigFactoryWillCreateConfig()
{
$obj = $this->getObjectForTrait(YourTrait::class);
$config = $obj->getQueueConfigFactory();
$this->assertInstanceOf(QueueConfigFactory::class, $config);
}
public function testGetQueueServiceWithoutInstanceWillCreateConfig()
{
$obj = $this->getObjectForTrait(YourTrait::class);
$service = $obj->getQueueService();
$this->assertInstanceOf(QueueService::class, $service);
}
}
Since PHP 7 we can now use annonymous classes...
$class = new class {
use TheTraitToTest;
};
// We now have everything available to test using $class

Categories