|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
/** |
|
4
|
|
|
* File containing the ConfigResolver class. |
|
5
|
|
|
* |
|
6
|
|
|
* @copyright Copyright (C) eZ Systems AS. All rights reserved. |
|
7
|
|
|
* @license For full copyright and license information view LICENSE file distributed with this source code. |
|
8
|
|
|
*/ |
|
9
|
|
|
namespace eZ\Bundle\EzPublishCoreBundle\DependencyInjection\Configuration; |
|
10
|
|
|
|
|
11
|
|
|
use eZ\Publish\Core\MVC\Symfony\Configuration\VersatileScopeInterface; |
|
12
|
|
|
use eZ\Publish\Core\MVC\Symfony\SiteAccess; |
|
13
|
|
|
use eZ\Publish\Core\MVC\Symfony\SiteAccess\SiteAccessAware; |
|
14
|
|
|
use eZ\Publish\Core\MVC\Exception\ParameterNotFoundException; |
|
15
|
|
|
use Symfony\Component\DependencyInjection\ContainerAwareInterface; |
|
16
|
|
|
use Symfony\Component\DependencyInjection\ContainerAwareTrait; |
|
17
|
|
|
use Symfony\Component\DependencyInjection\ContainerBuilder; |
|
18
|
|
|
|
|
19
|
|
|
/** |
|
20
|
|
|
* This class will help you get settings for a specific scope. |
|
21
|
|
|
* This is useful to get a setting for a specific siteaccess for example. |
|
22
|
|
|
* |
|
23
|
|
|
* It will check the different scopes available for a given namespace to find the appropriate parameter. |
|
24
|
|
|
* To work, the dynamic setting must comply internally to the following name format : "<namespace>.<scope>.parameter.name". |
|
25
|
|
|
* |
|
26
|
|
|
* - <namespace> is the namespace for your dynamic setting. Defaults to "ezsettings", but can be anything. |
|
27
|
|
|
* - <scope> is basically the siteaccess name you want your parameter value to apply to. |
|
28
|
|
|
* Can also be "global" for a global override. |
|
29
|
|
|
* Another scope is used internally: "default". This is the generic fallback. |
|
30
|
|
|
* |
|
31
|
|
|
* The resolve scope order is the following: |
|
32
|
|
|
* 1. "global" |
|
33
|
|
|
* 2. SiteAccess name |
|
34
|
|
|
* 3. "default" |
|
35
|
|
|
*/ |
|
36
|
|
|
class ConfigResolver implements VersatileScopeInterface, SiteAccessAware, ContainerAwareInterface |
|
37
|
|
|
{ |
|
38
|
|
|
use ContainerAwareTrait; |
|
39
|
|
|
|
|
40
|
|
|
const SCOPE_GLOBAL = 'global'; |
|
41
|
|
|
const SCOPE_DEFAULT = 'default'; |
|
42
|
|
|
|
|
43
|
|
|
const UNDEFINED_STRATEGY_EXCEPTION = 1; |
|
44
|
|
|
const UNDEFINED_STRATEGY_NULL = 2; |
|
45
|
|
|
|
|
46
|
|
|
/** |
|
47
|
|
|
* @var \eZ\Publish\Core\MVC\Symfony\SiteAccess |
|
48
|
|
|
*/ |
|
49
|
|
|
protected $siteAccess; |
|
50
|
|
|
|
|
51
|
|
|
/** |
|
52
|
|
|
* @var array Siteaccess groups, indexed by siteaccess name |
|
53
|
|
|
*/ |
|
54
|
|
|
protected $groupsBySiteAccess; |
|
55
|
|
|
|
|
56
|
|
|
/** |
|
57
|
|
|
* @var string |
|
58
|
|
|
*/ |
|
59
|
|
|
protected $defaultNamespace; |
|
60
|
|
|
|
|
61
|
|
|
/** |
|
62
|
|
|
* @var string |
|
63
|
|
|
*/ |
|
64
|
|
|
protected $defaultScope; |
|
65
|
|
|
|
|
66
|
|
|
/** |
|
67
|
|
|
* @var int |
|
68
|
|
|
*/ |
|
69
|
|
|
protected $undefinedStrategy; |
|
70
|
|
|
|
|
71
|
|
|
/** |
|
72
|
|
|
* @var string[] List of param => [services] loaded while siteAccess->matchingType was 'uninitialized' |
|
73
|
|
|
*/ |
|
74
|
|
|
private $tooEarlyLoadedList = []; |
|
75
|
|
|
|
|
76
|
|
|
/** |
|
77
|
|
|
* @param array $groupsBySiteAccess SiteAccess groups, indexed by siteaccess. |
|
78
|
|
|
* @param string $defaultNamespace The default namespace |
|
79
|
|
|
* @param int $undefinedStrategy Strategy to use when encountering undefined parameters. |
|
80
|
|
|
* Must be one of |
|
81
|
|
|
* - ConfigResolver::UNDEFINED_STRATEGY_EXCEPTION (throw an exception) |
|
82
|
|
|
* - ConfigResolver::UNDEFINED_STRATEGY_NULL (return null) |
|
83
|
|
|
*/ |
|
84
|
|
|
public function __construct( |
|
85
|
|
|
array $groupsBySiteAccess, |
|
86
|
|
|
$defaultNamespace, |
|
87
|
|
|
$undefinedStrategy = self::UNDEFINED_STRATEGY_EXCEPTION |
|
88
|
|
|
) { |
|
89
|
|
|
$this->groupsBySiteAccess = $groupsBySiteAccess; |
|
90
|
|
|
$this->defaultNamespace = $defaultNamespace; |
|
91
|
|
|
$this->undefinedStrategy = $undefinedStrategy; |
|
92
|
|
|
} |
|
93
|
|
|
|
|
94
|
|
|
public function setSiteAccess(SiteAccess $siteAccess = null) |
|
95
|
|
|
{ |
|
96
|
|
|
$this->siteAccess = $siteAccess; |
|
97
|
|
|
} |
|
98
|
|
|
|
|
99
|
|
|
/** |
|
100
|
|
|
* Sets the strategy to use if an undefined parameter is being asked. |
|
101
|
|
|
* Can be one of: |
|
102
|
|
|
* - ConfigResolver::UNDEFINED_STRATEGY_EXCEPTION (throw an exception) |
|
103
|
|
|
* - ConfigResolver::UNDEFINED_STRATEGY_NULL (return null). |
|
104
|
|
|
* |
|
105
|
|
|
* Defaults to ConfigResolver::UNDEFINED_STRATEGY_EXCEPTION. |
|
106
|
|
|
* |
|
107
|
|
|
* @param int $undefinedStrategy |
|
108
|
|
|
*/ |
|
109
|
|
|
public function setUndefinedStrategy($undefinedStrategy) |
|
110
|
|
|
{ |
|
111
|
|
|
$this->undefinedStrategy = $undefinedStrategy; |
|
112
|
|
|
} |
|
113
|
|
|
|
|
114
|
|
|
/** |
|
115
|
|
|
* @return int |
|
116
|
|
|
*/ |
|
117
|
|
|
public function getUndefinedStrategy() |
|
118
|
|
|
{ |
|
119
|
|
|
return $this->undefinedStrategy; |
|
120
|
|
|
} |
|
121
|
|
|
|
|
122
|
|
|
/** |
|
123
|
|
|
* Checks if $paramName exists in $namespace. |
|
124
|
|
|
* |
|
125
|
|
|
* @param string $paramName |
|
126
|
|
|
* @param string $namespace If null, the default namespace should be used. |
|
127
|
|
|
* @param string $scope The scope you need $paramName value for. It's typically the siteaccess name. |
|
128
|
|
|
* If null, the current siteaccess name will be used. |
|
129
|
|
|
* |
|
130
|
|
|
* @return bool |
|
131
|
|
|
*/ |
|
132
|
|
|
public function hasParameter($paramName, $namespace = null, $scope = null) |
|
133
|
|
|
{ |
|
134
|
|
|
$namespace = $namespace ?: $this->defaultNamespace; |
|
135
|
|
|
$scope = $scope ?: $this->getDefaultScope(); |
|
136
|
|
|
|
|
137
|
|
|
$defaultScopeParamName = "$namespace." . self::SCOPE_DEFAULT . ".$paramName"; |
|
138
|
|
|
$globalScopeParamName = "$namespace." . self::SCOPE_GLOBAL . ".$paramName"; |
|
139
|
|
|
$relativeScopeParamName = "$namespace.$scope.$paramName"; |
|
140
|
|
|
|
|
141
|
|
|
// Relative scope, siteaccess group wise |
|
142
|
|
|
$groupScopeHasParam = false; |
|
143
|
|
|
if (isset($this->groupsBySiteAccess[$scope])) { |
|
144
|
|
View Code Duplication |
foreach ($this->groupsBySiteAccess[$scope] as $groupName) { |
|
|
|
|
|
|
145
|
|
|
$groupScopeParamName = "$namespace.$groupName.$paramName"; |
|
146
|
|
|
if ($this->container->hasParameter($groupScopeParamName)) { |
|
147
|
|
|
$groupScopeHasParam = true; |
|
148
|
|
|
break; |
|
149
|
|
|
} |
|
150
|
|
|
} |
|
151
|
|
|
} |
|
152
|
|
|
|
|
153
|
|
|
return |
|
154
|
|
|
$this->container->hasParameter($defaultScopeParamName) |
|
155
|
|
|
|| $groupScopeHasParam |
|
156
|
|
|
|| $this->container->hasParameter($relativeScopeParamName) |
|
157
|
|
|
|| $this->container->hasParameter($globalScopeParamName); |
|
158
|
|
|
} |
|
159
|
|
|
|
|
160
|
|
|
/** |
|
161
|
|
|
* Returns value for $paramName, in $namespace. |
|
162
|
|
|
* |
|
163
|
|
|
* @param string $paramName The parameter name, without $prefix and the current scope (i.e. siteaccess name). |
|
164
|
|
|
* @param string $namespace Namespace for the parameter name. If null, the default namespace will be used. |
|
165
|
|
|
* @param string $scope The scope you need $paramName value for. It's typically the siteaccess name. |
|
166
|
|
|
* If null, the current siteaccess name will be used. |
|
167
|
|
|
* |
|
168
|
|
|
* @throws \eZ\Publish\Core\MVC\Exception\ParameterNotFoundException |
|
169
|
|
|
* |
|
170
|
|
|
* @return mixed |
|
171
|
|
|
*/ |
|
172
|
|
|
public function getParameter($paramName, $namespace = null, $scope = null) |
|
173
|
|
|
{ |
|
174
|
|
|
if (!$this->container instanceof ContainerBuilder && $this->siteAccess->matchingType === 'uninitialized') { |
|
175
|
|
|
$this->tooEarlyLoadedList[$paramName][] = $this->extractServiceName(); |
|
176
|
|
|
} |
|
177
|
|
|
|
|
178
|
|
|
$namespace = $namespace ?: $this->defaultNamespace; |
|
179
|
|
|
$scope = $scope ?: $this->getDefaultScope(); |
|
180
|
|
|
$triedScopes = array(); |
|
181
|
|
|
|
|
182
|
|
|
// Global scope |
|
183
|
|
|
$globalScopeParamName = "$namespace." . self::SCOPE_GLOBAL . ".$paramName"; |
|
184
|
|
|
if ($this->container->hasParameter($globalScopeParamName)) { |
|
185
|
|
|
return $this->container->getParameter($globalScopeParamName); |
|
186
|
|
|
} |
|
187
|
|
|
$triedScopes[] = self::SCOPE_GLOBAL; |
|
188
|
|
|
unset($globalScopeParamName); |
|
189
|
|
|
|
|
190
|
|
|
// Relative scope, siteaccess wise |
|
191
|
|
|
$relativeScopeParamName = "$namespace.$scope.$paramName"; |
|
192
|
|
|
if ($this->container->hasParameter($relativeScopeParamName)) { |
|
193
|
|
|
return $this->container->getParameter($relativeScopeParamName); |
|
194
|
|
|
} |
|
195
|
|
|
$triedScopes[] = $scope; |
|
196
|
|
|
unset($relativeScopeParamName); |
|
197
|
|
|
|
|
198
|
|
|
// Relative scope, siteaccess group wise |
|
199
|
|
|
if (isset($this->groupsBySiteAccess[$scope])) { |
|
200
|
|
View Code Duplication |
foreach ($this->groupsBySiteAccess[$scope] as $groupName) { |
|
|
|
|
|
|
201
|
|
|
$relativeScopeParamName = "$namespace.$groupName.$paramName"; |
|
202
|
|
|
if ($this->container->hasParameter($relativeScopeParamName)) { |
|
203
|
|
|
return $this->container->getParameter($relativeScopeParamName); |
|
204
|
|
|
} |
|
205
|
|
|
} |
|
206
|
|
|
} |
|
207
|
|
|
|
|
208
|
|
|
// Default scope |
|
209
|
|
|
$defaultScopeParamName = "$namespace." . self::SCOPE_DEFAULT . ".$paramName"; |
|
210
|
|
|
if ($this->container->hasParameter($defaultScopeParamName)) { |
|
211
|
|
|
return $this->container->getParameter($defaultScopeParamName); |
|
212
|
|
|
} |
|
213
|
|
|
$triedScopes[] = $this->defaultNamespace; |
|
214
|
|
|
unset($defaultScopeParamName); |
|
215
|
|
|
|
|
216
|
|
|
// Undefined parameter |
|
217
|
|
|
switch ($this->undefinedStrategy) { |
|
218
|
|
|
case self::UNDEFINED_STRATEGY_NULL: |
|
219
|
|
|
return null; |
|
220
|
|
|
|
|
221
|
|
|
case self::UNDEFINED_STRATEGY_EXCEPTION: |
|
222
|
|
|
default: |
|
223
|
|
|
throw new ParameterNotFoundException($paramName, $namespace, $triedScopes); |
|
224
|
|
|
} |
|
225
|
|
|
} |
|
226
|
|
|
|
|
227
|
|
|
/** |
|
228
|
|
|
* Try to extract service name that asked for a parameter using debug_backtrace(). |
|
229
|
|
|
* |
|
230
|
|
|
* @return string |
|
231
|
|
|
*/ |
|
232
|
|
|
private function extractServiceName() |
|
233
|
|
|
{ |
|
234
|
|
|
foreach (debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10) as $t) { |
|
235
|
|
|
if (!isset($t['function']) || $t['function'] === 'getParameter' || $t['function'] === __FUNCTION__) { |
|
236
|
|
|
continue; |
|
237
|
|
|
} |
|
238
|
|
|
|
|
239
|
|
|
// Extract service name from first service matching getXXService pattern |
|
240
|
|
|
// We can only reverse engineer traditional service name, namspace is stripped from class name based services |
|
241
|
|
|
if (\strpos($t['function'], 'get') === 0 && \strpos($t['function'], 'Service') === \strlen($t['function']) -7) { |
|
242
|
|
|
$serviceName = \strtolower(\preg_replace('/\B([A-Z])/', '_$1', \str_replace('_', '.', \substr($t['function'], 3, -7)))); |
|
243
|
|
|
if ($this->container->has($serviceName)) { |
|
244
|
|
|
return $serviceName; |
|
245
|
|
|
} |
|
246
|
|
|
|
|
247
|
|
|
return '->' . $t['function'] . '()'; |
|
248
|
|
|
} |
|
249
|
|
|
} |
|
250
|
|
|
|
|
251
|
|
|
return '??'; |
|
252
|
|
|
} |
|
253
|
|
|
|
|
254
|
|
|
/** |
|
255
|
|
|
* Changes the default namespace to look parameter into. |
|
256
|
|
|
* |
|
257
|
|
|
* @param string $defaultNamespace |
|
258
|
|
|
*/ |
|
259
|
|
|
public function setDefaultNamespace($defaultNamespace) |
|
260
|
|
|
{ |
|
261
|
|
|
$this->defaultNamespace = $defaultNamespace; |
|
262
|
|
|
} |
|
263
|
|
|
|
|
264
|
|
|
/** |
|
265
|
|
|
* @return string |
|
266
|
|
|
*/ |
|
267
|
|
|
public function getDefaultNamespace() |
|
268
|
|
|
{ |
|
269
|
|
|
return $this->defaultNamespace; |
|
270
|
|
|
} |
|
271
|
|
|
|
|
272
|
|
|
public function getDefaultScope() |
|
273
|
|
|
{ |
|
274
|
|
|
return $this->defaultScope ?: $this->siteAccess->name; |
|
275
|
|
|
} |
|
276
|
|
|
|
|
277
|
|
|
public function setDefaultScope($scope) |
|
278
|
|
|
{ |
|
279
|
|
|
$this->defaultScope = $scope; |
|
280
|
|
|
|
|
281
|
|
|
// On scope change check if siteaccess has been updated so we can log warnings if there are any |
|
282
|
|
|
if ($this->siteAccess->matchingType !== 'uninitialized') { |
|
283
|
|
|
$this->logTooEarlyLoadedParams(); |
|
284
|
|
|
} |
|
285
|
|
|
} |
|
286
|
|
|
|
|
287
|
|
|
private function logTooEarlyLoadedParams() |
|
288
|
|
|
{ |
|
289
|
|
|
if (empty($this->tooEarlyLoadedList)) { |
|
290
|
|
|
return; |
|
291
|
|
|
} |
|
292
|
|
|
|
|
293
|
|
|
$logger = $this->container->get('logger'); |
|
294
|
|
|
foreach ($this->tooEarlyLoadedList as $param => $services) { |
|
295
|
|
|
// Ideally we we would want to skip warnings for services that use dynamic settings on setters as that means |
|
296
|
|
|
// paramter will get update on scope change, but we don't have a way to detect that here. |
|
297
|
|
|
$logger->warning(sprintf( |
|
298
|
|
|
'ConfigResolver was used to load parameter "%s" before SiteAccess was loaded by the following services: %s. This should be avoided; ' |
|
299
|
|
|
. 'first try to use ConfigResolver lazily in these services instead of "$dynamic_paramter$" injection, ' |
|
300
|
|
|
. (PHP_SAPI == 'cli' ? 'if not possible make sure your commands that rely on them are lazy loaded, ' : '') |
|
301
|
|
|
. 'if nothing else helps try to mark service as lazy.', |
|
302
|
|
|
$param, |
|
303
|
|
|
'"' . implode($services, '", "') . '"' |
|
304
|
|
|
)); |
|
305
|
|
|
} |
|
306
|
|
|
|
|
307
|
|
|
$this->tooEarlyLoadedList = []; |
|
308
|
|
|
} |
|
309
|
|
|
} |
|
310
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.