-
-
Notifications
You must be signed in to change notification settings - Fork 59
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
Remove AbstractPluginManager#validate
#182
Comments
I found these snippets: https://psalm.dev/r/d12af359e2<?php
namespace Laminas {
/**
* @template InstanceType
*/
interface PluginManagerInterface
{
/**
* @psalm-assert InstanceType $instance
* @throws \InvalidArgumentException In case the value does not match expectations.
*/
public function validate(mixed $instance): void;
/** @return InstanceType */
public function get(string $id): mixed;
}
/**
* @template InstanceType
* @template-implements PluginManagerInterface<InstanceType>
*/
abstract class AbstractPluginManager implements PluginManagerInterface
{
/**
* @var class-string<InstanceType>|null
*/
protected string|null $instanceOf = null;
/**
* @psalm-assert InstanceType $instance
*/
public function validate(mixed $instance): void
{
if ($this->instanceOf === null) {
return;
}
if ($instance instanceOf $this->instanceOf) {
return;
}
throw new \InvalidArgumentException('booyah');
}
/**
* @return InstanceType
*/
public function get(string $id): mixed
{
/** @var mixed $instance */
$instance = null; // instance from factory or whatever
$this->validate($instance);
return $instance;
}
}
}
namespace UpstreamProject {
use Laminas\AbstractPluginManager;
/**
* @extends AbstractPluginManager<int|string>
*/
final class MyWhateverPluginManager extends AbstractPluginManager
{
}
$plugins = new MyWhateverPluginManager();
/** @psalm-trace $plugin */
$plugin = $plugins->get('foo');
}
https://psalm.dev/r/9a75a42fda<?php
/** @template InstanceType */
interface PluginManagerInterface
{}
/**
* @template InstanceType of object
* @psalm-require-implements PluginManagerInterface
*/
trait Foo
{
/** @var class-string<InstanceType> */
protected string $instanceOf;
/** @psalm-assert InstanceType $instance */
public function validate(mixed $instance): void
{
}
}
/**
* @implements PluginManagerInterface<stdClass>
*/
final class Bar implements PluginManagerInterface
{
/** @use Foo<stdClass> */
use Foo;
protected string $instanceOf = stdClass::class;
}
|
Few things:
/** @template TItem */
final class ValidatedPluginManager {
/** @param callable(mixed): TItem $validator */
public function __construct(
private readonly ContainerInterface $c,
private readonly $validator
) {
}
/** @return TItem */
function get(string $name): mixed {
return ($this->validator)($this->c->get($name));
}
} Here's an example of the inference working: https://psalm.dev/r/be1a96c20b |
I found these snippets: https://psalm.dev/r/be1a96c20b<?php
interface ContainerInterface { function get(string $name): mixed; }
/** @template TItem */
final class ValidatedPluginManager {
/** @param callable(mixed): TItem $validator */
public function __construct(
private readonly ContainerInterface $c,
private readonly $validator
) {
}
/** @return TItem */
function get(string $name): mixed {
return ($this->validator)($this->c->get($name));
}
}
interface SomethingA {}
/** @var ContainerInterface $c */
$pm = new ValidatedPluginManager($c, function (mixed $v) { assert($v instanceof SomethingA); return $v; });
$service = $pm->get('stuff');
/** @psalm-trace $service */
return $service;
|
I already mentioned else where that this is not how pluginmanagers are used. They are fetched from the container and not manually instantiated in upstream. Can you please provide an example on how to use that ValidatedPluginManager while fetching it from the container? |
I would still go with the validator approach then: /** @template-extends BasePluginManager<Foo> */
class MyPluginManager extends BasePluginManager { // yes, this is still better than a trait, to have different class names
public function __construct($c) {
parent::__construct(
$c,
function (mixed $v): Foo {
assert($v instanceof Foo);
return $v;
}
);
}
} |
Injected validation callback adds complexity for no reason. Concrete plugin managers do not change what is allowed at runtime. They can implement method directly just fine. |
To be crystal-clear, I'm not looking forward to maintaining compatibility with the trait: the trait is an absolute no-go from a design perspective. |
Fine by me with remaining with |
The trait can be added to the documentation as a BC layer. I dont have a hard need for it to be shipped. But I also dont see hilarious maintenance effort here tbh, its one method and a property. |
If all we do here is removing |
The method is required by the Interface and with this proposal has to be added to the various plugin managers out there. I do not want to remove it, just want to drop the flawed implementation in the abstract plugin manager. |
I don't understand this issue then, sorry 😓 I suggest sending a patch directly, so perhaps it's clearer there? |
Can we outline the exact thing you are not understanding? Are we fine with removing the concrete method implementation for For all those plugin managers out there which do only provide a single instance of a specific interface, I wanted to add either a trait or an So once one of those plugin managers have to get migrated:
|
What if... class ViewHelperPluginManager extends AbstractPluginManager implements ViewHelperPluginManagerInterface
{
public function get(string $id): ViewHelperPlugin
{
return parent::get($id);
}
} |
Would work from a raw php perspective but psalm will tell you that mixed might be returned. the validate is to ensure that the return typ fits expectations for static analysis without the need to execute and handle runtime issues. |
I've created that Migration path would be to replace Every other plugin manager (i.e. those which do return callables) have to implement an own |
AbstractPluginManager#validate
AbstractPluginManager#validate
Closed by #179 |
RFC
Goal
Due to the current implementation, implementors of the
AbstractPluginManager
can easily "oversee" that they have to provide$instanceOf
property to provide any validation of their plugin manager. This leads to implementations with no validation at all:https://psalm.dev/r/d12af359e2
Background
Even tho, the current implementation within
AbstractPluginManager
also allows adding complex generics to their plugin manager without adding any validation. Might be even the case that implementors think that laminas got their back do the fact that there is already anAbstractPluginManager#validate
.Given the example of https://psalm.dev/r/d12af359e2, it is quite easy to implement a
PluginManager
which can returnmixed
while stating to only returnint|string
.Considerations
Even tho, that in
laminas-servicemanager
v3 thevalidatePlugin
method was removed (which had to be implemented in implementing classes) - for convenience - I'd require it to be implemented again with v4 while providing a trait which can be implemented in case that aPluginManager
did not need more than that.In order to have more type-stability, this change would be mandatory tho.
Proposal(s)
AbstractPluginManager#validate
andAbstractPluginManager#$instanceOf
SingleInstanceOfPluginManagerTrait
which provides that functionality so it can be re-implemented convenientlyAppendix
https://psalm.dev/r/9a75a42fda
The text was updated successfully, but these errors were encountered: