Skip to content

Commit ec1e7ca

Browse files
committed
[DependencyInjection] added a way to automatically update scoped services
A service can now be marked as synchronized; when set, all method calls involving this service will be called each time this service is set. When in a scope, methods are also called to restore the previous version of the service.
1 parent 469330d commit ec1e7ca

22 files changed

+366
-24
lines changed

src/Symfony/Component/DependencyInjection/Container.php

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,10 @@ public function set($id, $service, $scope = self::SCOPE_CONTAINER)
206206
}
207207

208208
$this->services[$id] = $service;
209+
210+
if (method_exists($this, $method = 'synchronize'.strtr($id, array('_' => '', '.' => '_')).'Service')) {
211+
$this->$method();
212+
}
209213
}
210214

211215
/**
@@ -221,7 +225,7 @@ public function has($id)
221225
{
222226
$id = strtolower($id);
223227

224-
return isset($this->services[$id]) || method_exists($this, 'get'.strtr($id, array('_' => '', '.' => '_')).'Service');
228+
return array_key_exists($id, $this->services) || method_exists($this, 'get'.strtr($id, array('_' => '', '.' => '_')).'Service');
225229
}
226230

227231
/**
@@ -247,7 +251,7 @@ public function get($id, $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE
247251
{
248252
$id = strtolower($id);
249253

250-
if (isset($this->services[$id])) {
254+
if (array_key_exists($id, $this->services)) {
251255
return $this->services[$id];
252256
}
253257

@@ -263,7 +267,7 @@ public function get($id, $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE
263267
} catch (\Exception $e) {
264268
unset($this->loading[$id]);
265269

266-
if (isset($this->services[$id])) {
270+
if (array_key_exists($id, $this->services)) {
267271
unset($this->services[$id]);
268272
}
269273

@@ -289,7 +293,7 @@ public function get($id, $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE
289293
*/
290294
public function initialized($id)
291295
{
292-
return isset($this->services[strtolower($id)]);
296+
return array_key_exists(strtolower($id), $this->services);
293297
}
294298

295299
/**
@@ -393,8 +397,11 @@ public function leaveScope($name)
393397
$services = $this->scopeStacks[$name]->pop();
394398
$this->scopedServices += $services;
395399

396-
array_unshift($services, $this->services);
397-
$this->services = call_user_func_array('array_merge', $services);
400+
foreach ($services as $array) {
401+
foreach ($array as $id => $service) {
402+
$this->set($id, $service, $name);
403+
}
404+
}
398405
}
399406
}
400407

src/Symfony/Component/DependencyInjection/ContainerBuilder.php

Lines changed: 60 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,11 @@ class ContainerBuilder extends Container implements TaggedContainerInterface
4646
*/
4747
private $definitions = array();
4848

49+
/**
50+
* @var Definition[]
51+
*/
52+
private $obsoleteDefinitions = array();
53+
4954
/**
5055
* @var Alias[]
5156
*/
@@ -351,14 +356,28 @@ public function set($id, $service, $scope = self::SCOPE_CONTAINER)
351356

352357
if ($this->isFrozen()) {
353358
// setting a synthetic service on a frozen container is alright
354-
if (!isset($this->definitions[$id]) || !$this->definitions[$id]->isSynthetic()) {
359+
if (
360+
(!isset($this->definitions[$id]) && !isset($this->obsoleteDefinitions[$id]))
361+
||
362+
(isset($this->definitions[$id]) && !$this->definitions[$id]->isSynthetic())
363+
||
364+
(isset($this->obsoleteDefinitions[$id]) && !$this->obsoleteDefinitions[$id]->isSynthetic())
365+
) {
355366
throw new BadMethodCallException(sprintf('Setting service "%s" on a frozen container is not allowed.', $id));
356367
}
357368
}
358369

370+
if (isset($this->definitions[$id])) {
371+
$this->obsoleteDefinitions[$id] = $this->definitions[$id];
372+
}
373+
359374
unset($this->definitions[$id], $this->aliases[$id]);
360375

361376
parent::set($id, $service, $scope);
377+
378+
if (isset($this->obsoleteDefinitions[$id]) && $this->obsoleteDefinitions[$id]->isSynchronized()) {
379+
$this->synchronize($id);
380+
}
362381
}
363382

