PHPStan inheritance & property types - php

I have trouble passing PHPStan tests because of parent/child classes (besides, code runs well).
These two kind of errors always show up:
Parameter #1 $a of method xxx expects ConcreteChildClass, AbstractParentClass given
Property ClassName::$a (ConcreteChildClass) does not accept AbstractParentClass
Here is the code (must be compatible with PHP 5.6):
<?php
abstract class AbstractClassA
{
/**
* #return static
*/
abstract public function postProcess();
}
class ConcreteClassA extends AbstractClassA
{
/**
* #return ConcreteClassA
*/
public function postProcess()
{
return $this;
}
}
abstract class AbstractClassB
{
/**
* #return AbstractClassA
*/
abstract public function deserialize();
/**
* #return AbstractClassA
*/
public function someMethodA($params)
{
return $this->someMethodB();
}
/**
* #return AbstractClassA
*/
public function someMethodB()
{
$object = $this->deserialize();
return $object->postProcess();
}
}
class ConcreteClassB extends AbstractClassB
{
/**
* #return ConcreteClassA
*/
public function deserialize()
{
$object = new ConcreteClassA();
// some assignements
return $object;
}
}
And how this code is used:
class SomeClass
{
/** #var ConcreteClassA $propertyA */
public $propertyA;
/** #var ConcreteClassB $propertyB */
public $propertyB;
public function someMethod()
{
$this->propertyA = $this->propertyB->someMethodA($params);
$this->someProperty->someOtherMethod($this->propertyA);
// again, someOtherMethod expects ConcreteClassA but AbstractClassA given...
}
I tried to "play" with #template without success...
Please also note that I have limited control over abstract classes.
Thanks,

Related

Target [Illuminate\\Database\\Eloquent\\Model] is not instantiable while building

gets an error when everything is implemented correctly
"message": "Target [Illuminate\\Database\\Eloquent\\Model] is not instantiable while building [App\\Http\\Services\\TournamentService, App\\Http\\Repository\\Tournament\\TournamentRepository].",
MY CODE:
CONTROLLER:
public function getTournamentUserByType(TournamentService $tournamentService)
{
$tounaments = $tournamentService->getAll();
}
SERVICE:
class TournamentService implements TournamentServiceInterface
{
private TournamentRepositoryInterface $tournamentRepository;
public function __construct(TournamentRepositoryInterface $tournamentRepository)
{
$this->tournamentRepository = $tournamentRepository;
}
/**
* #return Collection
*/
public function getAll(): Collection
{
return $this->tournamentRepository->all();
}
REPOSITORY INTERFACE:
interface TournamentRepositoryInterface
{
/**
* #return Collection
*/
public function all(): Collection;
MY REPOSITORY:
class TournamentRepository extends BaseRepository implements TournamentRepositoryInterface
{
/**
* #param Model $model
*/
public function __construct(Model $model)
{
parent::__construct($model);
}
/**
* #return Collection
*/
public function all(): Collection
{
return $this->model->all();
}
Everything looks correct, I completely don't know why it gives me this error

How to unittest a method that is called in the constructor of the tested class with phpunit

I have a class Version and I want to unittest the isValidVersionString() method.
use InvalidArgumentException;
class Version
{
/**
* dot separated representation of the version
* #var string
*/
protected $versionString;
public function __construct($value)
{
$this->setVersionString($value);
}
/**
* #return string
*/
public function getVersionString(): string
{
return $this->versionString;
}
/**
* #param string $versionString
*
* #return Version
*/
public function setVersionString(string $versionString)
{
if(!$this->isValidVersionString($versionString)) {
throw new InvalidArgumentException();
}
$this->versionString = $versionString;
return $this;
}
/**
* valid format is xxx.xxx.xxx.xxx, with xxx not larger than 255
* #param string $value
* #return bool
*/
public function isValidVersionString(string $value): bool
{
//todo: implementation
}
}
How can I build this unittest? Because this method is also called via the constructor.
You could use a partial mock of the class:
class VersionTest extends \PHPUnit_Framework_TestCase
{
/**
* #test
*/
public function itShouldTest()
{
/** #var Version|\PHPUnit_Framework_MockObject_MockObject $mock */
$mock = $this->getMockBuilder(Version::class)
->disableOriginalConstructor()
->setMethods(['setVersionString','getVersionString'])
->getMock();
$this->assertTrue($mock->isValidVersionString('a-string'));
}
}
IMHO you can simply define the method isValidVersionString as static and simply test as is.

How inform PhpStorm about method position used in DependencyInjection way, which it "thinks" that doesn't exist?

Is there any option to inform PhpStorm that method which it says that not exist, is beyond his scope and is defined somewhere else ?
In simpler words:
I have method execution:
Db::transactional($this)->transactionalUpdate($result);
I have got method definition also:
public function transactionalUpdate(ImportantObjectButNotMuch $baconWithButter)
{
echo 'Do a lot of tricks...';
}
Unfortunately PhpStorm doesn't know that execution : ->transactionalUpdate($result); should run public function transactionalUpdate.
Is there any option to write PhpDoc or some other tag to inform it that in case of name refactorization it should change the original function name too ?
P.S. My class structure looks like this:
class Db
{
public static function transactional($object)
{
return TransactionalProxy::newInstance($object); //3. It returns ApiObject object
}
}
class ApiObject
{
public function update_record()
{
//1. I am starting from there
$result = new ImportantObjectButNotMuch();
Db::transactional($this)->transactionalUpdate($result); //2. Next i am passing $this to Db class, to transactional method //4. It should run below transactionalUpdate method
}
public function transactionalUpdate(ImportantObjectButNotMuch $baconWithButter)
{
echo 'Do a lot of tricks...'; //5. It ends there, it is working but PhpStorm doesn't see it
}
}
EDIT AFTER ANSWER:
#Nukeface and #Dmitry caused me to come up with the answer on my Question:
Lets see again into my files structure:
class Db
{
public static function transactional($object)
{
return TransactionalProxy::newInstance($object); //3. It returns ApiObject object
}
}
class ApiObject
{
public function update_record()
{
//1. I am starting from there
$result = new ImportantObjectButNotMuch();
//EDIT//Db::transactional($this)->transactionalUpdate($result); //2. Next i am passing $this to Db class, to transactional method //4. It should run below transactionalUpdate method
/** #var self $thisObject */
//Line above informs PhpStorm that $thisObject is ApiObject indeed
$thisObject = Db::transactional($this)
$thisObject->transactionalUpdate($result);
}
public function transactionalUpdate(ImportantObjectButNotMuch $baconWithButter)
{
echo 'Do a lot of tricks...'; //5. It ends there, it is working but PhpStorm doesn't see it
}
}
You should make use of Typehints. Updated your code below:
/**
* Class Db
* #package Namespace\To\Db
*/
class Db
{
/**
* #param $object
* #return ApiObject (per your line comment)
*/
public static function transactional($object)
{
return TransactionalProxy::newInstance($object); //3. It returns ApiObject object
}
}
/**
* Class ApiObject
* #package Namespace\To\ApiObject
*/
class ApiObject
{
/**
* #return void (I see no "return" statement)
*/
public function update_record()
{
//1. I am starting from there
$result = new ImportantObjectButNotMuch();
Db::transactional($this)->transactionalUpdate($result); //2. Next i am passing $this to Db class, to transactional method //4. It should run below transactionalUpdate method
}
/**
* #param ImportantObjectButNotMuch $baconWithButter
* #return void
*/
public function transactionalUpdate(ImportantObjectButNotMuch $baconWithButter)
{
echo 'Do a lot of tricks...'; //5. It ends there, it is working but PhpStorm doesn't see it
}
}
You can quickly create basic docblocks and typehints by typing /** then pressing either "enter" or "space". Enter if you want a docblock and space if you want a typehint.
Examples of own code below:
/**
* Class AbstractEventHandler
* #package Hzw\Mvc\Event
*/
abstract class AbstractEventHandler implements EventManagerAwareInterface
{
/**
* #var EventManagerInterface
*/
protected $events;
/**
* #var EntityManager|ObjectManager
*/
protected $entityManager;
/**
* AbstractEvent constructor.
* #param ObjectManager $entityManager
*/
public function __construct(ObjectManager $entityManager)
{
$this->setEntityManager($entityManager);
}
/**
* #param EventManagerInterface $events
*/
public function setEventManager(EventManagerInterface $events)
{
$events->setIdentifiers([
__CLASS__,
get_class($this)
]);
$this->events = $events;
}
/**
* #return EventManagerInterface
*/
public function getEventManager()
{
if (!$this->events) {
$this->setEventManager(new EventManager());
}
return $this->events;
}
/**
* #return ObjectManager|EntityManager
*/
public function getEntityManager()
{
return $this->entityManager;
}
/**
* #param ObjectManager|EntityManager $entityManager
* #return AbstractEventHandler
*/
public function setEntityManager($entityManager)
{
$this->entityManager = $entityManager;
return $this;
}
}
In the above example, PhpStorm knows what every function requires and returns. It knows the types and as some "return $this" it knows about the possibility to chain functions.
As an addition, the above code example uses only "docblocks". Below some "inline typehints" from within a function. Especially useful when it's not going to be immediately clear what is going to be returned. That way, again, PhpStorm knows from where to get functions, options, etc. to show you.
/** #var AbstractForm $form */
$form = $this->getFormElementManager()->get($formName, (is_null($formOptions) ? [] : $formOptions));
/** #var Request $request */
$request = $this->getRequest();
As a final hint. If you create a bunch of properties for a class, such as in my example protected $events or protected $entityManager, you can also generate the getters & setters. If your properties contain the docblocks, it will also generate the docblocks for you on these functions.
E.g. the property below
/**
* #var EntityManager|ObjectManager
*/
protected $entityManager;
When using "Alt + Insert" you get a menu at cursor location. Choose "Getters/Setters". In the pop-up, select "entityManager" and check the box at the bottom for "fluent setters". Then the code below is generated for you:
/**
* #return ObjectManager|EntityManager
*/
public function getEntityManager()
{
return $this->entityManager;
}
/**
* #param ObjectManager|EntityManager $entityManager
* #return AbstractEventHandler
*/
public function setEntityManager($entityManager)
{
$this->entityManager = $entityManager;
return $this;
}
The closes thing you can do to what you want to do is to use #return with multiple types.
/**
* #param $object
* #return ApiObject|AnotherApiObject|OneMoreApiObject
*/
public static function transactional($object)
{
return TransactionalProxy::newInstance($object);
}

How to document a class which implements more than one interface?

Let us say you have a class
class View implements ViewInterface, TemplateInterface {
private $template;
public function setTemplate($template) {
$this->template = $template;
return $this;
}
}
When you are then documenting the TemplateInterface and you come to the setTemplate($template) method what do you put down as its return type?
If I put View as the return type and then I later have different classes implementing the TemplateInterface which are not of type View my API documentation would be wrong.
Do you put down a return type of View, ViewInterface, TemplateInterface, or a mixture of them?
namespace Views\Interfaces;
interface TemplateInterface {
/* --------------------------------- */
public function getTemplate();
/* ---------------------------------
* #args: void
*
* #return: String - The name of the template.
*/
/* ------------------------------------------ */
public function setTemplate($template);
/* ------------------------------------------
* #arg 1: (String $template) - The name of the template file.
*
* #return: Not sure yet!!!
*/
}
The interface should look like:
interface TemplateInterface
{
/**
* #return TemplateInterface
*/
public function setTemplate($template);
}
And the view class:
class View implements ViewInterface, TemplateInterface {
private $template;
/**
* #return TemplateInterface
* or (maybe better)
* #return View (as it implements TemplateInterface and is more specialized)
*/
public function setTemplate($template) {
$this->template = $template;
return $this;
}
}

accessing entity manager inside phpunittest

I have the following unit test code in symfony:
<?php
// src/Acme/DemoBundle/Tests/Utility/CalculatorTest.php
namespace Shopious\MainBundle\Tests;
class ShippingCostTest extends \PHPUnit_Framework_TestCase
{
public function testShippingCost()
{
$em = $this->kernel->getContainer()->get('doctrine.orm.entity_manager');
$query = $em->createQueryBuilder();
$query->select('c')
->from("ShopiousUserBundle:City", 'c');
$result = $query->getQuery()->getResult();
var_dump($result);
}
}
and I am trying to access the entity manager here, howver it always gives me this error:
Undefined property: Acme\MainBundle\Tests\ShippingCostTest::$kernel
To achieve this you need to create a base test class (let's call it KernelAwareTest) with following contents:
<?php
namespace Shopious\MainBundle\Tests;
require_once dirname(__DIR__).'/../../../app/AppKernel.php';
/**
* Test case class helpful with Entity tests requiring the database interaction.
* For regular entity tests it's better to extend standard \PHPUnit_Framework_TestCase instead.
*/
abstract class KernelAwareTest extends \PHPUnit_Framework_TestCase
{
/**
* #var \Symfony\Component\HttpKernel\Kernel
*/
protected $kernel;
/**
* #var \Doctrine\ORM\EntityManager
*/
protected $entityManager;
/**
* #var \Symfony\Component\DependencyInjection\Container
*/
protected $container;
/**
* #return null
*/
public function setUp()
{
$this->kernel = new \AppKernel('test', true);
$this->kernel->boot();
$this->container = $this->kernel->getContainer();
$this->entityManager = $this->container->get('doctrine')->getManager();
$this->generateSchema();
parent::setUp();
}
/**
* #return null
*/
public function tearDown()
{
$this->kernel->shutdown();
parent::tearDown();
}
/**
* #return null
*/
protected function generateSchema()
{
$metadatas = $this->getMetadatas();
if (!empty($metadatas)) {
$tool = new \Doctrine\ORM\Tools\SchemaTool($this->entityManager);
$tool->dropSchema($metadatas);
$tool->createSchema($metadatas);
}
}
/**
* #return array
*/
protected function getMetadatas()
{
return $this->entityManager->getMetadataFactory()->getAllMetadata();
}
}
Then your own test class will be extended from this one:
<?php
namespace Shopious\MainBundle\Tests;
use Shopious\MainBundle\Tests\KernelAwareTest;
class ShippingCostTest extends KernelAwareTest
{
public function setUp()
{
parent::setUp();
// Your own setUp() goes here
}
// Tests themselves
}
And then use parent's class methods. In your case, to access entity manager, do:
$entityManager = $this->entityManager;

Categories