Services
Note
Watch a short video tutorial on YouTube: Components & Services.
The OXID eShop uses the Symfony DI Container to handle services. For more information about DI Container refer to the Symfony DI Container documentation
In a module, you can:
Define service
For example, you need to adjust the price calculation of the product. You can define a single responsible service for this:
interface PriceCalculatorInterface
{
public function getPrice(string $productId, int $amount): Price;
}
class ERPPriceCalculator implements PriceCalculatorInterface
{
public function getPrice(string $productId, int $amount): Price
{
$this->doSomeCalculation();
// ...
return $price
}
}
Register service
Then you can register the service in OXID DI Container. Create in root directory of your module
services.yaml
file and register it there:
services:
SomeCompany\SpecialERPModule\PriceCalculatorInterface:
class: SomeCompany\SpecialERPModule\ERPPriceCalculator
autowire: true
Note
class argument in services.yaml is optional and can be omitted (when it’s undefined, DI container considers that the ID of the service is the PHP class with fully qualified name). Following 2 examples of service arguments will have the same effect:
services:
SomeCompany\SpecialERPModule\PriceCalculator:
class: SomeCompany\SpecialERPModule\PriceCalculator
services:
SomeCompany\SpecialERPModule\PriceCalculator:
We recommend to use the interface namespace as the DI container key for
the service if you have only the one implementation for an interface and
provide class
parameter with the implementation namespace.
So we can inject with autowiring an interface as dependency in our classes
and avoid dependencies on implementations (dependency inversion principle).
public function __construct(PriceCalculatorInterface $priceCalculator)
{
$this->priceCalculator = $priceCalculator;
}
In this example we have dependency on PriceCalculatorInterface (abstraction), but not on ERPPriceCalculator (implementation). PriceCalculatorInterface will be autowired and no changes in the yaml file are needed because the key of the service is the same as provided in the constructor argument type.
Inject own, third party module or shop services
You can use your own, shop services or even services of other modules via dependency injection.
use Psr\Log\LoggerInterface;
class ERPPriceCalculator implements PriceCalculatorInterface
{
private $shopLogger;
public function __construct(LoggerInterface $shopLogger)
{
$this->shopLogger = $shopLogger;
}
public function getPrice(string $productId, int $amount): Price
{
$this->shopLogger->info('Log something');
$this->doSomeCalculation();
// ...
return $price;
}
}
In this example a shop service with id ‘PsrLogLoggerInterface’ will be autowired and no changes in the yaml file are needed, because the key of the logger service is the same as provided in the constructor argument type.
Use services in non-DI classes
What if you want to use your new service (or any eShop service) in one of the non-DI OXID eSales classes as well? You’ll need to sacrifice the benefits of Dependency Injection and resort to Service Locator pattern for such scenario.
For example, you can just create a subclass and use the service after fetching it directly in one of the method overrides:
class ERPArticle extends Article_parent
{
public function getPrice($amount = 1)
{
$erpPriceCalculator = ContainerFacade::get(PriceCalculatorInterface::class);
return $erpPriceCalculator->getPrice($this->getId(), $amount)
}
}
Note
Each Symfony service is defined as private by default. Services that need to be accessed directly from the container (via Service Locator) should have public visibility.