php annotations, namespaces and use keyword - php

I am writing an annotation parser for php (I cannot used any 3rd party ones due to specific needs) and I took sympfony2 code as an inspiration
namespace Acme\StoreBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="product")
*/
class Product
{
/**
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
As I can see here we define ORM in use statement and then somehow the annotation parser knows that ORM is an alias to Doctrine\ORM\Mapping.
My ultimate goal is to be able to pass class names as aliases into my annotations
use some/namespace/Enum;
use another/ns/PropEnum;
class Abc
{
/**
* #Enum(PropEnum)
**/
protected $prop;
}
Could you please point me towards the right direction as I do not even know where to start?
Thanks

Let's take a look at how doctrine/annotations (the package used by Symfony) solves this problem.
The PhpParser parses the PHP file of the class (which you can get by ReflectionClass#getFilename()). Let's look at the parseClass() method line by line:
if (method_exists($class, 'getUseStatements')) {
return $class->getUseStatements();
}
Doctrine has a custom StaticReflectionClass class which already has a getUseStatements() method to get the use statements in the class file. I assume you don't use that class, so let's move on!
if (false === $filename = $class->getFilename()) {
return array();
}
$content = $this->getFileContent($filename, $class->getStartLine());
if (null === $content) {
return array();
}
These statements retrieve the file content of the class. If there was no content or no file, there are also no use statements.
$namespace = preg_quote($class->getNamespaceName());
$content = preg_replace('/^.*?(\bnamespace\s+' . $namespace . '\s*[;{].*)$/s', '\\1', $content);
Here they are looking for the line that defines the namespace for the class in the file. It'll be followed by the use statements, so the regex just extracts the namespace and everything that follows it.
$tokenizer = new TokenParser('<?php ' . $content);
$statements = $tokenizer->parseUseStatements($class->getNamespaceName());
return $statements;
Then it creates a TokenParser class with only that content in it and let it find the use statements in it.
If you look at the TokenParser, you'll find that it uses token_get_all() to transform the file into PHP tokens used by the PHP engine and then just moves through that token tree looking for T_USE statements, which it extracts and saves.

Related

Symfony 4 Custom annotation problem #ORM\Entity does not exist

as part of the development of my CMS that I publish in a while .. I am facing a problem.
error :
[Semantical Error] The annotation "#Doctrine\ORM\Mapping\Entity" in class ScyLabs\GiftCodeBundle\Entity\GiftCode does not exist, or could not be auto-loaded.
I explain to you ,
Basically, in the project, everything is Overridable, it is already the case, with configurations in the file services.yaml.
For obvious reasons of simplicity, and an immediate need, to allow me to create a second bundle inheriting from it. I told myself that doing my "Override" or saying to the project: "Hello here I am, I am a class uses me" is very convenient with annotations (and much clearer).
So, I create a custom annotation (So far so good ..) That you find here ..
<?php
/**
* Created by PhpStorm.
* User: alex
* Date: 04/11/2019
* Time: 14:25
*/
namespace ScyLabs\NeptuneBundle\Annotation\ScyLabsNeptune;
/**
* #Annotation
* #Target({"CLASS"})
* #Attributes({
* #Attribute("key",type="string"),
* #Attribute("classNameSpace",type="string"),
* })
*/
class Override
{
/**
* #var string
*/
public $key;
/**
* #var string
*/
public $classNameSpace;
public function __construct(array $opts) {
$this->key = $opts['value'];
$this->classNameSpace = $opts['class'];
}
}
Well, my annotation was in place, I will now put it in an entity, .. As here
<?php
/**
* Created by PhpStorm.
* User: alex
* Date: 05/11/2019
* Time: 10:20
*/
namespace ScyLabs\GiftCodeBundle\Entity;
use ScyLabs\NeptuneBundle\Annotation\ScyLabsNeptune;
use Doctrine\ORM\Mapping as ORM;
/**
* #ScyLabsNeptune\Override("gift",class="ScyLabs\GiftCodeBundle\Entity\GiftCode")
* #ORM\Entity()
*/
class GiftCode
{
}
Why do that ? And in fact, everything is automated in the neptune, except special case, it will automatically generate all the URLs necessary for the proper functioning of an entity (ADD / EDIT / DELETE / LIST) ... And for this, it must indicate to the project that the entity exists, and that it must be part of this system.
So, until now I use a very complete configuration in services.yaml, in which I fill a table keys => value, corresponding to "key" => "Namespace"
In my case: "gift" => "ScyLabs \ GiftCodeBundle \ Entity \ GiftCode"
In short, suddenly, for override, I do a treatment in a compilation step
<?php
/**
* Created by PhpStorm.
* User: alex
* Date: 01/08/2018
* Time: 09:46
*/
namespace ScyLabs\NeptuneBundle;
class ScyLabsNeptuneBundle extends Bundle
{
public function getContainerExtension()
{
// Compilation de l'extension
return new ScyLabsNeptuneExtension();
}
}
And in this extension, I have this piece of code that makes everything
$bundles = require $projectDir.'/config/bundles.php';
foreach ($bundles as $bundle => $env){
if($bundle === ScyLabsNeptuneBundle::class)
continue;
if(method_exists(new $bundle,'getParent') && (new $bundle)->getParent() === ScyLabsNeptuneBundle::class){
$reader = new AnnotationReader();
$reflClass = new \ReflectionClass(GiftCode::class);
$classAnotations = $reader->getClassAnnotation($reflClass,"Override");
foreach ($classAnotations as $classAnotation){
if($classAnotation instanceof Override && class_exists($classAnotation->classNameSpace)){
$config['override'][$classAnotation->key] = $classAnotation->classNameSpace; }
}
}
}
From what I suspect after a lot of research, at the compilation stage of my extension, #ORM \ Entity, and or / / Autowire, it seems not compiled yet.
The problem is that suddenly, when I get my personal annotation (Override), I can not recover #ORM \ Entity, and I can not necessarily remove it because it would not work anymore as an entity.
Why do that here? Because behind I have another step of comoilation (A CompilationPass)
$container->addCompilerPass(new ResolveDoctrineTargetEntitiesPass(),PassConfig::TYPE_BEFORE_OPTIMIZATION,1000);
Who, redefined the Entities that doctrine will call in relation to the painting that I send to him (you know, the one I defined just before).
With this I give the possibility of override entities with an identical name.
What to do ?? .. I confess that I can not do more ...
Thanks in advance friends;)
By default, the annotation reader does not use the same autoloader as classes.
You need to tell him how to load the annotation class like that :
AnnotationRegistry::registerUniqueLoader('class_exists');
For more explanation, you can look at the doc https://www.doctrine-project.org/projects/doctrine-annotations/en/1.6/annotations.html#registering-annotations
thanks for you response.
But it don't work and this fonction is deprecated and removed to Annotations 2.0.
BUT , when i try i found a resolution.
When i follow your link and try the code in the page , i try this function
AnnotationRegistry#registerFile($file)
For get #ORM\Entity file path , i use
new \ReflectionClass(ORM\Entity::class);
And , this work.
I deleted AnnotationRegistry#registerFile($file) function , and this work.
Thanks you for help ;)
You'r the best

