Symfony3 run-time service laden
Ik schrijf momenteel een ERP-systeem op basis van het Symfony framework. Een van de benodigde functionaliteiten is dat het pakketten moet kunnen verzenden, d.m.v. verschillende carriers, bijv. UPS of DHL. Voor de API's zijn genoeg libraries beschikbaar die ik kan gebruiken. Hier heb ik dan een kleine service over geschreven die mijn input omzetten naar input die de API slikt.
Op basis van welke carrier de gebruiker gebruikt moet ik één van de services gebruiken. Ik dacht eerst aan een oplossing als de volgende:
Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
<?php
namespace AppBundle\Service;
class ShippingFactory
{
public function create ($method, $container)
{
return $container -> get ($method -> getCarrier () -> getName());
}
}
[/code]
In m'n services.yml heb ik de verschillende carriers gedefinieerd als services. Maar hier ben ik geen fan van aangezien ik dan de container moet doorgeven aan m'n factory service. Misschien denk ik veel te moeilijk, maar is hier geen nettere manier voor? In de documentatie kwam ik ook wat tegen over Service Configuration, maar dit is allemaal op basis van vooraf gedefinieerde parameters.
namespace AppBundle\Service;
class ShippingFactory
{
public function create ($method, $container)
{
return $container -> get ($method -> getCarrier () -> getName());
}
}
[/code]
In m'n services.yml heb ik de verschillende carriers gedefinieerd als services. Maar hier ben ik geen fan van aangezien ik dan de container moet doorgeven aan m'n factory service. Misschien denk ik veel te moeilijk, maar is hier geen nettere manier voor? In de documentatie kwam ik ook wat tegen over Service Configuration, maar dit is allemaal op basis van vooraf gedefinieerde parameters.
Code (php)
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
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
<?php
class ShippingFactory
{
/** @var Carrier */
private $carriers = [];
public function addCarrier($name, Carrier $carrier)
{
if (isset($this->carriers[$name])) {
throw new \InvalidArgumentException(sprintf('Carrier "%s" already exists.', $name));
}
$this->carriers[$name] = $carrier;
}
public function create(ShippingMethod $method)
{
$carrierName = $method->getCarrier()->getName();
if (!isset($this->carriers[$carrierName])) {
throw new \RuntimeException(sprintf(
'No carrier named "%s" can be found, available carriers: %s',
$carrierName,
implode(', ', array_keys($this->carriers))
));
}
return $this->carrier[$carrierName];
}
}
?>
class ShippingFactory
{
/** @var Carrier */
private $carriers = [];
public function addCarrier($name, Carrier $carrier)
{
if (isset($this->carriers[$name])) {
throw new \InvalidArgumentException(sprintf('Carrier "%s" already exists.', $name));
}
$this->carriers[$name] = $carrier;
}
public function create(ShippingMethod $method)
{
$carrierName = $method->getCarrier()->getName();
if (!isset($this->carriers[$carrierName])) {
throw new \RuntimeException(sprintf(
'No carrier named "%s" can be found, available carriers: %s',
$carrierName,
implode(', ', array_keys($this->carriers))
));
}
return $this->carrier[$carrierName];
}
}
?>
Maak nu van deze factory een service:
Code (php)
1
2
3
4
2
3
4
# app/config/services.yml
services:
app.shipping_factory:
class: AppBundle\Shipping\ShippingFactory
services:
app.shipping_factory:
class: AppBundle\Shipping\ShippingFactory
Vervolgens moet je nog zorgen dat alle carrier services worden toegevoegd aan deze app.shipping_factory service. Dit doe je door de services een tag mee te geven en in een zogenoemde compiler pass alle services met een bepaalde tag toe te voegen aan de shipping factory. Lees meer hierover in http://symfony.com/doc/current/cookbook/service_container/compiler_passes.html en http://symfony.com/doc/current/components/dependency_injection/tags.html
Code (php)
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
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
<?php
class RegisterCarriersPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('app.shipping_factory')) {
// wanneer de factory service niet aanwezig is valt er niks te doen, dus stop de functie
return;
}
$factoryDefinition = $container->getDefinition('app.shipping_factory');
// vind alle services met de app.carrier tag
$carrierServiceIDs = $container->findTaggedServiceIds('app.carrier');
foreach ($carrierServiceIDs as $carrierServiceID => $tags) {
foreach ($tags as $attributes) {
// roep de addCarrier method van de factory aan wanneer de factory service wordt geinitialiseer
// en geef hier de carrier name (tag attribute) en de carrier instance mee
$factoryDefinition->addMethodCall('addCarrier', array(
$attributes['name'],
new Reference($carrierServiceID)
));
}
}
}
}
?>
class RegisterCarriersPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('app.shipping_factory')) {
// wanneer de factory service niet aanwezig is valt er niks te doen, dus stop de functie
return;
}
$factoryDefinition = $container->getDefinition('app.shipping_factory');
// vind alle services met de app.carrier tag
$carrierServiceIDs = $container->findTaggedServiceIds('app.carrier');
foreach ($carrierServiceIDs as $carrierServiceID => $tags) {
foreach ($tags as $attributes) {
// roep de addCarrier method van de factory aan wanneer de factory service wordt geinitialiseer
// en geef hier de carrier name (tag attribute) en de carrier instance mee
$factoryDefinition->addMethodCall('addCarrier', array(
$attributes['name'],
new Reference($carrierServiceID)
));
}
}
}
}
?>
Super! Bedankt voor je uitgebreide antwoord. Ik had al even gekeken naar tags en compiler passes, maar had de link nog niet gemaakt.