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

phpDocumentor\Reflection\Php\File not extendable #207

Closed
Chrico opened this issue Jul 22, 2021 · 3 comments
Closed

phpDocumentor\Reflection\Php\File not extendable #207

Chrico opened this issue Jul 22, 2021 · 3 comments

Comments

@Chrico
Copy link

Chrico commented Jul 22, 2021

Glossary

  • Strategies/Strategy = phpDocumentor\Reflection\Php\ProjectFactoryStrategy
  • Element(s) = phpDocumentor\Reflection\Element
  • ProjectFactory = phpDocumentor\Reflection\Php\ProjectFactory
  • File = phpDocumentor\Reflection\Php\File
  • ContextStack = phpDocumentor\Reflection\Php\Factory\ContextStack

Is your feature request related to a problem? Please describe.

I'd like to implement some custom Strategies and Element to parse out of a framework function-calls (similar to what phpDocumentor\Reflection\Php\Factory\Define does).

The ProjectFactory allows to add in its constructor Strategies. The Strategies are build based on the ProjectFactoryStrategy-interface. So it is possible to implement own ones.

class CustomStrategy extends Factory\AbstractFactory implements ProjectFactoryStrategy
{

    public function matches(ContextStack $context, object $object): bool
    {
        return true;
    }

    protected function doCreate(ContextStack $context, object $object, StrategyContainer $strategies): void
    {
        // do something
    }
}

and register them:

$strategies = new ProjectFactoryStrategies([new CustomStrategy()]);
$projectFactory = new ProjectFactory($strategies);

Inside of each Strategy it is possible to access the current File via $file = $context->peek(); in doCreate() via ContextStack. This File implementation is not extendable in many ways:

  1. The File-class is final.
  2. The class only restricts to Element implementation via add*-method (addTrait(), addInterface(), addClass(), ...).
  3. Those Element-implementations like phpDocumentor\Reflection\Php\Constant, phpDocumentor\Reflection\Php\Function_ are all final as well, so not extendable.

On ContextStack we also have no possibility to do anything to Project or File. You can access the Project via ContextStack::getProject() but not decorate/extend it and set back to the stack. Same goes for File.


Describe the solution you'd like

There are 2 options (plus 1 bonus) which come into my mind:

v1)

In 2. it could be possible to extend the phpDocumentor\Reflection\Php\File-class by 2 new methods:

/**
 * @param Element $element
 */ 
public function addCustomElement(Element $element): void
{
    $this->customElements[(string)$element->->getFqsen()] = $element;
}

/**
 * @return Element[]
 */ 
public function getCustomElements(): array 
{
    return $this->customElements;
}

This would allow to implement some custom Elements and set those in Strategies to the File to access them lateron via the Project::getFiles() again:

foreach($project->getFiles() as $file) {
     foreach($file->getCustomElements()() as $customElements) {
          // let's go.
     }
}

v2)

Remove from 3. the final from all classes which implement Element to allow to extend them. This way you could have:

class MyCustomFunction extends phpDocumentor\Reflection\Php\Function_
{
     public function setSomething(): void
     {
           // ..
     }
}

And set this via File::addFunction() in a Strategy to access it lateron via:

foreach($project->getFiles() as $file) {
     foreach($file->getFunctions() as $func) {
          if($func instanceof MyCustomFunction::class){
               // do something
          }
     }
}

v3)

And last but not least to cover my specific use case: Add similar to phpDocumentor\Reflection\Php\Factory\Define following new classes:

  • phpDocumentor\Reflection\Php\Factory\FunctionCall (a Strategy)
  • phpDocumentor\Reflection\Php\FunctionCall (an Element)

to collect all inline function calls in Files. This could be parsed by an own Strategy, so it could be removed/not added to avoid overhead and File could have new methods:

/**
 * @param FunctionCall$element
 */ 
public function addFunctionCall(FunctionCall $funcCall): void
{
    $this->funcCalls[(string)$funcCall->->getFqsen()] = $funcCall;
}

/**
 * @return funcCall[]
 */ 
public function getFunctionCalls(): array 
{
    return $this->funcCalls;
}

Access could be easy as all others are:

foreach($project->getFiles() as $file) {
     foreach($file->getFunctionCalls()() as $funcCalls) {
          // let's go.
     }
}
@jaapio
Copy link
Member

jaapio commented Aug 8, 2021

Hi,

Thanks for your request, I would like to discuss some options we have. The reason our model is final and not extendable is because we want to limit this library to just reflect the things we need in phpDocumentor. However, I see the options you could have when our model would be more open for extension.

Earlier we were thinking about some meta information which should contain some free format meta-data tree on each element. In your request, this could be a list of function calls done in a certain file. But in #90 the request was to add some other information to functions.

I see several options to add the meta information.

Add meta information to the model


In this example, meta can be literally anything. As long as the new Meta interface is used.

final class File
{
   public function addMeta(Meta $meta)
   {
   }
}

The addMeta will be introduced on all elements. This approach has one important done side. Since the Meta interface is a free format for the users of this library they will have to check everything once consuming. It would also break the option to cache the result. That why we are so strict about what can be added. We optimized this library for speed, and allow caching of the results. Any circular relations would break this.

Introduce metadata in ContextStack


The ContextStack is an object passed into each of the Strategies. Allowing users to inject their own information into the factories ContextStack would be a solution, which will never break this library, and allowing people to collect all information collect during the processing of the node tree.
Since you can always identify each element by it's unique name, files to have the filename and path, elements have the FQSEN.

This would allow you to create your own meta-data model, which is fully typed. Inject it into your own strategies. Wrap our strategies in your ect. It would never interfere with our changes, making your work detached from whatever we change in this library.

In this situation, we would need to introduce a ContextFactory which can be added to the ProjectFactory. The context will use this factory to reproduce itself, taking the carry with it.


I think the latter would be my preferred way to go. Of course, this would come with some proper documentation to help users to create their own metadata. Because it is most flexible and will never interfere with the purpose of this library. There will breaking changes in the strategies once php develops. It is not clear enough right now, but they are not part of our public interface. And should not be consumed by other projects. Since we have to be as close as possible to what php as language does.

I would like to know what you think of this. Thanks for your help!

@jaapio
Copy link
Member

jaapio commented Oct 11, 2021

I created a Metadata class to make is possible to extend the parser output. A first draft of the docs about this can be found here:

https://github.com/phpDocumentor/Reflection/blob/5.x/docs/meta-data.rst#metadata

I'm now testing it myself with a realworld project.

@jaapio
Copy link
Member

jaapio commented Nov 13, 2021

See #90 and phpDocumentor/phpDocumentor#3067

@jaapio jaapio closed this as completed Nov 13, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants