Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move to a modern version of reflection docblock #140

Merged
merged 8 commits into from
Aug 6, 2022
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,6 @@ For further documentation information, see the [docs](docs/en/Index.md)
This module changes the content of your files and currently there is no backup functionality. PHPStorm has a Local history for files and of course you have your code version controlled...
I tried to add complete UnitTests, but I can't garantuee every situation is covered.

Windows users should be aware that the PHP Docs are generated with PSR in mind and use \n for line endings rather than Window's \r\n, some editors may have a hard time with these line endings.

This module should **never** be installed on a production environment.
4 changes: 2 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@
}
],
"require": {
"php": ">=5.6.0",
"php": "^7.2 || ^8.0",
"silverstripe/framework": "^4",
"phpdocumentor/reflection-docblock": "^2.0@dev"
"phpdocumentor/reflection-docblock": "^5.2"
},
"require-dev": {
"scriptfusion/phpunit-immediate-exception-printer": "^1",
Expand Down
40 changes: 33 additions & 7 deletions src/Generators/AbstractTagGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,21 @@

namespace SilverLeague\IDEAnnotator\Generators;

use Generator;
use phpDocumentor\Reflection\DocBlock\DescriptionFactory;
use phpDocumentor\Reflection\DocBlock\StandardTagFactory;
use phpDocumentor\Reflection\DocBlock\Tag;
use ReflectionClass;
use ReflectionException;
use phpDocumentor\Reflection\DocBlockFactory;
use phpDocumentor\Reflection\FqsenResolver;
use phpDocumentor\Reflection\TypeResolver;
use SilverLeague\IDEAnnotator\DataObjectAnnotator;
use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Extension;
use SilverStripe\Core\Injector\Injector;
use Generator;
use ReflectionClass;
use ReflectionException;
use SilverLeague\IDEAnnotator\Reflection\ShortNameResolver;

/**
* AbstractTagGenerator
Expand Down Expand Up @@ -43,6 +49,13 @@ abstract class AbstractTagGenerator
*/
protected $tags = [];

/**
* @var StandardTagFactory
*/
protected $tagFactory;

protected static $pageClassesCache = [];

/**
* DocBlockTagGenerator constructor.
*
Expand All @@ -57,6 +70,18 @@ public function __construct($className, $existingTags)
$this->reflector = new ReflectionClass($className);
$this->tags = $this->getSupportedTagTypes();

//Init the tag factory
if (DataObjectAnnotator::config()->get('use_short_name')) {
$fqsenResolver = new ShortNameResolver();
} else {
$fqsenResolver = new FqsenResolver();
}

$this->tagFactory = new StandardTagFactory($fqsenResolver);
$descriptionFactory = new DescriptionFactory($this->tagFactory);
$this->tagFactory->addService($descriptionFactory);
$this->tagFactory->addService(new TypeResolver($fqsenResolver));

$this->generateTags();
}

Expand Down Expand Up @@ -88,7 +113,7 @@ abstract protected function generateTags();
*/
public function getTags()
{
return (array)call_user_func_array('array_merge', $this->tags);
return (array)call_user_func_array('array_merge', array_values($this->tags));
}

/**
Expand Down Expand Up @@ -143,9 +168,10 @@ protected function pushMixinTag($tagString)
*/
protected function pushTagWithExistingComment($type, $tagString)
{
$tagString = sprintf('@%s %s', $type, $tagString);
$tagString .= $this->getExistingTagCommentByTagString($tagString);

return new Tag($type, $tagString);
return $this->tagFactory->create($tagString);
}

/**
Expand All @@ -155,7 +181,7 @@ protected function pushTagWithExistingComment($type, $tagString)
public function getExistingTagCommentByTagString($tagString)
{
foreach ($this->getExistingTags() as $tag) {
$content = $tag->getContent();
$content = $tag->__toString();
// A tag should be followed by a space before it's description
// This is to prevent `TestThing` and `Test` to be seen as the same, when the shorter
// is after the longer name
Expand Down Expand Up @@ -191,7 +217,7 @@ protected function generateOwnerTags()
if (Injector::inst()->get($this->className) instanceof Extension) {
$owners = iterator_to_array($this->getOwnerClasses($className));
$owners[] = $this->className;
$tagString = '\\' . implode("|\\", array_values($owners)) . ' $owner';
$tagString = sprintf('\\%s $owner', implode("|\\", array_values($owners)));
if (DataObjectAnnotator::config()->get('use_short_name')) {
foreach ($owners as $key => $owner) {
$owners[$key] = $this->getAnnotationClassName($owner);
Expand Down
48 changes: 44 additions & 4 deletions src/Generators/ControllerTagGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,27 @@

namespace SilverLeague\IDEAnnotator\Generators;

use ReflectionClass;
use ReflectionException;
use SilverStripe\CMS\Controllers\ContentController;
use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Config\Config;
use Page;
use ReflectionClass;

class ControllerTagGenerator extends AbstractTagGenerator
{
/**
* ControllerTagGenerator constructor.
*
* @param string $className
* @param $existingTags
* @throws ReflectionException
*/
public function __construct($className, $existingTags)
{
$this->mapPageTypesToControllerName();

parent::__construct($className, $existingTags);
}

/**
* @return void
Expand All @@ -32,8 +46,18 @@ protected function generateControllerObjectTags()
if (class_exists($pageClassname) && $this->isContentController($this->className)) {
$pageClassname = $this->getAnnotationClassName($pageClassname);

$this->pushPropertyTag($pageClassname . ' dataRecord');
$this->pushMethodTag('data()', $pageClassname . ' data()');
$this->pushPropertyTag(sprintf('%s dataRecord', $pageClassname));
$this->pushMethodTag('data()', sprintf('%s data()', $pageClassname));

// don't mixin Page, since this is a ContentController method
if ($pageClassname !== 'Page') {
$this->pushMixinTag($pageClassname);
}
} elseif ($this->isContentController($this->className) && array_key_exists($this->className, self::$pageClassesCache)) {
$pageClassname = $this->getAnnotationClassName(self::$pageClassesCache[$this->className]);

$this->pushPropertyTag(sprintf('%s dataRecord', $pageClassname));
$this->pushMethodTag('data()', sprintf('%s data()', $pageClassname));

// don't mixin Page, since this is a ContentController method
if ($pageClassname !== 'Page') {
Expand All @@ -54,4 +78,20 @@ protected function isContentController($className)
return ClassInfo::exists(ContentController::class)
&& $reflector->isSubclassOf(ContentController::class);
}

/**
* Generates the cache of Page types to Controllers when the controller_name config is used
*/
protected function mapPageTypesToControllerName()
{
if (empty(self::$pageClassesCache)) {
$pageClasses = ClassInfo::subclassesFor(Page::class);
foreach ($pageClasses as $pageClassname) {
$controllerName = Config::inst()->get($pageClassname, 'controller_name');
if (!empty($controllerName)) {
self::$pageClassesCache[$controllerName] = $pageClassname;
}
}
}
}
}
30 changes: 20 additions & 10 deletions src/Generators/DocBlockGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@

