Can a DTO reference VO of the domain model? - collections

Question
Can a data transfer object (DTO) reference a value object (VO) of the domain model?
Context
In my domain, I've an importer which import aggregates from a collection. The collection is made of DTO build by a collector on which the importer rely. Since both the importer and collector are services (interfaces) of my domain, can the DTO reference domain value objects, or should I stick with primitives and turn them into value objects only when processing the collection (build of aggregates)?
Collector implementation where DTO made of value objects from the domain model are built
<?php
/**
* i-MSCP Patcher plugin
*
* #author Laurent Declercq <l.declercq#nuxwin.com>
* #copyright (C) 2019 Laurent Declercq <l.declercq#nuxwin.com>
* #license i-MSCP License <https://www.i-mscp.net/license-agreement.html>
*/
/**
* #noinspection
* PhpUnhandledExceptionInspection
* PhpDocMissingThrowsInspection
*/
declare(strict_types=1);
namespace iMSCP\Plugin\Patcher\Infrastructure\Domain\Service\Component\Importer;
use iMSCP\Plugin\Patcher\Domain\Model\Component\ComponentBuild;
use iMSCP\Plugin\Patcher\Domain\Model\Component\ComponentName;
use iMSCP\Plugin\Patcher\Domain\Model\Component\ComponentVersion;
use iMSCP\Plugin\Patcher\Domain\Service\Component\Importer\ComponentCollector;
use iMSCP\Plugin\Patcher\Domain\Service\Component\Importer\DTO\ComponentDTO;
use iMSCP\Plugin\Patcher\Domain\Service\Component\Importer\DTO\ComponentDTOCollection;
use iMSCP_Config_Handler_File as MergedConfig;
use iMSCP_Plugin_Manager as PluginManager;
use RuntimeException;
use Throwable;
/**
* Class DefaultComponentCollector
* #package iMSCP\Plugin\Patcher\Infrastructure\Domain\Service\Component\Importer
*/
class DefaultComponentCollector implements ComponentCollector
{
/**
* #var MergedConfig
*/
private $mergedConfig;
/**
* #var PluginManager
*/
private $pluginManager;
/**
* DefaultComponentCollector constructor.
*
* #param MergedConfig $mergedConfig
* #param PluginManager $pluginManager
*/
public function __construct(
MergedConfig $mergedConfig, PluginManager $pluginManager
)
{
$this->mergedConfig = $mergedConfig;
$this->pluginManager = $pluginManager;
}
/**
* #inheritDoc
*/
public function collect(ComponentDTOCollection $collection): void
{
try {
// Core
$collection->add(new ComponentDTO(
ComponentName::fromString('core'),
ComponentVersion::fromString($this->mergedConfig['Version']),
ComponentBuild::fromString($this->mergedConfig['Build'])
));
// Plugins
$this->collectComponentsFromCorePluginManager($collection);
} catch (Throwable $e) {
throw new RuntimeException(sprintf(
"Couldn't collect list of components: %s", $e->getMessage()
), $e->getCode(), $e);
}
}
/**
* Collects components from the i-MSCP core plugin manager.
*
* #param ComponentDTOCollection $collection
* #return void
*/
private function collectComponentsFromCorePluginManager(
ComponentDTOCollection $collection
): void
{
$pluginList = $this->pluginManager->pluginGetList('all', false);
foreach ($pluginList as $pluginName) {
$pluginInfo = $this->pluginManager
->pluginGetInfo($pluginName)
->toArray();
$componentDTO = new ComponentDTO(
ComponentName::fromString($pluginInfo['name']),
ComponentVersion::fromString($pluginInfo['version']),
ComponentBuild::fromString((string)$pluginInfo['build'])
);
$collection->add($componentDTO);
}
}
}

Can a data transfer object (DTO) reference a value object (VO) of the domain model?
Yes, but you want to be very careful about doing that.
A data transfer object is, at its core, a message. For a message to serve its purpose, both the sender and the receiver must have compatible understandings of its semantics. Making an incompatible change to the DTO schema requires corresponding changes to the receiver.
A value object, in the domain model, is not a message. It is structured information, purely an implementation detail of the current model. If we want to deploy a new version of our model, which uses a completely different arrangement of values, or their underlying data structures, then we can.
So having a DTO (which is supposed to be stable) depend on a value object (which does not promise to be stable) is creating an opportunity for problems down the road.
In cases where your vocabulary of values is stable, then the risks are lower.

Related

Inject service based on dynamic value in Symfony

I have 2 services, BlueWorkerService and YellowWorkerService, both implementing the same interface, WorkerServiceInterface. Each of these services use the same entities but with different required logic.
I need to inject one of, but not both, of these classes and use them in ProcessorService so that the interface methods are called using on correct Worker. Which worker service to use is dependent on which Worker is currently being processed. I'll break it down:
Class WorkerProcessor {
private $workerService;
public function __construct(WorkerServiceInterface $workerServiceInterface)
{
$this->workerService = $workerServiceInterface;
}
public function getMixedColourWithRed() {
return $this->workerService->mixWithRed();
}
}
The worker service that is being used would be based on whether the worker being processed has the colour property of Blue or Yellow.
I know I can probably use a Factory to achieve this as described here but my problem is how to tell the factory which Worker colour I am processing?
Running on Symfony 3.4
If you need more info, just ask and I will update the question.
NOTE: I'm using Symfony 4.3.1. I'll post it like that, then I'll help you to move all code from this architecture to Symfony 3.4.
I'm using a similar concept to load different classes in my project. Let me explain first, then I'll add code under this text.
Firstly, I'm loading a custom compiler pass under src/Kernel.php (your file is app/AppKernel.php):
/**
* {#inheritDoc}
*/
public function build(ContainerBuilder $container)
{
$container->addCompilerPass(new BannerManagerPass());
}
BannerManagerPass its created under src/DependencyInjection/Compiler (in your case should be src/BUNDLE/DependencyInjection/Compiler`).
class BannerManagerPass implements CompilerPassInterface
{
/**
* {#inheritDoc}
*/
public function process(ContainerBuilder $container)
{
if (!$container->has(BannerManager::class)) {
return;
}
$definition = $container->findDefinition(BannerManager::class);
$taggedServices = $container->findTaggedServiceIds('banner.process_banners');
foreach (array_keys($taggedServices) as $id) {
$definition->addMethodCall('addBannerType', [new Reference($id)]);
}
}
}
As you see, this class should implement CompilerPassInterface. You can observe that I'm looking for specific services tagged as banner.process_banners. I'll show how I tagged services a little bit later. Then, I'm calling addBannerType method from BannerManager.
App\Service\BannerManager.php: (in your case src/BUNDLE/Service/BannerManager.php)
class BannerManager
{
/**
* #var array
*/
private $bannerTypes = [];
/**
* #param BannerInterface $banner
*/
public function addBannerType(BannerInterface $banner)
{
$this->bannerTypes[$banner->getType()] = $banner;
}
/**
* #param string $type
*
* #return BannerInterface|null
*/
public function getBannerType(string $type)
{
if (!array_key_exists($type, $this->bannerTypes)) {
return null;
}
return $this->bannerTypes[$type];
}
/**
* Process request and return banner.
*
* #param string $type
* #param Server $server
* #param Request $request
*
* #return Response
*/
public function process(string $type, Server $server, Request $request)
{
return $this->getBannerType($type)->process($request, $server);
}
}
This class has a custom method (created by me) called process(). You can name it whatever you want it, but I think that's pretty verbose. All parameters are sent by me, so don't mind. You can send whatever you want.
Now we have our Manager and compiler pass is set. It's time to set our banner types (based on my example) and tag them!
My banner types are under src/Service/Banner/Types (in your case should be src/BUNDLE/Service/WhateverYouWant/Type. This does not matter! You can change it later from services.yaml).
These types are implementing my BannerInterface. It does not matter the code under the class in this instance. One more thing that I should warn you! You should see that under BannerManager, inside the addBannerType() I'm calling $banner->getType(). This is one method inherited from BannerInterface in my case and it has a unique string (in my example I have three banner types: small, normal, large). This method can have any name, but don't forget to update it as well in your manager.
We are almost ready! We should tag them, then we are ready to try them!
Go to your services.yaml and add these lines:
App\Service\Banner\Types\:
resource: '../src/Service/Banner/Types/'
tags: [banner.process_banners]
Please see the tag!
Whatever I want to show a custom banner, I'm using a simple URL with $_GET where I keep my banner type, then I load it like this:
public function view(?Server $server, Request $request, BannerManager $bannerManager)
{
...
return $bannerManager->getBannerType($request->query->get('slug'))->process($request, $server);
}

Symfony Entity load calculated field but not always

I have a symfony entity that has a not mapped calculated field
namespace AppBundle\Entity;
class Page
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* Page count. Non-mapped
*
* #var integer
*/
protected $pageCount;
}
The $pageCount value is obtainable by consuming a remote service that will provide the value for use in the application.
I figured the best way is to use the postLoad event to handle this.
class PageListener
{
/**
* #ORM\PostLoad
*/
public function postLoad(LifecycleEventArgs $eventArgs)
{
// ...
}
}
I need to retrieve this value when loading values.
public function indexAction()
{
// I want to fetch the pageHits here
$pagesListing = $this->getDoctrine()
->getRepository('AppBundle:Pages')
->findAll();
// I don't want to fetch the pageHits here
$pagesListing2 = $this->getDoctrine()
->getRepository('AppBundle:Pages')
->findAll();
}
However, this will ALWAYS result in a call to a remote service.
There may be cases where I do not want the service to be invoked, so that it reduced a performance load on the application.
How can I fetch the remote values automatically, but only when I want to.
Your "problem" is pretty common and one of the reasons I never use Doctrine repositories directly.
Solution I would recommend
Always make custom repository services and inject Doctrine into them.
That way, if you want to merge some data from some other data source (eg. Redis, filesystem, some remote API), you have complete control over it and process is encapsulated.
Example:
class PageRepository
{
private $em;
private $api;
public function __construct(EntityManagerInterface $em, MyAwesomeApi $api)
{
$this->em = $em;
$this->api = $api;
}
public function find($id)
{
return $em->getRepository(Page::class)->find($id);
}
public function findAll()
{
return $em->getRepository(Page::class)->findAll();
}
public function findWithCount($id)
{
$page = $this->find($id);
$count = $this->myAwesomeApi->getPageCount($id);
return new PageWithCount($page, $count);
}
}
Solution I wouldn't recommend, but works :)
If you don't want to change your code structure and want to keep it as it is, you could make a really simple change that will make your pageCount be loaded only when it is necessary:
Move code from Page::postLoad method into Page::getPageCount()
Example:
public function getPageCount()
{
if (null === $this->pageCount) {
$this->pageCount = MyAwesomeApi::getPageCount($this->id);
}
return $this->pageCount;
}
This way, pageCount will only be loaded if something tries to access it.

How to get #request_stack service in app/console context?

I have services that require the #request_stack to fetch parameters.
Now, I want to expose certain functionality to console commands callable via ./app/console//. Yet in the context of an ./app/console, there is no #request_stack, yet one can input arguments.
In order to resolve this issue, I am now creating basically two services, one basic, only waiting for the params, and one being able to use the #request_stack.
Yet I dislike that there are two ways for the data to be fetched in the request-based flow and via the app/console.
Hence I am wondering, as I am simply want the data that comes per default via the request to also be able to be inputted via console arguments:
Can I setup a custom request_stack to simulate a request during a console command?
When I was investigating this issue, I stumbled across request stack push method, where a warning was already in place in the doc block:
/**
* Pushes a Request on the stack.
*
* This method should generally not be called directly as the stack
* management should be taken care of by the application itself.
*/
public function push(Request $request)
{
$this->requests[] = $request;
}
So while it would be possible to do it this way, I decided against the approach of my original question and to refactor my application instead.
I have created a context value object which just holds the parameter data:
/**
* Context
**/
class Context
{
/**
* #var string
*/
private $countryCode;
/**
* Context constructor.
* #param string $countryCode
*/
public function __construct($countryCode = '')
{
$this->countryCode = $countryCode;
}
/**
* #return string
*/
public function getCountryCode()
{
return $this->countryCode;
}
}
And a ContextFactory that creates the context with by the request stack:
class ContextFactory extends RequestAwareService
{
/**
* ContextFactory constructor.
* #param RequestStack $stack
*/
public function __construct(RequestStack $stack)
{
$this->setRequestStack($stack);
}
/**
* #return Context
*/
public function create()
{
return new Context($this->request->getCountryCode());
}
}
(The RequestAwareService is just a helper class to more easily parse the request.)
I then defined the services in my Bundle services.yml:
context.factory:
class: Kopernikuis\MyBundle\Service\Config\ContextFactory
arguments:
- '#request_stack'
context:
class: Kopernikuis\MyBundle\Service\Config\Context
factory:
- '#context.factory'
- create
Instead of injecting the #request_stack, I am now injecting my #context value object, which also had the benefit of reducing the hierarchy as now only one service parses the request_stack once, and I also noticed that certain functionality got much simpler as I could remove parameters from method calls, as they were all provided by the context object instead.
And in my custom commands, I can just replace my context
protected function execute(InputInterface $input, OutputInterface $output)
{
// #todo: use variable from InputInterface
$context = new Context('fnordfoo');
$this->getContainer()->set('context', $context);
}
With the newly gained knowledge, I strongly disagree with my original intent of trying to manually set the #request_stack.
Refactoring the code base to not necessarily require the #request_stack was a more solid choice.

symfony: where should I store and how to retrieve a tax rate?

Im wondering where should I store and how to retrieve a tax rate that Im going to use in multiple classes. I would like to set this value as a parameter just one time. Is parameters.yml a good place to set it?
/**
* #ORM\Column(type="string")
*/
protected $taxRate;
I will have the code above in multiple classes as I said.
Bear in mind that I want to use also the value on templates.
Well if you just want to set the value at a single point then the easiest solution would be using a trait.
The benefit is that you can use multiple traits which feels like multiple inheritance instead of a big basic class that is unflexible and does not care about the single responsibility principle.
The trait might look like this:
namespace Your\Namespace;
use Doctrine\ORM;
trait TaxRate {
/**
* #ORM\Column(type="string")
*/
protected $taxRate = 'your_default_value';
// set get methods here
}
You can then use it multiple times:
namespace Your\Namespace;
use Your\Namespace\TaxRate;
class Entity {
use TaxRate;
// more methods here
}
Adding parameters from parameters.yml to an entity is quite a mess in Symfony. An idea is to use the entity as a service which is not working when using Doctrine's find(). So I don't recommend this.
This code looks like it belongs to an Entity. You can share this parameter by creating an abstract class and make all the entities that need and have this property to extend form it:
abstract class TaxableEntity {
/**
* #ORM\Column(type="string")
*/
protected $taxRate;
}
class Entity extends TaxableEntity {
//here you have access to taxRate
}
This approach has its drawbacks though, which are the usual drawbacks of inheritance. Once you get many consumers of this API (protected) you'll have a very hard time re-factoring, plus you're enforcing all your Taxable entities to have that field mapped into the DB.
Another approach, would be to wrap the concept of a TaxRate into a ValueObject. This way you'd be able to use it more as a regular attribute but with a stronger type:
class TaxRate {
private function __construct($rate) {
$this->rate = $rate;
}
public static function fromFloat($value) {
if(!is_float($value)) {
throw new \InvalidArgumentException();
}
return self($value);
}
public static function fromInteger($value) {
if(!is_int($value)) {
throw new \InvalidArgumentException();
}
return self((float)$value);
}
}
Anyway, the "parameters.yml" has nothing to do with your object's properties, but more about your project's property. For instance, the necessary parameters to connect to your DB would be well located in the parameters.yml:
database_host: localhost
database_port: 3306
database_user: user
database_password: password
database_name: db_name
This way they can be injected/used in your services by means of the DIC.
I think I should consider to define it as a constant: http://symfony.com/doc/current/best_practices/configuration.html#constants-vs-configuration-options
An option where you could handle varying tax rates for when they are changed would be to store your tax rates in a separate table and reference that from your entities, possibly using a trait for ease of use and DRY code.
I will be using annotations as it seems that's what you are using.
AppBundle\Entity\TaxRate
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="app_tax_rate")
* #ORM\Entity(repositoryClass="AppBundle\Entity\TaxRateRepository")
*/
class TaxRate
{
/**
* #var int
*
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #var float
*
* #ORM\Column(type="decimal", scale=2, column="tax_rate")
*/
private $rate;
/**
* #var \DateTime
*
* #ORM\Column(type="datetime", column="active_from")
*/
private $activeFrom;
.. getter & setters
}
In this you would store the tax rate and the date the rate became/becomes active. If, for example, you knew that in 3 months the rate was going to change you could add that to your database in preparation but it wouldn't take effect until that date. You would also be able to set an entities tax rates from the past or the future, should the need arise.
AppBundle\Entity\TaxRateRepository
use Doctrine\ORM\EntityRepository;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class TaxRateRepository extends EntityRepository
{
/**
* Get current tax rate if available
*
* #return TaxRate
* #throws NotFoundHttpException
*/
public function getCurrentTaxRateForDate(\DateTime $date)
{
return $this->getTaxRateForDate(new \DateTime());
}
/**
* Get tax rate for a specified date if available
*
* #param \DateTime $date
* #return TaxRate
* #throws NotFoundHttpException
*/
public function getTaxRateForDate(\DateTime $date)
{
$queryBuilder = $this->createQueryBuilder('r');
$rate = $queryBuilder
->where($queryBuilder->expr()->lte('r.activeFrom', ':date'))
->setParameter('date', $date)
->orderBy('r.activeFrom', 'DESC')
->setMaxResults(1)
->getQuery()
->getResult();
if (null === $rate) {
throw new NotFoundHttpException(sprintf(
'No tax rate available for "%s"',
$date->format('Y-m-d')
));
}
return $rate;
}
}
This "getTaxRateForDate" method will find all tax rates that became active on or before today and order them by the date they became active, in reverse, then return the first in the list.. so the last tax rate to become active before the given date. The "getCurrentTaxRate" will do the above but automatically set the date to "today".
You can then create a trait that you can drop into any entity that uses a tax rate, with the trait containing the necessary annotations. The association will be unidirectional meaning you would be able to get the tax rate from the order/invoice/etc but not the other way around.
AppBundle\Entity\Traits\TaxableTrait
use Doctrine\ORM\Mapping as ORM;
trait TaxableTrait
{
/**
* #ORM\ManyToOne(targetEntity="TaxRate")
* #ORM\JoinColumn(name="tax_rate_id", referencedColumnName="id")
*/
private $taxRate;
.. getters & setters
}
Then in each of your entities you can just add the trait which will add the getter, setter and annotation for the field.
use AppBundle\Entity\Traits\TaxableTrait;
class Order
{
use TaxableTrait;
// the rest of your entity
}
To use this you would then do something like..
$order = new Order();
// This, as stated in your custom repository, would cause an exception
// to be thrown if no tax rate was available for "now"
$order->setTaxRate($taxRateRepository->getCurrentTaxRate());
.. set the rest of the order details
$order->getTaxRate(); // would get the tax rate object
$order->getTaxRate()->getRate(); // would get the actual rate
{{ order.taxRate.rate }} // would get the rate in a twig template
Create service that returns taxrate from database .
For Performance use Doctrine Second Level Cache ( there will be no difference if you access tax one or hundred times - after first time value would be store in SLC - till end of request )

How can I issue one Insert statement for multiple rows in Propel

is there a way to issue one INSERT statement instead of calling save() method on each object? Can I call save() on PropelObjectCollection?
You can call save on a PropelObjectCollection from the class itself but it will issue multiple insert statements to do the work.
There is not much of a performance gain from issuing one INSERT rather than multiple if they are all wrapped in a transaction which Propel does by default. Also given the way Propel recurses to save related objects I suspect trying to do this would add a lot of complexity.
<?php
/**
* This file is part of the Propel package.
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*
* #license MIT License
*/
/**
* Class for iterating over a list of Propel objects
*
* #author Francois Zaninotto
* #package propel.runtime.collection
*/
class PropelObjectCollection extends PropelCollection
{
/**
* Save all the elements in the collection
*
* #param PropelPDO $con
*
* #throws PropelException
*/
public function save($con = null)
{
if (!method_exists($this->getModel(), 'save')) {
throw new PropelException('Cannot save objects on a read-only model');
}
if (null === $con) {
$con = $this->getConnection(Propel::CONNECTION_WRITE);
}
$con->beginTransaction();
try {
/** #var $element BaseObject */
foreach ($this as $element) {
$element->save($con);
}
$con->commit();
} catch (PropelException $e) {
$con->rollback();
throw $e;
}
}
It is enough to put all of your save() calls in one transaction. In my code I had to insert 270 records in MySQL database.
Results without transactions:
real 0m15.127s
user 0m0.300s
sys 0m0.056s
Results with transactions:
real 0m0.687s
user 0m0.244s
sys 0m0.036s
There is a huge difference.

Resources