-
Notifications
You must be signed in to change notification settings - Fork 42
Make StandaloneExtensionManagers configurable #55
Conversation
👎 from me. Standalone extension manager should not be extended and docs should be corrected not to suggest that. |
Rationale? |
I still oppose extending the class. It have numerous downsides.
I see only two valid options here and none of them is to extend:
final class MyExtensionManager implements ExtensionManagerInterface
{
private $decoratedManager;
private $extensions = [
'Media\Entry' = MyApp\RSS\Media\Entry::class,
];
public function __construct(ExtensionManagerInterface $em)
{
$this->decoratedManager = $em;
}
/**
* Do we have the extension?
*
* @param string $extension
* @return bool
*/
public function has($extension)
{
return (array_key_exists($extension, $this->extensions) || $this->decoratedManager->has($extension));
}
/**
* Retrieve the extension
*
* @param string $extension
* @return mixed
*/
public function get($extension)
{
if (array_key_exists($extension, $this->extensions)) {
$class = $this->extensions[$extension];
return new $class();
}
return $this->decoratedManager->get($extension);
}
} And of course there is always configurable container implementation for more complex needs. |
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:
I'm using a framework because it helps me do things with less effort; I want to add one extension, but I have zero interest in writing an extension manager - that's a role I would expect the framework to fulfil. |
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. If you need flexible configurable extension manager, that role is filled by As I said in previous comment, I will support expanding standalone responsibilities by making it configurable. Feel free to open PR for that. |
@@ -9,9 +9,9 @@ | |||
|
|||
namespace Zend\Feed\Reader; | |||
|
|||
class StandaloneExtensionManager implements ExtensionManagerInterface | |||
final class StandaloneExtensionManager implements ExtensionManagerInterface |
There was a problem hiding this comment.
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.
Can you also provide same change for Writer? |
OK. I've removed |
* Remove an extension. | ||
* | ||
* @param string $name | ||
* @return bool |
There was a problem hiding this comment.
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
I've noticed that the code style of the extension list is quite different in each. In Reader:
in Writer:
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. |
|
Hm. Looks like it fails to find classes using pseudoconstants:
I will switch back to strings. |
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, |
There was a problem hiding this comment.
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 to Zend\Feed\Reader\Extension\Atom\Entry
Can you also update docs? For this change only |
Fixing test data provider... |
namespace ZendTest\Feed\Writer; | ||
|
||
use PHPUnit\Framework\TestCase; | ||
use Zend\Feed\Reader\StandaloneExtensionManager; |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
*/ | ||
public function remove($name) | ||
{ | ||
if (array_key_exists($name, $this->extensions)) { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's because unset
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.
*/ | ||
public function add($name, $class) | ||
{ | ||
$this->extensions[$name] = $class; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 of Zend\Feed\Reader\Extension\AbstractEntry
or Zend\Feed\Reader\Extension\AbstractFeed
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
This check for Writer extensions makes little sense zend-feed/src/Writer/ExtensionPluginManager.php Lines 135 to 158 in 316e9b7
How should it be ported to standalone extension manager? |
All passing now. That OK? |
@@ -9,6 +9,8 @@ | |||
|
|||
namespace Zend\Feed\Writer; | |||
|
|||
use Zend\ServiceManager\Exception\InvalidServiceException; |
There was a problem hiding this comment.
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. Use Zend\Feed\Writer\Exception\InvalidArgumentException
@@ -9,22 +9,24 @@ | |||
|
|||
namespace Zend\Feed\Reader; | |||
|
|||
use Zend\ServiceManager\Exception\InvalidServiceException; |
There was a problem hiding this comment.
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. Use Zend\Feed\Reader\Exception\InvalidArgumentException
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
throw that other type
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I see why.
@@ -80,4 +85,13 @@ public function testEachPluginRetrievalReturnsNewInstance($pluginName, $pluginCl | |||
$this->assertInstanceOf($pluginClass, $test); | |||
$this->assertNotSame($extension, $test); | |||
} | |||
|
|||
public function testPluginAddRemove() |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 in add
is already checking for derived classes, because the class it checks for is abstract, so anything that is_a Extension\AbstractEntry
is by definition derived from it, so passing a further derivation wouldn't be testing anything further.
There was a problem hiding this comment.
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); | ||
} | ||
|
||
public function testPluginAddRemove() |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
Add exceptions to tests
Make StandaloneExtensionManagers configurable
Thanks, @Synchro |
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.