Why does PHPDoc error in PHPStorm over this code? - php

In the following code the #return is underlined red. I have it expecting an interface to be returned because that is what all of the different Vendor adapters implement.
/**
* VendorFactory constructor.
* #param Model $model
* #return \Traders\Interfaces\VendorAdapterInterface
*/
public function __construct(Model $model)
{
return $this->createAdapter($model);
}
This is the code for the createAdapter which does not have the #return underlined in red.
/**
* #param Model $model
* #return \Traders\Interfaces\VendorAdapterInterface
*/
public function createAdapter(Model $model)
{
$type = str_replace('App\Models\\', '', get_class($model)).'s';
$fqcn = '\Traders\Adapters\\'.$type.'\\'.ucfirst(strtolower($model->name));
return new $fqcn($model);
}
I have tried doing the /** docblock and letting PHPStorm enter what it believes is the return value and it just keeps giving me
#return mixed

Your problem is the return in the constructor.
Constructors do not take return values, they get executed when an instance of that class is instantiated.

Related

PHP: Understanding #templates annotation with the use of #throws tags

I'am using PhpStorm and have started using #template annotations.
However, I have trouble understanding exactly how they work in relation to #throws tags.
Here are some examples (of fantasy) to explain what I did not understand (or that I am wrong ...).
Let's start with this:
/**
* A custom ErrorException
*/
class CustomErrorException extends ErrorException {}
/**
* This class can throw exceptions
*/
class CanThrowExceptions
{
/**
* Checks `$value` is `true` or throws `$exception`
* #template TrueValue
* #template PassedException as \ErrorException
* #param TrueValue $value Value you want to check
* #param class-string<PassedException> $exception
* #return TrueValue
* #throws PassedException
*/
public static function isTrueOrErrorException($value, string $exception = \ErrorException::class)
{
if ($value) {
return $value;
}
throw new $exception;
}
}
In other words, the isTrueOrErrorException method can throw $exception.
$exception can be ErrorException (default) or another exception that extends ErrorException (like CustomErrorException).
So far, I believe I have used the #templates correctly (is that right?).
Now let's try to get PhpStorm to generate some code. I get this result:
class Example
{
/**
* #return bool
*/
public function test1(): bool
{
return CanThrowExceptions::isTrueOrErrorException(true);
}
/**
* #return bool
*/
public function test2(): bool
{
return CanThrowExceptions::isTrueOrErrorException(true, CustomErrorException::class);
}
}
There is no #throws tag, when I would expect ErrorException for the first method and CustomErrorException for the second.
Needless to say, if I try to edit by hand, the IDE will complain and report a problem to me.
I mean, I think I should normally get this (but this is not the case!):
class Example
{
/**
* #return bool
* #throws \ErrorException
*/
public function test1(): bool
{
return CanThrowExceptions::isTrueOrErrorException(true);
}
/**
* #return bool
* #throws \CustomErrorException
*/
public function test2(): bool
{
return CanThrowExceptions::isTrueOrErrorException(true, CustomErrorException::class);
}
}
So what have I done wrong so far?
Let's go to complicate the code.
I add the valueIsIntOrCustomErrorException() method to the CanThrowExceptions class, which is now like this:
class CustomErrorException extends ErrorException {}
/**
* This class can throw exceptions
*/
class CanThrowExceptions
{
/**
* Checks `$value` is an int or throws `CustomErrorException`
* #template IntValue
* #param IntValue $value
* #return IntValue
*/
public static function valueIsIntOrCustomErrorException($value)
{
//This returns a boolean, so I can't return here
self::isTrueOrErrorException(is_int($value), CustomErrorException::class);
return $value;
}
/**
* Checks `$value` is `true` or throws `$exception`
* #template TrueValue
* #template PassedException as \ErrorException
* #param TrueValue $value Value you want to check
* #param class-string<PassedException> $exception
* #return TrueValue
* #throws PassedException
*/
public static function isTrueOrErrorException($value, string $exception = \ErrorException::class)
{
if ($value) {
return $value;
}
throw new $exception;
}
}
Yet another oddity. Again, no #throws tags for valueIsIntOrCustomErrorException() method!
This is strange, because at least here I would have expected it, since it calls the isTrueOrErrorException() method.
Still, I keep wondering, where am I wrong?
I add some tests of this new method:
class Example
{
//Here same `test1 ()` and `test2 ()` as above
/**
* #return int
*/
public function test3(): int
{
return CanThrowExceptions::valueIsIntOrCustomErrorException(1);
}
}
Again, no #throws tags (should be #throws \CustomErrorException).
Can you help me, with examples or by suggesting corrections to my code? What I did not understand?
Thanks.