namespace SilverLeague\IDEAnnotator\Generators;

use InvalidArgumentException;
use LogicException;
use phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\DocBlock\Serializer;
use phpDocumentor\Reflection\DocBlock\Tag;
use phpDocumentor\Reflection\DocBlockFactory;
use SilverStripe\Control\Controller;
use InvalidArgumentException;
use LogicException;
use ReflectionClass;
use ReflectionException;
use SilverStripe\Control\Controller;

/**
* Class DocBlockGenerator
Expand All @@ -34,6 +35,11 @@ class DocBlockGenerator
*/
protected $tagGenerator;

/**
* @var DocBlockFactory
*/
protected $docBlockFactory;

/**
* DocBlockGenerator constructor.
*
Expand All @@ -45,6 +51,7 @@ public function __construct($className)
{
$this->className = $className;
$this->reflector = new ReflectionClass($className);
$this->docBlockFactory = DocBlockFactory::createInstance();

$generatorClass = $this->reflector->isSubclassOf(Controller::class)
? ControllerTagGenerator::class : OrmTagGenerator::class;
Expand All @@ -59,7 +66,11 @@ public function __construct($className)
public function getExistingTags()
{
$docBlock = $this->getExistingDocBlock();
$docBlock = new DocBlock($docBlock);
if (!$docBlock) {
return [];
}

$docBlock = $this->docBlockFactory->create($docBlock);

return $docBlock->getTags();
}
Expand Down Expand Up @@ -97,15 +108,14 @@ public function getGeneratedDocBlock()
*/
protected function mergeGeneratedTagsIntoDocBlock($existingDocBlock)
{
$docBlock = new DocBlock($this->removeExistingSupportedTags($existingDocBlock));
$docBlock = $this->docBlockFactory->create(($existingDocBlock ?: "/**\n*/"));
UndefinedOffset marked this conversation as resolved.
Show resolved Hide resolved

if (!$docBlock->getText()) {
$docBlock->setText('Class \\' . $this->className);
$summary = $docBlock->getSummary();
if (!$summary) {
$summary = sprintf('Class \\%s', $this->className);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like it might be here, something goes wrong and all classes are prefixed with a \

}

foreach ($this->getGeneratedTags() as $tag) {
$docBlock->appendTag($tag);
}
$docBlock = new DocBlock($summary, $docBlock->getDescription(), $this->getGeneratedTags());

$serializer = new Serializer();
$docBlock = $serializer->getDocComment($docBlock);
Expand Down
2 changes: 1 addition & 1 deletion src/Generators/OrmTagGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class OrmTagGenerator extends AbstractTagGenerator
*/
protected static $dbfield_tagnames = [
DBInt::class => 'int',
DBBoolean::class => 'boolean',
DBBoolean::class => 'bool',
DBFloat::class => 'float',
DBDecimal::class => 'float',
];
Expand Down
2 changes: 1 addition & 1 deletion src/Helpers/AnnotatePermissionChecker.php
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ public function classNameIsSupported($className)
*/
public function moduleIsAllowed($moduleName)
{
return in_array($moduleName, $this->enabledModules(), null);
return in_array($moduleName, $this->enabledModules(), false);
}

/**
Expand Down
19 changes: 19 additions & 0 deletions src/Reflection/ShortNameResolver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace SilverLeague\IDEAnnotator\Reflection;

use phpDocumentor\Reflection\Fqsen;
use phpDocumentor\Reflection\FqsenResolver;
use phpDocumentor\Reflection\Types\Context;

class ShortNameResolver extends FqsenResolver
{
public function resolve(string $fqsen, ?Context $context = null): Fqsen
{
if ($context === null) {
$context = new Context('');
}

return new Fqsen($fqsen);
}
}
4 changes: 2 additions & 2 deletions tests/unit/DataObjectAnnotatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ public function testInversePlayerRelationOfTeam()

$content = $this->annotator->getGeneratedFileContent(file_get_contents($filePath), Player::class);

$this->assertContains('@property boolean $IsRetired', $content);
$this->assertContains('@property bool $IsRetired', $content);
$this->assertContains('@property string $ShirtNumber', $content);
$this->assertContains('@property string $Shirt', $content);
$this->assertContains('@property int $FavouriteTeamID', $content);
Expand Down Expand Up @@ -277,7 +277,7 @@ public function testShortInversePlayerRelationOfTeam()

$content = $this->annotator->getGeneratedFileContent(file_get_contents($filePath), Player::class);

$this->assertContains('@property boolean $IsRetired', $content);
$this->assertContains('@property bool $IsRetired', $content);
$this->assertContains('@property string $ShirtNumber', $content);
$this->assertContains('@property int $FavouriteTeamID', $content);
$this->assertContains('@method Team FavouriteTeam()', $content);
Expand Down