Preserving auto-completion abilities with Symfony2 Dependency Injection - php

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

Related

Weird issue with parameter validation on usage of interface implementation

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

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
}

Code completion of my classes in netbeans 8

How do I enable code completion for clases and methods I create in code for in netbeans 8.
Thanks,
Dion
You really should google this kinda thing, but I'm in the mood to answer a question :-)
Anyway, most IDE's read your code in pretty much the same way. I can't attest to NetBeans specifically (I use PHPStorm), but the general idea is to make sure you add docblocks to your classes, methods etc. The IDE reads these and can then provide decent code-completion.
<?php
namespace App;
/**
* Class MyClass
* Does some stuff
* #package App
*/
class MyClass extends SomeOtherClass
{
/**
* This is my var
* #var string
*/
public $myVar = 'some val';
/**
* This is my method
* #param string $yourString
* #return SomethingElse
*/
public function myMethod ($yourString)
{
$this->myVar = $yourString;
return new SomethingElse($this->myVar);
}
}
Have a look at the PHPdoc site for the tag syntax. Most IDE's will also have a way of generating this for you as well.

Symfony container traits

Strange problem,
I have controller which uses \Symfony\Component\DependencyInjection\ContainerAwareTrait
class MainController
{
use \Symfony\Component\DependencyInjection\ContainerAwareTrait;
/**
* #Route("/", name="_index")
* #Template()
*/
public function indexAction()
{
var_dump($this->container);
return array();
}
}
but result is NULL.
Tried on:
Symfony 2.5.*
MAMP 3.0
PHP 5.4 5.5
My searches have not helped me. I think the solution is easy.
Any ideas how to trace this error?
UPD: When i extend from Controller, container is available and everything is working properly. But according to symfony Controller reference extending is optional, i can use traits instead.
I'll venture a guess based on a quick glance into the Symfony source code: You still need to declare that you adhere to the ContainerAwareInterface Interface.
This is what the code looks like whenever Symfony is setting a container on a controller.
if ($controller instanceof ContainerAwareInterface) {
$controller->setContainer($this->container);
}
So then I suppose you need to do something like this:
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerAwareTrait;
// ...
class MainController implements ContainerAwareInterface
{
use ContainerAwareTrait;
/**
* #Route("/", name="_index")
* #Template()
*/
public function indexAction()
{
var_dump($this->container);
return array();
}
}
As an aside, this is arguably a pretty good case for Duck Typing, particularly if they had named the method something a bit more specific or if it were cheaper to inspect the parameter types to methods at runtime

How to prevent PhpStorm from showing an Expected... warning when using PHPUnit mocks?

When mocking an interface in PHPUnit, PhpStorm complains when it's used as parameter for a type-hinted function.
Example
interface InterfaceA{
}
class ClassA{
public function foo(InterfaceA $foo){}
}
class PhpStormTest extends PHPUnit_Framework_TestCase
{
public function testFoo(){
$mock = $this->getMock("InterfaceA");
$a = new ClassA();
$a->foo($mock);
}
}
On $a->foo($mock); PhpStorm underlines $mock with the warning Expected InterfaceA, got PHPUnit_Framework_MockObject_MockObject
Image
I guess it's happening because PHPUnit creates the mock a runtime and PhpStorm cannot know that it's actually implementing the interface.
I found a workaround to this problem in the Jetbrain blog at PhpStorm Type Inference and Mocking Frameworks. The important part:
By default, PhpStorm is capable of figuring out the available methods
on the mock object. However, it only displays those for PHPUnit’s
PHPUnit_Framework_MockObject_MockObject class. Fortunately, we can
solve this by instructing PhpStorm to infer type information from
other classes as well, by using a simple docblock comment.
So to make the warning disappear, we need to add /** #var InterfaceA */ /** #var InterfaceA|PHPUnit_Framework_MockObject_MockObject */ (cudos to Supericy) to let PhpStorm know our mock actually implements InterfaceA:
interface InterfaceA{
}
class ClassA{
public function foo(InterfaceA $foo){}
}
class PhpStormTest extends PHPUnit_Framework_TestCase
{
public function testFoo(){
/** #var InterfaceA|PHPUnit_Framework_MockObject_MockObject */
$mock = $this->getMock("InterfaceA");
$a = new ClassA();
$a->foo($mock);
}
}
This bugged me for some time, hope it helps someone :)
Edit
Since PHPUnit_Framework_MockObject_MockObject is really ugly to type, you can abbreviate it via MOOMOO and let PHPStorms auto-complete do the rest:
Another plugin I have used for this is the Dynamic Return Type Plugin, it lets you configure return types of methods in a very dynamic way (the example is to have better type information from Mocks).
Another, less verbose but possibly riskier, approach can be to wrap the call to getMock() with your own function and mark that with #return mixed:
/**
*
* #return mixed
*/
public function myGetMock($str)
{
return $this->getMock($str);
}
Calling this method instead of $this->getMock() will make the warning disappear.

Categories