364383
/**
@@ -885,19 +904,7 @@ private function createService(Definition $definition, $id)
885904
}
886905

887906
foreach ($definition->getMethodCalls() as $call) {
888-
$services = self::getServiceConditionals($call[1]);
889-
890-
$ok = true;
891-
foreach ($services as $s) {
892-
if (!$this->has($s)) {
893-
$ok = false;
894-
break;
895-
}
896-
}
897-
898-
if ($ok) {
899-
call_user_func_array(array($service, $call[0]), $this->resolveServices($parameterBag->resolveValue($call[1])));
900-
}
907+
$this->callMethod($service, $call);
901908
}
902909

903910
$properties = $this->resolveServices($parameterBag->resolveValue($definition->getProperties()));
@@ -999,4 +1006,43 @@ public static function getServiceConditionals($value)
9991006

10001007
return $services;
10011008
}
1009+
1010+
/**
1011+
* Synchronizes a service change.
1012+
*
1013+
* This method updates all services that depend on the given
1014+
* service by calling all methods referencing it.
1015+
*
1016+
* @param string $id A service id
1017+
*/
1018+
private function synchronize($id)
1019+
{
1020+
foreach ($this->definitions as $definitionId => $definition) {
1021+
// only check initialized services
1022+
if (!$this->initialized($definitionId)) {
1023+
continue;
1024+
}
1025+
1026+
foreach ($definition->getMethodCalls() as $call) {
1027+
foreach ($call[1] as $argument) {
1028+
if ($argument instanceof Reference && $id == (string) $argument) {
1029+
$this->callMethod($this->get($definitionId), $call);
1030+
}
1031+
}
1032+
}
1033+
}
1034+
}
1035+
1036+
private function callMethod($service, $call)
1037+
{
1038+
$services = self::getServiceConditionals($call[1]);
1039+
1040+
foreach ($services as $s) {
1041+
if (!$this->has($s)) {
1042+
return;
1043+
}
1044+
}
1045+
1046+
call_user_func_array(array($service, $call[0]), $this->resolveServices($this->getParameterBag()->resolveValue($call[1])));
1047+
}
10021048
}

src/Symfony/Component/DependencyInjection/Definition.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class Definition
3636
private $public;
3737
private $synthetic;
3838
private $abstract;
39+
private $synchronized;
3940

4041
protected $arguments;
4142

@@ -56,6 +57,7 @@ public function __construct($class = null, array $arguments = array())
5657
$this->tags = array();
5758
$this->public = true;
5859
$this->synthetic = false;
60+
$this->synchronized = false;
5961
$this->abstract = false;
6062
$this->properties = array();
6163
}
@@ -569,6 +571,34 @@ public function isPublic()
569571
return $this->public;
570572
}
571573

574+
/**
575+
* Sets the synchronized flag of this service.
576+
*
577+
* @param Boolean $boolean
578+
*
579+
* @return Definition The current instance
580+
*
581+
* @api
582+
*/
583+
public function setSynchronized($boolean)
584+
{
585+
$this->synchronized = (Boolean) $boolean;
586+
587+
return $this;
588+
}
589+
590+
/**
591+
* Whether this service is synchronized.
592+
*
593+
* @return Boolean
594+
*
595+
* @api
596+
*/
597+
public function isSynchronized()
598+
{
599+
return $this->synchronized;
600+
}
601+
572602
/**
573603
* Sets whether this definition is synthetic, that is not constructed by the
574604
* container, but dynamically injected.

src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -567,7 +567,7 @@ protected function get{$name}Service()
567567
*/
568568
private function addServices()
569569
{
570-
$publicServices = $privateServices = $aliasServices = '';
570+
$publicServices = $privateServices = $aliasServices = $synchronizers = '';
571571
$definitions = $this->container->getDefinitions();
572572
ksort($definitions);
573573
foreach ($definitions as $id => $definition) {
@@ -576,6 +576,8 @@ private function addServices()
576576
} else {
577577
$privateServices .= $this->addService($id, $definition);
578578
}
579+
580+
$synchronizers .= $this->addServiceSynchronizer($id, $definition);
579581
}
580582

581583
$aliases = $this->container->getAliases();
@@ -584,7 +586,60 @@ private function addServices()
584586
$aliasServices .= $this->addServiceAlias($alias, $id);
585587
}
586588

