Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings
This repository was archived by the owner on Jan 30, 2020. It is now read-only.

Make StandaloneExtensionManagers configurable#55

Closed
Synchro wants to merge22 commits intozendframework:developfromSynchro:master

Conversation

@Synchro
Copy link
Contributor

This is a simple PR that fixes my request in#54. If you're happy with this, I will add changes to the docs as well, also fixing#44.

@Xerkus
Copy link
Member

👎 from me. Standalone extension manager should not be extended and docs should be corrected not to suggest that.

@Synchro
Copy link
ContributorAuthor

Rationale?

@weierophinney
Copy link
Member

@Xerkus I disagree. In this case, extension will allow users tonot need a container implementation in order to provide more extensions; they can instead extend the standalone implementation to add theirs in.

@Synchro — I'm re-opening and will review.

Synchro reacted with heart emoji

@Xerkus
Copy link
Member

I still oppose extending the class. It have numerous downsides.

  • extended class will not be updated along with baseStandaloneExtensionManager if it copied the property.
  • extended class that is changing behavior ofStandaloneExtensionManager should be separate implementation anyway as it will be a coupling of unrelated responsibilities.
  • extending limits our ability to change implementation details at will, which is an additional maintenance burden. Not as much of a problem in this simple isolated case but it is there.

I see only two valid options here and none of them is to extend:

  • introduce methods to configure extensions property to theStandaloneExtensionManager and forbid extension
  • extend functionality via decorator implementing the interface, as I was saying in the related Issue:
finalclass MyExtensionManagerimplements ExtensionManagerInterface{private$decoratedManager;private$extensions = ['Media\Entry' =MyApp\RSS\Media\Entry::class,    ];publicfunction__construct(ExtensionManagerInterface$em)    {$this->decoratedManager =$em;    }/**     * Do we have the extension?     *     * @param  string $extension     * @return bool     */publicfunctionhas($extension)    {return (array_key_exists($extension,$this->extensions) ||$this->decoratedManager->has($extension));    }/**     * Retrieve the extension     *     * @param  string $extension     * @return mixed     */publicfunctionget($extension)    {if (array_key_exists($extension,$this->extensions)) {$class =$this->extensions[$extension];returnnew$class();        }return$this->decoratedManager->get($extension);    }}

And of course there is always configurable container implementation for more complex needs.

@Synchro
Copy link
ContributorAuthor

All this seems to be massive overkill - something that calls itself a plugin manager singularly fails to do anything matching that description - needing to write a convoluted decorator at all smells like a workaround for an inflexible base class. If a provided extension manager doesn't manage extensions, what is it for? A simpler alternative to extend/decorate would be to add an "addExtension" method, so I could say:

$m = new StandalonePluginManager;$m->addExtension('Media/Entry', 'MyApp\RSS\Media\Entry');

I'm using a framework because it helps me do things with less effort; I want to add oneextension, but I have zero interest in writing anextension manager - that's a role I would expect the framework to fulfil.

@Xerkus
Copy link
Member

Extension manager is a contract established by the interface. Responsibility of the contract is to manage extensions for zend-feed, ie create, configure and provide.
StandaloneExtensionManager is a concrete implementation that is in no way obligated to provide anything beside said contract. Its responsibility is to make zend-feed work standalone without external dependency and it does exactly that.
NoopExtensionManager, for example, would do nothing, provide no extensions at all while still being a valid concrete implementation.

If you need flexible configurable extension manager, that role is filled byExtensionPluginManager. It is already written and supported.

As I said in previous comment, I will support expanding standalone responsibilities by making it configurable. Feel free to open PR for that.

namespaceZend\Feed\Reader;

class StandaloneExtensionManagerimplements ExtensionManagerInterface
finalclass StandaloneExtensionManagerimplements ExtensionManagerInterface
Copy link
Member

Choose a reason for hiding this comment

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

We can't make it final, it will require a major version bump as someone is quite likely already extending it.

@XerkusXerkus changed the base branch frommaster todevelopNovember 14, 2017 07:42
@Xerkus
Copy link
Member

Can you also provide same change for Writer?

