1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
<?php
declare(strict_types=1);
namespace Laminas\ServiceManager;
use Interop\Container\ContainerInterface;
use Laminas\ServiceManager\Exception\ContainerModificationsNotAllowedException;
use Laminas\ServiceManager\Exception\InvalidServiceException;
use Psr\Container\ContainerInterface as PsrContainerInterface;
use function class_exists;
use function get_class;
use function gettype;
use function is_object;
use function method_exists;
use function sprintf;
use function trigger_error;
use const E_USER_DEPRECATED;
/**
* Abstract plugin manager.
*
* Abstract PluginManagerInterface implementation providing:
*
* - creation context support. The constructor accepts the parent container
* instance, which is then used when creating instances.
* - plugin validation. Implementations may define the `$instanceOf` property
* to indicate what class types constitute valid plugins, omitting the
* requirement to define the `validate()` method.
*
* The implementation extends `ServiceManager`, thus providing the same set
* of capabilities as found in that implementation.
*
* @psalm-import-type ServiceManagerConfiguration from ServiceManager
* @psalm-suppress PropertyNotSetInConstructor
*/
abstract class AbstractPluginManager extends ServiceManager implements PluginManagerInterface
{
/**
* Whether or not to auto-add a FQCN as an invokable if it exists.
*
* @var bool
*/
protected $autoAddInvokableClass = true;
/**
* An object type that the created instance must be instanced of
*
* @var null|string
* @psalm-var null|class-string
*/
protected $instanceOf;
/**
* Sets the provided $parentLocator as the creation context for all
* factories; for $config, {@see \Laminas\ServiceManager\ServiceManager::configure()}
* for details on its accepted structure.
*
* @param null|ConfigInterface|ContainerInterface|PsrContainerInterface $configInstanceOrParentLocator
* @param array $config
* @psalm-param ServiceManagerConfiguration $config
*/
public function __construct($configInstanceOrParentLocator = null, array $config = [])
{
if (
$configInstanceOrParentLocator instanceof PsrContainerInterface
&& ! $configInstanceOrParentLocator instanceof ContainerInterface
) {
/**
* {@see \Laminas\ServiceManager\Factory\FactoryInterface} typehints
* against interop container and as such cannot accept non-interop
* psr container. Decorate it as interop.
*/
$configInstanceOrParentLocator = new PsrContainerDecorator($configInstanceOrParentLocator);
}
if (
null !== $configInstanceOrParentLocator
&& ! $configInstanceOrParentLocator instanceof ConfigInterface
&& ! $configInstanceOrParentLocator instanceof ContainerInterface
) {
throw new Exception\InvalidArgumentException(sprintf(
'%s expects a ConfigInterface or ContainerInterface instance as the first argument; received %s',
self::class,
is_object($configInstanceOrParentLocator)
? get_class($configInstanceOrParentLocator)
: gettype($configInstanceOrParentLocator)
));
}
if ($configInstanceOrParentLocator instanceof ConfigInterface) {
trigger_error(sprintf(
'Usage of %s as a constructor argument for %s is now deprecated',
ConfigInterface::class,
static::class
), E_USER_DEPRECATED);
$config = $configInstanceOrParentLocator->toArray();
}
parent::__construct($config);
if (! $configInstanceOrParentLocator instanceof ContainerInterface) {
trigger_error(sprintf(
'%s now expects a %s instance representing the parent container; please update your code',
__METHOD__,
ContainerInterface::class
), E_USER_DEPRECATED);
}
$this->creationContext = $configInstanceOrParentLocator instanceof ContainerInterface
? $configInstanceOrParentLocator
: $this;
}
/**
* Override configure() to validate service instances.
*
* @param array $config
* @psalm-param ServiceManagerConfiguration $config
* @return self
* @throws InvalidServiceException If an instance passed in the `services` configuration is invalid for the
* plugin manager.
* @throws ContainerModificationsNotAllowedException If the allow override flag has been toggled off, and a
* service instanceexists for a given service.
*/
public function configure(array $config)
{
if (isset($config['services'])) {
/** @psalm-suppress MixedAssignment */
foreach ($config['services'] as $service) {
$this->validate($service);
}
}
parent::configure($config);
return $this;
}
/**
* Override setService for additional plugin validation.
*
* {@inheritDoc}
*/
public function setService($name, $service)
{
$this->validate($service);
parent::setService($name, $service);
}
/**
* @param string $name Service name of plugin to retrieve.
* @param null|array<mixed> $options Options to use when creating the instance.
* @return mixed
* @throws Exception\ServiceNotFoundException If the manager does not have
* a service definition for the instance, and the service is not
* auto-invokable.
* @throws InvalidServiceException If the plugin created is invalid for the
* plugin context.
*/
public function get($name, ?array $options = null)
{
if (! $this->has($name)) {
if (! $this->autoAddInvokableClass || ! class_exists($name)) {
throw new Exception\ServiceNotFoundException(sprintf(
'A plugin by the name "%s" was not found in the plugin manager %s',
$name,
static::class
));
}
$this->setFactory($name, Factory\InvokableFactory::class);
}
/** @psalm-suppress MixedAssignment */
$instance = ! $options ? parent::get($name) : $this->build($name, $options);
$this->validate($instance);
return $instance;
}
/**
* {@inheritDoc}
*/
public function validate($instance)
{
if (method_exists($this, 'validatePlugin')) {
trigger_error(sprintf(
'%s::validatePlugin() has been deprecated as of 3.0; please define validate() instead',
static::class
), E_USER_DEPRECATED);
$this->validatePlugin($instance);
return;
}
if (empty($this->instanceOf) || $instance instanceof $this->instanceOf) {
return;
}
throw new InvalidServiceException(sprintf(
'Plugin manager "%s" expected an instance of type "%s", but "%s" was received',
self::class,
$this->instanceOf,
is_object($instance) ? get_class($instance) : gettype($instance)
));
}
/**
* Implemented for backwards compatibility only.
*
* Returns the creation context.
*
* @deprecated since 3.0.0. The creation context should be passed during
* instantiation instead.
*
* @return void
*/
public function setServiceLocator(ContainerInterface $container)
{
trigger_error(sprintf(
'Usage of %s is deprecated since v3.0.0; please pass the container to the constructor instead',
__METHOD__
), E_USER_DEPRECATED);
$this->creationContext = $container;
}
}