Weird issue with parameter validation on usage of interface implementation - php

So basically I'm not sure if this is a PhpStorm issue parsing my code or if its a weird quirk of PHP and interfaces but basically I have the following interface
<?php
namespace App\Contracts;
/**
* Interface IFileSource
* #package App\Contracts
*/
interface IFileSource
{
public function getFilesByPattern(string $filePattern) : array;
}
with the following implementation
<?php
namespace App\Sources;
use App\Contracts\IFileService;
use App\Services\File\FileService;
/**
* Class FileSource
* #package App\Sources
*/
class FileSource implements IFileSource
{
/**
* #var FileService
*/
private $fileService;
public function __construct (IFileService $fileService)
{
$this->fileService = $fileService;
}
/**
* #param string $filePattern
* #return File[]
* NOTE THIS ASSUMES FILESYSTEM
*/
public function getFilesByPattern (string $filePattern) : array
{
$filesDetails = $this->fileService->getFilesByPattern($filePattern);
return [];
}
}
and the usage
<?php
namespace App\Console\Commands;
use App\Contracts\IFileSource;
use App\Sources\FileSource;
class ImportXML extends Command
{
/**
* #var FileSource
*/
protected $fileSource;
public function __construct (IFileSource $fileSource)
{
parent::__construct();
$this->fileSource = $fileSource;
}
public function handle () : void
{
$filePattern = 'APATTERN';
$files = $this->fileSource->getFilesByPattern($filePattern)
}
}
My question relates to the usage of this implementation.
So the following is a valid usage:
$filePattern = 'APATTERN';
$this->fileSource->getFilesByPattern(filePattern)
But for some reason the following is also seen as a valid usage?
$filePattern = 'APATTERN';
$this->fileSource->getFilesByPattern(filePattern,filePattern,filePattern,filePattern,filePattern,filePattern,filePattern)
Why does it not care that i am not conforming to my implementation?

Why does it not care that i am not conforming to my implementation
That's the whole point of interfaces - they don't care about implementations. They only care about how the method is defined and if the signature conforms to the interface.
However, I think the real question being asked here is why the PHP interpreter doesn't throw an exception when multiple arguments are passed to the function. The answer is because this is how PHP implements overloading. They allow a variable number of arguments to be passed which you can access with functions such as func_get_args.
You should definitely read https://www.php.net/manual/en/functions.arguments.php#functions.variable-arg-list and also look into the new(ish) splat operator ....
Similar QAs
How to pass variable number of arguments to a PHP function
https://softwareengineering.stackexchange.com/questions/165467/why-php-doesnt-support-function-overloading

So incase someone else stumbles across this,
thanks to LazyOne for the help explaining what i was doing wrong
it was due to the fact i am enforcing the implementation in the PHPdoc rather than relying on the interface to enforce the type hinting (or adding the interface as the type hint instead of the implementation), once i changed this it began complaining as expected.
Why enforce an implementation when the point of the interface is the enforce such things.
Doh

Related

Make PhpStorm favor #param instead of type declaration when using generics

In a post on the software engineering forum me and the community agreed that one of my architecture problems might be best solved using generics. Since in PHP generics are only supported via doc tags, my interface declarations always have two param types hinted: The #param tag and the PHP type declaration:
<?php
/**
* #template T of NodeInterface
*/
interface DirectedLinkInterface
{
/**
* #param T $startNode
*/
public function setStartNode(NodeInterface $startNode);
}
The problem is that whenever I use PHP's type declaration, PhpStorm ignores the more specific template variable.
Example:
I have a class that implements a specific version of the generic interface:
/**
* #template-implements DirectedLinkInterface<DrawActionNode>
*/
class DirectedImageProcessingLink implements DirectedLinkInterface, FollowLinkInterface
{
/**
* #param DrawActionNode $startNode
*/
public function setStartNode(NodeInterface $startNode): void
{
$this->startNode = $startNode;
}
}
But when I use this class incorrectly, PhpStorm doesn't find the error, UNLESS I remove the type declarations:
$node = new WhateverNodeInterface();
$link = new DirectedImageProcessingLink();
$link->setStartNode($node); //No error found by PhpStorm
The above code will only be considered as faulty when I remove the type declaration, which I don't really want since a missing type declaration looks kind of old school php hackish, even if the hinted type is more generic than the PHPDoc.
<?php
/**
* #template T of NodeInterface
*/
interface DirectedLinkInterface
{
/**
* #param T $startNode
*/
public function setStartNode(/*No type declaration here*/ $startNode);
//Removing the type declaration as done above makes PhpStorm use the actual template
//variable which, in the interface user's code, will be more specific and thus errors can be better found
}
Do you know any way to convince PhpStorm to use the #param tag even though there is a type declaration? Or do you think that type declarations aren't that necessary if you use #param tags in combination with generics?

Use variable in php comment

I need to add $variable in my php return document
for example I have this function :
/**
* #return \Panel\Model\{$Service}
*/
public function getService($Service){
// I call service `foo` from Model folder
}
I see this post : What's the meaning of #var in php comments but it has no information about how to do it , and also study #var-phpdoc but that has nothing for me.
if you ask me why I should do that , because I want use phpStorm Ctrl+Click advantage on $this->getService('foo')->bar()
thanks in advance
As LazyOne said in the comments already PHP interfaces should solve your problem. You can 't use variables in PHPDoc formatted comments. Sure, if you use an IDE like PHPStorm with a plugin that enables the usage of variables in PHPDoc comments, the problem is solved for yourself. What, when other developers, which don 't use PHPStorm or the relevant plugin, want to work in the same project? In my view you should use php native functionality to solve your issue.
Here 's a short example how to use interfaces.
declare('strict_types=1');
namespace Application\Model;
interface ModelInterface
{
public function getFoo() : string;
public function setFoo() : ModelInterface;
}
The only thing you have to do now is using this interface with your models like in the following example.
declare('strict_types=1');
namespace Application\Model;
class FooModel implements ModelInterface
{
protected $foo = '';
public function getFoo() : string
{
return $this->foo;
}
public function setFoo(string $foo) : ModelInterface
{
$this->foo = $foo;
return $this;
}
}
As you can see the FooModel class implements the ModelInterface interface. So you have to use the methods declared in the interface in you model class. This means, that your getService Method could look like the following example.
/**
* Some getter function to get a model
* #return \Application\Model\ModelInterface
*/
public function getService($service) : ModelInterface
{
return $service->get(\Application\Model\Foo::class);
}
Your IDE knows now which methods the returned class can use. It allows you to use chaining and some more features. While typing your IDE should know now, that the returned class can use getFoo and setFoo methods. Further the setFoo methods enables comfortable chaining for calls like ..
// variable contains the string 'foo'
// your ide knows all methods
$fooString = $this->getService($serviceLocator)->setFoo('foo')->getFoo();
Are you using Symfony? Then you could use the Symfony plugin that solves this problem for you. For other frameworks, there should be similar solutions. If you use your own framework, you need to write such a plugin on your own, as PhpStorm can not resolve the given class otherwise.
I think what you are looking for is phpdoc.
https://docs.phpdoc.org/guides/docblocks.html
/**
* #param string $Service This is the description.
* #return \Panel\Model\{$Service}
*/
public function getService($Service){
// I call service `foo` from Model folder
}

PHP dynamic return type hinting

Suppose I have the following PHP function:
/**
* #param string $className
* #param array $parameters
* #return mixed
*/
function getFirstObject($className, $parameters) {
// This uses a Doctrine DQl builder, but it could easily replaced
// by something else. The point is, that this function can return
// instances of many different classes, that do not necessarily
// have common signatures.
$builder = createQueryBuilder()
->select('obj')
->from($className, 'obj');
addParamClausesToBuilder($builder, $parameters, 'obj');
$objects = $builder
->getQuery()
->getResult();
return empty($objects) ? null : array_pop($objects);
}
Basically, the function always returns either an instance of the class specified with the $className parameter or null, if something went wrong. The only catch is, that I do not know the full list of classes this function can return. (at compile time)
Is it possible to get type hinting for the return type of this kind of function?
In Java, I would simply use generics to imply the return type:
static <T> T getOneObject(Class<? extends T> clazz, ParameterStorage parameters) {
...
}
I am aware of the manual type hinting, like
/** #var Foo $foo */
$foo = getOneObject('Foo', $params);
but I would like to have a solution that does not require this boilerplate line.
To elaborate: I am trying to write a wrapper around Doctrine, so that I can easily get the model entities that I want, while encapsulating all the specific usage of the ORM system. I am using PhpStorm.
** edited function to reflect my intended usage. I originally wanted to keep it clean of any specific use case to not bloat the question. Also note, that the actual wrapper is more complex, since I also incorporate model-specific implicit object relations and joins ect.
I use phpdoc #method for this purpose. For example, I create AbstractRepository class which is extend by other Repository classes. Suppose we have AbstractRepository::process(array $results) method whose return type changes according to the class that extends it.
So in sub class:
/**
* #method Car[] process(array $results)
*/
class CarRepo extends AbstractRepository {
//implementation of process() is in the parent class
}
Update 1:
You could also use phpstan/phpstan library. Which is used for static code analyses and you can use it to define generic return types:
/**
* #template T
* #param class-string<T> $className
* #param int $id
* #return T|null
*/
function findEntity(string $className, int $id)
{
// ...
}
This can now be achieved with the IntellJ (IDEA/phpStorm/webStorm) plugin DynamicReturnTypePlugin:
https://github.com/pbyrne84/DynamicReturnTypePlugin
If you use PHPStorm or VSCode (with the extension PHP Intelephense by Ben Mewburn) there is an implementation named metadata where you could specify your own type-hinting based on your code doing the magic inside. So the following should work (as it did on VSCode 1.71.2)
<?php
namespace PHPSTORM_META {
override(\getFirstObject(0), map(['' => '$0']));
}