@Synchro
Copy link
ContributorAuthor

OK. I've removedfinal. Are you happy with the general approach?

* Remove an extension.
*
* @param string $name
* @return bool
Copy link
Member

Choose a reason for hiding this comment

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

It does not need to report the result. It is idempotent operation. void would be fine

@Synchro
Copy link
ContributorAuthor

I've noticed that the code style of the extension list is quite different in each. In Reader:

    'Atom\Entry'            => 'Zend\Feed\Reader\Extension\Atom\Entry',

in Writer:

    'Atom\Renderer\Feed'           => Extension\Atom\Renderer\Feed::class,

They are consistent within themselves, but not with each other. It makes writing tests easier using the string style as I don't have to provide a mocked extension class - I know that older versions used the literal style, so I assume there's some history to that.

I noticed that the Writer doesn't have a test file for this, so I'm adding one.

@Xerkus
Copy link
Member

Xerkus commentedNov 14, 2017
edited
Loading

::class pseudoconstant is preferred. It is a special case, it does not require class to actually exist.
What it does is resolves class using namespace and local imports, returns string.
\SomeClass::class and'SomeClass' are exactly the same, except static analysis will warn of non-existent class in former and won't in latter.

@Synchro
Copy link
ContributorAuthor

Hm. Looks like it fails to find classes using pseudoconstants:

Fatal error: Class 'Zend\Feed\Reader\Zend\Feed\Reader\Extension\Podcast\Feed' not found in /home/travis/build/zendframework/zend-feed/src/Reader/StandaloneExtensionManager.php on line 50

I will switch back to strings.

@Synchro
Copy link
ContributorAuthor

Uh, I could always just un-break the fully namespaced names...

'Syndication\Feed' =>'Zend\Feed\Reader\Extension\Syndication\Feed',
'Thread\Entry' =>'Zend\Feed\Reader\Extension\Thread\Entry',
'WellFormedWeb\Entry' =>'Zend\Feed\Reader\Extension\WellFormedWeb\Entry',
'Atom\Entry' =>Zend\Feed\Reader\Extension\Atom\Entry::class,
Copy link
Member

Choose a reason for hiding this comment

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

It is resolved to FQCN using normal rules.
Extension\Atom\Entry::class will resolve properly toZend\Feed\Reader\Extension\Atom\Entry

@XerkusXerkus changed the titleMake $extensions property protectedMake StandaloneExtensionManagers configurableNov 14, 2017
@Xerkus
Copy link
Member

Xerkus commentedNov 14, 2017
edited
Loading

Can you also update docs? For this change only

@XerkusXerkus added this to the2.9.0 milestoneNov 14, 2017
@Synchro
Copy link
ContributorAuthor

Fixing test data provider...

namespaceZendTest\Feed\Writer;

usePHPUnit\Framework\TestCase;
useZend\Feed\Reader\StandaloneExtensionManager;
Copy link
Member

Choose a reason for hiding this comment

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

This is supposed to be Writer

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

Ah, my IDE had collapsed the use statements so I didn't see them. Fixed.