What's the best design for extra data on behaviour class?

I have a game where the player can finish some tasks.
I have separated the behaviour part of the task to its ORM part.
Eventually a copy of the task is being saved somewhere on the player's document (doesn't matter where for this specific question).
The problem is, I am not sure where to put the extra information that I send to the client that is not necessary for the behaviour itself, but it is needed to show the player information regarding the task itself.
This is my task interface:
interface ITask
{
/**
* #param Player $player
*/
public function init(Player $player);
/**
* #param PlayerAction $action
*/
public function progress(PlayerAction $action);
public function reset();
/**
* #return bool
*/
public function isComplete();
}
This is my abstract task:
abstract class BaseTask implements ITask
{
/**
* #var int
*/
public $id;
/**
* #var int
*/
protected $currentValue;
/**
* #var int
*/
protected $targetValue;
public function __construct($targetValue)
{
$this->currentValue = 0;
$this->targetValue = $targetValue;
}
/**
* #param int
*/
public abstract function setCurrentValue($current);
/**
* #return int
*/
public abstract function getCurrentValue();
/**
* #return int
*/
public abstract function getID();
/**
* #param int
*/
public abstract function setID($id);
/**
* #return int
*/
public abstract function getTargetValue();
/**
* #param int
*/
public abstract function setTargetValue($target);
/**
* #return boolean
*/
public function isComplete()
{
if ($this->getCurrentValue() >= $this->getTargetValue())
{
return true;
}
return false;
}
}
Now I need to decide how where to put the extra data, e.g description, title, theme etc...
I thought about two options: I can just put it on the base task
itself, but then what happens if I don't need it? I just leave it
blank? feel like the wrong place for me.
I could create a wrapper
class that will hold the task, but then I will need to always
call the wrapper to get to the task, and it feels kind of
wrong.
Looking for alternative suggestions.
You should inherit the CustomTask from TaskBase.
If you you have limitation in inheritance, encapsulate additional fields into a class called TaskAdditionalInfoBase and associate to the TaskBase.
Then various classes can inherit TaskAdditionalInfoBase to present a custom additional info to the the task.

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);
}

Does this concept to add methods to an existing PHP interface scale?

I am using Nicolas Widart's Laravel Modules package to help manage a large app, and keep everything separated into logical modules. I would like to be able to drop in different modules and have them play nicely without any extra configuration.
All of my modules will define interfaces and default implementations that allow the application (the system controlling which modules are loaded) to specify that it wants to use a specific implementation instead, through dependency injection.
I am able to make some assumptions by having some modules require others, for example a payment processing module (Module PP) can assume that a payment is tied to a user (with which the interface for a user is defined in another module, Module U).
My ideal scenario is that I could add to an existing PHP interface that is defined in another required module. For example, being able to retrieve a user from a repository defined in Module U and call a method on it that was defined in Module PP.
Once Module PP resolves the interface (again, through dependency injection) from Module U to a class, I want my method from Module PP to be callable on that class.
I have been able to achieve this using the __call magic method as below.
Extensions Module
This module defines the core operations to add to an existing interface.
IsExtendable Interface
<?php
namespace Modules\Extensions\Contracts;
interface IsExtendable
{
/**
* Get the list of extensions for this entity.
*
* #return array
*/
public static function getExtensions();
/**
* Adds an extension to this entity.
*
* #param string $name
* #param mixed $function
*/
public static function addExtension($name, $function);
/**
* Checks whether the entity has the given extension.
*
* #param string $name
*
* #return bool
*/
public static function hasExtension($name);
/**
* Call the extension if it exists, or pass it further up the chain.
*
* #param string $name
* #param mixed $arguments
*
* #return mixed
*/
public function __call($name, $arguments);
}
IsExtendable Trait
<?php
namespace Modules\Extensions;
trait IsExtendable
{
/** #var $extensions */
private static $extensions = [];
/**
* Get the list of extensions for this entity.
*
* #return array
*/
public static function getExtensions()
{
return self::$extensions;
}
/**
* Adds an extension to this entity.
*
* #param string $name
* #param mixed $function
*/
public static function addExtension($name, $function)
{
if(is_callable($function) == FALSE)
{
throw new \InvalidArgumentException('Function must be callable.');
}
self::$extensions[$name] = $function;
}
/**
* Checks whether the entity has the given extension.
*
* #param string $name
*
* #return bool
*/
public static function hasExtension($name)
{
return array_key_exists($name, self::getExtensions()) == TRUE;
}
/**
* Calls the extension if it exists, or passes it further up the chain.
*
* #param string $name
* #param mixed $arguments
*
* #return mixed
*/
public function __call($name, $arguments)
{
if(self::hasExtension($name) == TRUE)
{
$callable = self::getExtensions()[$name];
return call_user_func_array($callable, array_merge(array($this), $arguments));
}
else
{
return parent::__call($name, $arguments);
}
}
}
Service Provider
<?php
namespace Modules\Extensions\Providers;
use Illuminate\Support\ServiceProvider;
use Modules\Extensions\Contracts\IsExtendable as IsExtendableContract;
class ExtensionServiceProvider extends ServiceProvider
{
/**
* #param string $implementation
* #param string $functionName
*
* #return callable
*/
public function prepareExtension($implementation, $functionName)
{
return $implementation . '::' . $functionName;
}
/**
* #param string $contract
* #param string $implementation
*
* #return void
*/
public function extractExtensions($contract, $implementation)
{
$reflection = new \ReflectionClass($implementation);
$methods = [];
foreach($reflection->getMethods(\ReflectionMethod::IS_STATIC) as $method)
{
// TODO: May be able to use $method->getClosure() here
// https://stackoverflow.com/questions/8299886/php-get-static-methods
$methods[] = $method->getName();
}
$this->registerExtensions($contract, $methods, $implementation);
}
/**
* #param string $contract
* #param string $name
* #param string $function
*
* #return void
*/
public function registerExtension($contract, $name, $function)
{
// Resolve the contract to an implementation
$base = app($contract);
// Check that it is suitable for extension
if($base instanceof IsExtendableContract)
{
$base::addExtension($name, $function);
}
}
/**
* #param string $contract
* #param array $extensions
* #param string|null $implementation
*
* #return void
*/
public function registerExtensions($contract, array $extensions = [], $implementation = NULL)
{
// Resolve the contract to an implementation
$base = app($contract);
// Check that it is suitable for extension
if($base instanceof IsExtendableContract)
{
foreach($extensions as $name => $function)
{
if(is_int($name) == TRUE)
{
if(is_string($function) == TRUE)
{
$name = $function;
}
else
{
throw new \InvalidArgumentException('All extensions must have a valid name.');
}
}
if(is_string($function) == TRUE)
{
if(strpos($function, '::') === FALSE && $implementation != NULL)
{
$function = $this->prepareExtension($implementation, $function);
}
}
$base::addExtension($name, $function);
}
}
}
}
Module U
User Interface
<?php
namespace Modules\Auth\Contracts\Entities;
interface User
{
/**
* #return int
*/
public function getId();
/**
* #return string
*/
public function getName();
/**
* #return string
*/
public function getEmail();
/**
* #return \DateTime
*/
public function getCreatedAt();
/**
* #return \DateTime
*/
public function getUpdatedAt();
}
User Implementation
<?php
namespace Modules\Auth\Entities;
use Modules\Extensions\Contracts\IsExtendable as IsExtendableContract;
use Modules\Auth\Contracts\Entities\User as UserContract;
use Modules\Extensions\IsExtendable;
class User implements
IsExtendableContract,
UserContract
{
use IsExtendable;
/**
* #return int
*/
public function getId()
{
return $this->id;
}
/**
* #return string
*/
public function getName()
{
return $this->name;
}
/**
* #return string
*/
public function getEmail()
{
return $this->email;
}
/**
* #return \DateTime
*/
public function getCreatedAt()
{
return $this->created_at;
}
/**
* #return \DateTime
*/
public function getUpdatedAt()
{
return $this->updated_at;
}
}
Module PP
User Extension
<?php
namespace Modules\Test\Entities\Extensions;
use Modules\Auth\Contracts\Entities\User;
class UserExtension
{
/**
* #param User $context
*/
public static function getCardLastFour($context)
{
return $context->card_last_four;
}
/**
* #param User $context
*/
public static function getCardBrand($context)
{
return $context->card_brand;
}
/**
* #param User $context
*/
public static function getStripeId($context)
{
return $context->stripe_id;
}
}
Service Provider
<?php
namespace Modules\Test\Providers\Extensions;
use Modules\Auth\Contracts\Entities\User as UserContract;
use Modules\Test\Entities\Extensions\UserExtension;
use Modules\Extensions\Providers\ExtensionServiceProvider;
class StripeExtensionProvider extends ExtensionServiceProvider
{
public function boot()
{
// TODO: Set the contract as a static field on the extension to then automatically extract from all extension files in a folder
$this->extractExtensions(UserContract::class, UserExtension::class);
}
}
My question is, is this method scalable (across maybe 10 modules), and can you foresee any issues with it? Or is there a better/more popular (and supported) way to do this? I don't want to get 2 years into a project and discover that I really hate the way I've implemented this.
I know that this concept won't support IDE autocompletion out of the box but I could build in a way to generate the PHPDocs similar to this package.
I have researched the Decorator pattern but this feels clunky in that I would always need to rely on a new implementation within each module, instead of just adding to the existing one.
I realise this is a big question so my sincere thanks to anyone willing to have a look at it!
Check out Laravel's macroable trait. It's basically the same idea, and Laravel uses it all over the place.
So yes, it scales - up to a certain point. Like almost everything else, this is a tool that can be abused. Use it with a dash of common sense, and you should be OK.

Netbeans Auto-Complete Not Working For Custom PHP Class

I've got the following class in a Zend Framework project:
<?php
/**
* User's class
*
* This class should be responsible for all
*
* #author Steve Davies
* #copyright 2012
* #version SVN: $Id$
*/
class Api_Admin_Users extends Api_Core
{
/**
* Class Constructor
*
* #return void
*/
public function __construct() {
parent::__construct();
}
/**
* Get User's name
*
* This returns the user's name
*
* #return void
*/
public function new() {
$user = self::_instance()->_em->getRepository('UserManagement\Users')->find('1');
echo $user->getFullName();
}
}
However when I try and use code hinting on $user->getFullName();, it doesn't work.
Using the following trick from here, it works:
/**
* Get User's name
*
* This returns the user's name
*
* #return void
*/
public function new() {
/* #var $user \UserManagement\Users */
$user = self::_instance()->_em->getRepository('UserManagement\Users')->find('1');
echo $user->getFullName();
}
But, I don't want to have to include that comment line everytime I instantiate the object. When I try to move this to the Class definition - or even the method definition, it fails to work.
Can anyone provide an answer for this?
PHP is a dynamic language and as such it is not trivial to infer variable types from static code analysis (like it is in Java for example).
It's especially difficult with factory methods like yours getRepository('UserManagement\Users').
NetBeans currently has no way of knowing how to translate the function argument to the type of returned variable (unless you're satisfied with some parent class from which all subclasses returned by that factory derive). Unfortunatelly vdoc's are the only way to deal with such cases.
Create a method in Api_Admin_Users to access the repository and add the type hint there. This will benefit all methods in the class. As long as the methods in the repository are type-hinted correctly, you're all set.
class Api_Admin_Users extends Api_Core
{
/**
* Class Constructor
*
* #return void
*/
public function __construct() {
parent::__construct();
}
/**
* Get the repository
*
* #return \UserManagement\Users
*/
public static function getRepository() {
return self::_instance()->_em->getRepository('UserManagement\Users');
}
/**
* Get User's name
*
* This returns the user's name
*
* #return void
*/
public function new() {
$user = self::getRepository()->find('1');
echo $user->getFullName();
}
}

Categories