Why is this method available in this trait?

I am looking to extend a trait by using it in another trait. However the trait is using a method that looks like it isn't extending. The trait works, so I am wondering how.
Why does this trait have access to the markEntityForCleanup method?
The code is in this repo for Drupal Test Traits
<?php
namespace weitzman\DrupalTestTraits\Entity;
use Drupal\Tests\node\Traits\NodeCreationTrait as CoreNodeCreationTrait;
/**
* Wraps the node creation trait to track entities for deletion.
*/
trait NodeCreationTrait
{
use CoreNodeCreationTrait {
createNode as coreCreateNode;
}
/**
* Creates a node and marks it for automatic cleanup.
*
* #param array $settings
* #return \Drupal\node\NodeInterface
*/
protected function createNode(array $settings = [])
{
$entity = $this->coreCreateNode($settings);
$this->markEntityForCleanup($entity);
return $entity;
}
}
I found the issue.
When using the Drupal Test Traits package you are expected to use your own custom php-unit bootstrap.php and manually load the required packages.
Adding this line to the bottom of the bootstrap script will gain access to the namespace in php.
// <?php is needed for SO to do the syntax highlighting.
<?php
// Register more namespaces, as needed.
$class_loader->addPsr4('weitzman\DrupalTestTraits\Entity\\', "$root/vendor/weitzman\drupal-test-triats\src\Entity");

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 namespace in #param

Is it good practice to include namespaces for classes in #param annotations? I know that phpdoc does not support namespaces, but how will other tools like phpdox or Doxygen act?
Which way is better / more common?
namespace foo\someNamespace;
use foo\someOtherNamespace\MyOtherClass;
--- with namespace ---
/**
* #param \foo\someOtherNamespace\MyOtherClass $otherClass
*/
class myClass(MyOtherClass $otherClass)
{
// do something
}
--- without namespace ---
/**
* #param MyOtherClass $otherClass
*/
class myClass(MyOtherClass $otherClass)
{
// do something
}
The namespace is a part of the complete name of the class. So if you wouldn't add the namespace to the classname in #param you would give a wrong type.
Personally I think namespaces will become very soon the main criteria for the organization of classes in PHP. So the documentation tools will all have to use them.

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