*/
publicfunctionremove($name)
{
if (array_key_exists($name,$this->extensions)) {
Copy link
Member

Choose a reason for hiding this comment

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

Any reason, why this check is removed?

Copy link
Member

Choose a reason for hiding this comment

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

it is not needed. unset will work the same and there is no distinction between removing instance and noop.

Copy link
Member

Choose a reason for hiding this comment

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

@Xerkus
You are right, it throws no error.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

It's becauseunset is a language construct, not a real function, and it doesn't care whether the thing you're unsetting exists or not. The check was only there before to set the return value.

*/
publicfunctionadd($name,$class)
{
$this->extensions[$name] =$class;
Copy link
Member

Choose a reason for hiding this comment

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

No validation what is added to the manager?

Copy link
Member

Choose a reason for hiding this comment

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

good point.$class should be checked to be instance ofZend\Feed\Reader\Extension\AbstractEntry orZend\Feed\Reader\Extension\AbstractFeed

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

I wondered about that - $class can be a string (it was in the components until I changed it), but it might be the name of something that doesn't exist yet, so I don't know that it can be validated at this point.

Copy link
Member

@XerkusXerkusNov 14, 2017
edited
Loading

Choose a reason for hiding this comment

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

with check it will be autoloaded when extension is registered. Plugin manager validates on creation.

@Xerkus
Copy link
Member

This check for Writer extensions makes little sense

publicfunctionvalidate($plugin)
{
if ($plugininstanceofExtension\AbstractRenderer) {
// we're okay
return;
}
if ('Feed' ==substr(get_class($plugin), -4)) {
// we're okay
return;
}
if ('Entry' ==substr(get_class($plugin), -5)) {
// we're okay
return;
}
thrownewInvalidServiceException(sprintf(
'Plugin of type %s is invalid; must implement %s\Extension\RendererInterface'
.'or the classname must end in "Feed" or "Entry"',
(is_object($plugin) ?get_class($plugin) :gettype($plugin)),
__NAMESPACE__
));
}

How should it be ported to standalone extension manager?

@Synchro
Copy link
ContributorAuthor

All passing now. That OK?


namespaceZend\Feed\Writer;

useZend\ServiceManager\Exception\InvalidServiceException;
Copy link
Member

Choose a reason for hiding this comment

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

You cannot use ServiceManager exceptions in here. UseZend\Feed\Writer\Exception\InvalidArgumentException


namespaceZend\Feed\Reader;

useZend\ServiceManager\Exception\InvalidServiceException;
Copy link
Member

Choose a reason for hiding this comment

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

You cannot use ServiceManager exceptions in here. UseZend\Feed\Reader\Exception\InvalidArgumentException

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

But that's the type that is thrown?

Copy link
Member

Choose a reason for hiding this comment

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

throw that other type

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

OK, I see why.

$this->assertNotSame($extension,$test);
}

publicfunctiontestPluginAddRemove()
Copy link
Member

Choose a reason for hiding this comment

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

You need additional tests for expected exception in case of invalid extension class or object of valid extension.

Copy link
Member

Choose a reason for hiding this comment

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

You need additional tests to ensure extensions derived from both allowed classes are accepted

Copy link
ContributorAuthor

Choose a reason for hiding this comment

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

I've added tests for the exceptions, but the check inadd is already checking for derived classes, because the class it checks for is abstract, so anything that is_aExtension\AbstractEntry is by definition derived from it, so passing a further derivation wouldn't be testing anything further.

Copy link
Member

Choose a reason for hiding this comment

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

Those should be separate tests with descriptive names for what you are testing for
testAddAcceptsValidExtensionClasses()
testAddRejectsInvalidExtensions()
testAddRejectsInstanceOfValidExtension()

That way we have intent of the test and its implementation details rather than just asserting that current code runs without errors. It makes sure that if code changes at a later stage no intent will be lost.

$this->assertNotSame($extension,$test);
}

publicfunctiontestPluginAddRemove()
Copy link
Member

Choose a reason for hiding this comment

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

Same here, additional tests for expected exception in case of invalid extension class or object of valid extension.

Copy link
Member

Choose a reason for hiding this comment

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

additional tests to ensure extensions derived from allowed class or with classnames ending with 'Feed' or 'Entry' are accepted

weierophinney added a commit that referenced this pull requestDec 4, 2017
Make StandaloneExtensionManagers configurable
weierophinney added a commit that referenced this pull requestDec 4, 2017
weierophinney added a commit that referenced this pull requestDec 4, 2017
@weierophinney
Copy link
Member

Thanks,@Synchro

Sign up for freeto subscribe to this conversation on GitHub. Already have an account?Sign in.

Reviewers

2 more reviewers

@froschdesignfroschdesignfroschdesign left review comments

@XerkusXerkusXerkus approved these changes

Reviewers whose approvals may not affect merge requirements

Assignees

No one assigned

Projects

None yet

Milestone

2.9.0

Development

Successfully merging this pull request may close these issues.

4 participants

@Synchro@Xerkus@weierophinney@froschdesign

[8]ページ先頭

©2009-2025 Movatter.jp