Is it possible to define method with different parameters in a PHP interface?

I'm developing a service that is being injected a Logger object but I can have 2 different kind of loggers, I'm planning on having a syslog logger and a queue message system logger. Is this possible?
The idea is having an interface:
interface Loggable
{
public function log() ;
}
and 2 classes that implement that interface:
class Syslogger implements Loggable
{
public function log()
{
...
}
}
class QMSLogger implements Loggable
{
public function log($queueName)
{
...
}
}
The only way I could come with is having an array as a parameter and use it on one class and not using on the other one... but that is a little bit smelly :P
You're asking if it's possible: yes it is, but…
If you implement an interface, you must respect its contract.
interface Loggable
{
public function log();
}
This interface's contract is you can call log() without any parameter.
In order to respect that, you can make the parameter optional:
class QMSLogger implements Loggable
{
public function log($queueName = null)
{
...
}
}
This is perfectly valid PHP and it respects the Liskov Substitution Principle. Of course, you must not use that optional parameter when coding against the interface, else you are obviously breaking the interface. Such parameter can be useful only when you are using the implementation (e.g. in some part of the code which is tightly coupled to the QMSLogger).
However this is probably not the solution to your problem as $queueName seems to be a configuration value and it might be better to pass it in the class' constructor (as explained in the other answer).
As stated in the comments, that's not the same interface. If you cannot generalize the interface across all possible logger implementations, make the configuration differences part of the instance constructor:
class QMSLogger implements Loggable {
protected $queueName;
public function __construct($queueName) {
$this->queueName = $queueName;
}
public function log() {
...
}
}
I came across a similar case where I wanted to create an interface that simply ensure any classes that implemented it would have a method of the same name, but would allow for implementation with different parameters.
/**
* Interface Loggable
* #method log
*/
interface Loggable
{
}
Now the Loggable interface can be implemented with different parameters like so.
class Syslogger implements Loggable
{
public function log($key, $value)
{
...
}
}
You can also pass the parameters as an array , in this way you respect the contract from one hand and also be flexible to insert any values with any amount inside the array , check this out :
abstract class FeaturesAbstract
{
/**
* #param array $paramsArray
*
* #return mixed
*/
abstract public function addExecute($paramsArray);
}
And to actually use this method you could send the parameters like this :
$this->abstract->addExecute(array('paramA' => $paramA, 'paramB' => $paramB));
And then inside the concrete implementation you get the parameters like this :
/**
* #param array $paramsArray
*
* #return void
*/
public function addExecute($paramsArray)
{
$a = $paramsArray['paramA'];
$b = $paramsArray['paramB'];
$c = ...
}
Good luck :)

Preserving auto-completion abilities with Symfony2 Dependency Injection

I'm using PHP Storm as my IDE, but I believe that other IDE's such as Netbeans will have the same issue as I'll explain below.
When using a framework like Symfony2, we have the wonderful world of Dependency Injection added. So objects can simply be instantiated using code like the following snippet:
$myThingy = $this->get('some_cool_service');
This is very handy, as objects are already configured beforehand. The one problem is, that auto-completion breaks entirely in basically any PHP IDE, as the IDE does not know what type the get() method is returning.
Is there a way to preserve auto-completion? Would creating for example an extension of Controller be the answer? For example:
class MyController extends Controller {
/**
* #return \MyNamespace\CoolService
*/
public getSomeCoolService() {
return new CoolService();
}
}
and then for application controllers, specify MyController as the base class instead of Controller?
What about using a Factory class, or any other possible methods?
It is more involving, but you can still do this with eclipse PDT:
$myThingy = $this->get('some_cool_service');
/* #var $myThingy \MyNamespace\CoolService */
UPDATE:
The example on this page shows you may also use the other way round with phpStorm:
$myThingy = $this->get('some_cool_service');
/* #var \MyNamespace\CoolService $myThingy */
You could define private properties in your controllers
class MyController extends Controller
{
/**
* #var \Namespace\To\SomeCoolService;
*/
private $my_service;
public function myAction()
{
$this->my_service = $this->get('some_cool_service');
/**
* enjoy your autocompletion :)
*/
}
}
I use base Controller class for bundle. You need to annotate the return in method. At least that works on Eclipse.
/**
* Gets SomeCoolService
*
* #return \Namespace\To\SomeCoolService
*/
protected function getSomeCoolService()
{
return $this->get('some_cool_service');
}
I don't like /*var ... */, because it gets too much into code.
I don't like private properties, because you can wrongly assume that services are already loaded.
I use Komodo Studio, and tagging variables with #var, even inside methods, preserves auto completion for me.
namespace MyProject\MyBundle\Controller;
use Symfony\Component\DependencyInjection\ContainerAware;
use Symfony\Component\HttpFoundation\Request;
class WelcomeController extends ContainerAware
{
public function indexAction()
{
/*#var Request*/$request = $this->container->get('request');
$request->[autocomplete hint list appears here]
}
}
working with netbeans IDE 7.1.2 PHP

Categories