587-
return $publicServices.$aliasServices.$privateServices;
589+
return $publicServices.$aliasServices.$synchronizers.$privateServices;
590+
}
591+
592+
/**
593+
* Adds synchronizer methods.
594+
*
595+
* @param string $id A service identifier
596+
* @param Definition $definition A Definition instance
597+
*/
598+
private function addServiceSynchronizer($id, Definition $definition)
599+
{
600+
if (!$definition->isSynchronized()) {
601+
return;
602+
}
603+
604+
$code = '';
605+
foreach ($this->container->getDefinitions() as $definitionId => $definition) {
606+
foreach ($definition->getMethodCalls() as $call) {
607+
foreach ($call[1] as $argument) {
608+
if ($argument instanceof Reference && $id == (string) $argument) {
609+
$arguments = array();
610+
foreach ($call[1] as $value) {
611+
$arguments[] = $this->dumpValue($value);
612+
}
613+
614+
$call = $this->wrapServiceConditionals($call[1], sprintf("\$this->get('%s')->%s(%s);", $definitionId, $call[0], implode(', ', $arguments)));
615+
616+
$code .= <<<EOF
617+
if (\$this->initialized('$definitionId')) {
618+
$call
619+
}
620+
621+
EOF;
622+
}
623+
}
624+
}
625+
}
626+
627+
if (!$code) {
628+
return;
629+
}
630+
631+
$name = Container::camelize($id);
632+
633+
return <<<EOF
634+
635+
/**
636+
* Updates the '$id' service.
637+
*/
638+
protected function synchronize{$name}Service()
639+
{
640+
$code }
641+
642+
EOF;
588643
}
589644

590645
private function addNewInstance($id, Definition $definition, $return, $instantiation)

src/Symfony/Component/DependencyInjection/Dumper/XmlDumper.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,12 @@ private function addService($definition, $id, \DOMElement $parent)
127127
if (!$definition->isPublic()) {
128128
$service->setAttribute('public', 'false');
129129
}
130+
if ($definition->isSynthetic()) {
131+
$service->setAttribute('synthetic', 'true');
132+
}
133+
if ($definition->isSynchronized()) {
134+
$service->setAttribute('synchronized', 'true');
135+
}
130136

131137
foreach ($definition->getTags() as $name => $tags) {
132138
foreach ($tags as $attributes) {
@@ -239,6 +245,9 @@ private function convertParameters($parameters, $type, \DOMElement $parent, $key
239245
} elseif ($behaviour == ContainerInterface::IGNORE_ON_INVALID_REFERENCE) {
240246
$element->setAttribute('on-invalid', 'ignore');
241247
}
248+
if (!$value->isStrict()) {
249+
$element->setAttribute('strict', 'false');
250+
}
242251
} elseif ($value instanceof Definition) {
243252
$element->setAttribute('type', 'service');
244253
$this->addService($value, null, $element);

src/Symfony/Component/DependencyInjection/Dumper/YamlDumper.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,14 @@ private function addService($id, $definition)
9494
$code .= sprintf(" file: %s\n", $definition->getFile());
9595
}
9696

97+
if ($definition->isSynthetic()) {
98+
$code .= sprintf(" synthetic: true\n");
99+
}
100+
101+
if ($definition->isSynchronized()) {
102+
$code .= sprintf(" synchronized: true\n");
103+
}
104+
97105
if ($definition->getFactoryMethod()) {
98106
$code .= sprintf(" factory_method: %s\n", $definition->getFactoryMethod());
99107
}

src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ private function parseDefinition($id, $service, $file)
148148
$definition = new Definition();
149149
}
150150

151-
foreach (array('class', 'scope', 'public', 'factory-class', 'factory-method', 'factory-service', 'synthetic', 'abstract') as $key) {
151+
foreach (array('class', 'scope', 'public', 'factory-class', 'factory-method', 'factory-service', 'synthetic', 'synchronized', 'abstract') as $key) {
152152
if (isset($service[$key])) {
153153
$method = 'set'.str_replace('-', '', $key);
154154
$definition->$method((string) $service->getAttributeAsPhp($key));

src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,10 @@ private function parseDefinition($id, $service, $file)
153153
$definition->setSynthetic($service['synthetic']);
154154
}
155155

156+
if (isset($service['synchronized'])) {
157+
$definition->setSynchronized($service['synchronized']);
158+
}
159+
156160
if (isset($service['public'])) {
157161
$definition->setPublic($service['public']);
158162
}

src/Symfony/Component/DependencyInjection/Loader/schema/dic/services/services-1.0.xsd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
<xsd:attribute name="scope" type="xsd:string" />
8787
<xsd:attribute name="public" type="boolean" />
8888
<xsd:attribute name="synthetic" type="boolean" />
89+
<xsd:attribute name="synchronized" type="boolean" />
8990
<xsd:attribute name="abstract" type="boolean" />
9091
<xsd:attribute name="factory-class" type="xsd:string" />
9192
<xsd:attribute name="factory-method" type="xsd:string" />

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy