I'm starting a new bundle. Its goal is to display some statistics arrays and charts. The problem is I don' t know where to transform raw data into usable data in view's arrays and charts. I read lot of articles about keeping the controllers as thin as possible. And as far as I know, repositories are meant to extract data, not transform them.
Where am I supposed to transform my raw data, according to Symfony2 best practices?
it depends on your application but based on what you described looks like you need to define a Service and write all your logic there so your controller would look something like this
$customService = $this->get('my_custom_service');
$data = $customService->loadMyData();
read more about Services in Symfony: http://symfony.com/doc/current/book/service_container.html
Simply create your own, custom service that uses some repository/ies to extract the data and transform it into usable form.
Sample:
// repository
interface MyRepository {
public function findBySomething($something);
}
class MyRepositoryImpl extends EntityRepository implements MyRepository {
public function findBySomething($something) {
return $this->createQueryBuilder('a')
->where('a.sth = :sth')
->setParameter('std', $something)
->getQuery()
->getResult();
}
}
// service
interface MyService {
public function fetchSomeData();
}
class MyServiceImpl implements MyService {
/** #var MyRespostiory */
private $repo;
public function __construct(MyRepository $repo) {
$this->repo = $repo;
}
public function fetchSomeData() {
$rawData = $this->repo->findBySomething(123);
$data = [];
// do sth
return $data;
}
}
// final usage, eg. within a constructor
class MyConstructor extends Controller {
/** #var MyService */
private $myService;
public function __construct(MyService $myService) {
$this->myService = $myService;
}
public function someAction() {
// you could also get access to the service using $this->get('...')
$data = $this->myService->fetchSomeData();
return $this->render('SomeTemplate', [
'data' => $data
]);
}
}
// service declaration
<service id="myService" class="MyServiceImpl">
<argument type="service" id="doctrine.repository.my_repository" />
</service>
Related
I have a subscriber class subscribing to HttpCacheHitEvent. I want to extract context specific data from other objects. I have been using Context::createDefaultContext() before, but that does not account different parameters of the request such as the locale.
Whats the best way to get or create a context from HttpCacheHitEvent?
You have to start the session, grab the token of it and resolve than the Context using SalesChannelContextFactory.
I cannot recommend doing this as it will make the HttpCache much slower. Maybe you can set cookies to the browser and use them there?
You can inject the service SalesChannelRequestContextResolver and use it in combination with the router service to assemble a SalesChannelContext (and subsequently the Context) from the resolved Request.
<service id="Foo\MyPlugin\CacheHitListener">
<argument type="service" id="router"/>
<argument type="service" id="Shopware\Core\Framework\Routing\SalesChannelRequestContextResolver"/>
<tag name="kernel.event_subscriber"/>
</service>
class CacheHitListener implements EventSubscriberInterface
{
private $matcher;
private RequestContextResolverInterface $contextResolver;
/**
* #param UrlMatcherInterface|RequestMatcherInterface $matcher
*/
public function __construct($matcher, RequestContextResolverInterface $contextResolver)
{
$this->matcher = $matcher;
$this->contextResolver = $contextResolver;
}
public static function getSubscribedEvents(): array
{
return [HttpCacheHitEvent::class => 'onCacheHit'];
}
public function onCacheHit(HttpCacheHitEvent $event): void
{
if ($this->matcher instanceof RequestMatcherInterface) {
$parameters = $this->matcher->matchRequest($event->getRequest());
} else {
$parameters = $this->matcher->match($event->getRequest()->getPathInfo());
}
$event->getRequest()->attributes->add($parameters);
$this->contextResolver->resolve($event->getRequest());
$context = $event->getRequest()->attributes->get(PlatformRequest::ATTRIBUTE_CONTEXT_OBJECT);
// ...
}
}
You can also get the SalesChannelContext like this:
$request->attributes->get(PlatformRequest::ATTRIBUTE_SALES_CHANNEL_CONTEXT_OBJECT);
I decorated UrlGeneratorInterface
app.decorator.url_generator:
class: App\CoreBundle\Routing\Extension\UrlGenerator
decorates: Symfony\Component\Routing\Generator\UrlGeneratorInterface
arguments: ['#app.decorator.url_generator.inner']
but it's not used in cases where some bundle in example executes $this->generator->generate(), and I tracked what Symfony does through XDebug and CompiledUrlGenerator is used instead. I can see where this happens, namely in Symfony\Component\Routing\Router in getGenerator it specifically checks for CompiledUrlGenerator::class. But I don't want to override vanilla Symfony code. How am I supposed to override/decorate/extend which class in order for mine to be chosen always, as I have special parameters I need to add to the path. Thank you in advance!
I found it.
app.decorator.router:
class: App\CoreBundle\Routing\Extension\Router
decorates: 'router.default'
arguments: ['#app.decorator.router.inner']
Decorating this actually makes all packages use your Router. And as the UrlGenerator it has the generate function which can be extended.
EDIT: On request I provide the router class as well:
class Router implements RouterInterface {
protected $innerRouter;
public function __construct(RouterInterface $innerRouter) {
$this->innerRouter = $innerRouter;
}
public function setContext(RequestContext $context)
{
$this->innerRouter->setContext($context);
}
public function getContext()
{
return $this->innerRouter->getContext();
}
public function getRouteCollection()
{
return $this->innerRouter->getRouteCollection();
}
public function generate($name, $parameters = [], $referenceType = self::ABSOLUTE_PATH)
{
//add here to $parameters...
return $this->innerRouter->generate($name, $parameters, $referenceType);
}
public function match($pathinfo)
{
$parameters = $this->innerRouter->match($pathinfo);
//add here to $parameters...
return $parameters;
}
}
Symfony 2.8.13 / Doctrine ORM 2.5.5 / PHPUnit 5.7.5
I want to test a method of a class that makes use of the doctrine entity manager. This public method calls a private one that instantiates a Bookmark entity, flushes it and returns this entity. Then later, in the tested method I need to access the entity Id. Everything is mocked excepted the Bookmark entity itself. The main problem is that there is no setId() method in my entity. Here is the code and my main idea to solve this issue but I don't know if it is correct ?
Tested class and method
class BookmarkManager
{
//...
public function __construct(TokenStorageInterface $tokenStorage, ObjectManager $em, Session $session)
{
//...
}
public function manage($bookmarkAction, $bookmarkId, $bookmarkEntity, $bookmarkEntityId)
{
//...
$bookmark = $this->add($bookmarkEntity, $bookmarkEntityId);
//...
$bookmarkId = $bookmark->getId();
//...
}
private function add($entity, $entityId)
{
//...
$bookmark = new Bookmark();
//...
$this->em->persist($bookmark);
$this->em->flush();
return $bookmark;
}
}
Test
class BookmarkManagerTest extends \PHPUnit_Framework_TestCase
{
public function testThatRestaurantAdditionToBookmarksIsWellManaged()
{
//...
// THIS WON'T WORK AS NO setId() METHOD EXISTS
$entityManagerMock->expects($this->once())
->method('persist')
->will($this->returnCallback(function ($bookmark) {
if ($bookmark instanceof Bookmark) {
$bookmark->setId(1);
}
}));
//...
$bookManager = new BookmarkManager($tokenStorageMock, $entityManagerMock, $sessionMock);
//...
}
}
Solutions ?
1- Make usage of reflection class as proposed here :
$entityManagerMock->expects($this->once())
->method('persist')
->will($this->returnCallback(function ($bookmark) {
if ($bookmark instanceof Bookmark) {
$class = new \ReflectionClass($bookmark);
$property = $class->getProperty('id');
$property->setAccessible(true);
$property->setValue($bookmark, 1);
//$bookmark->setId(1);
}
}));
2- Create a test Boookmark entity that extends from the real one and add a setId() method. Then create a mock of this class and replace and customize the one got from the ReturnCallback method with this one ? It seems crappy...
Any thoughts ? Thanks for your help.
The reflection looks interesting but it decreases readability of tests (mixing with mocks makes the situation tough).
I would create a fake for entity manager and implements there setting id based on reflection:
class MyEntityManager implements ObjectManager
{
private $primaryIdForPersitingObject;
public function __construct($primaryIdForPersitingObject)
{
$this->primaryIdForPersitingObject = $primaryIdForPersitingObject;
}
...
public function persist($object)
{
$reflectionClass = new ReflectionClass(get_class($object));
$idProperty = $reflectionClass->getProperty('id');
$idProperty->setAccessible(true);
$idProperty->setValue($object, $this->primaryIdForPersitingObject);
}
public function flush() { }
...
}
Once you implemented this, you can inject the instance of MyEntityManager and make your tests small and easier to maintain.
You test would look like
<?php
class BookmarkManagerTest extends \PHPUnit_Framework_TestCase
{
public function testThatRestaurantAdditionToBookmarksIsWellManaged()
{
// ...
$entityManager = MyEntityManager(1);
//...
$bookManager = new BookmarkManager($tokenStorageMock, $entityManager, $sessionMock);
//...
}
}
Of course, a situation may be harder if there is a need of setting different ids for many persisting objects. Then you can, for example, increase $primaryIdForPersitingObject on persist call
public function persist($object)
{
$reflectionClass = new ReflectionClass(get_class($object));
$idProperty = $reflectionClass->getProperty('id');
$idProperty->setAccessible(true);
$idProperty->setValue($object, $this->primaryIdForPersitingObject);
$this->primaryIdForPersitingObject++;
}
It may be extended even further to have separate primaryIdForPersitingObject each entity class, and your tests will be still clean.
I'm trying to create a manager to handle basic requests of a controller (list, new, edit, delete). I need to inject the form factory within the constructor of this service. By what name should I call?
I need something like this:
lp_ExpedienteManager:
class: AppBundle\Services\ExpedienteManager\ExpedienteManager
arguments: [ "#doctrine.orm.entity_manager", "#security.token_storage", "#form_factory" ]
Thanks for your time!
For future references, since Symfony 3.3 this service is available as Symfony\Component\Form\FormFactoryInterface. So you can inject in your services like
use Symfony\Component\Form\FormFactoryInterface;
class AccountBridge
{
private $formFactory;
public function __construct(FormFactoryInterface $formFactory)
{
$this->formFactory = $formFactory;
}
public function accountCreateAction(Account $account)
{
$form = $this->formFactory->create(AccountType::class, $account);
}
}
How would I go about binding a Symfony config tree to a class rather than returning an array?
Using Symfony\Component\Config\Definition\Processor returns an array.
In my case I want the config to be bound to a class so I can use methods to combine parts of the data.
Here is a simple example of my use case. I want the config bound to a class so I can use a method to join table.name and table.version together (my actual use case is more complex, but this is a simple example)
config.yml
db:
table:
name: some_table
version: v2
ConfigurationInterface
class DBConfiguration implements ConfigurationInterface
{
/**
* {#inheritDoc}
*/
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('db');
$rootNode
->children()
->arrayNode('table')
->children()
->scalarNode('name')->isRequired()->end()
->scalarNode('version')->end()
->end()
->end()
;
return $treeBuilder;
}
}
Class I want to bind the config to
class DB
{
public $table;
public function __construct()
{
$this->table = new Table();
}
}
class Table
{
public $name;
public $version;
/**
* #return string
* Calculate the full table name.
*/
public function getTableName()
{
return $this->name.'-'.$this->version;
}
}
The Symfony Config component doesn't support that.
However, in a Symfony project, this is usually done at the container compile phase. In your bundle's Extension class, you will have access to the configuration tree of your bundle in array form.
You can then take this array and assign it to a service defined in the service container that will create your config object.
This is exactly how DoctrineBundle's configuration class is built:
Abstract services (for the configuration and the factory) are defined in dbal.xml
When loading DoctrineBundle's extension, an instance of the abstract config service is created for each defined connection.
An instance of the abstract factory service is created for each defined connection.
The options array is then passed to the abstract factory service along with the configuration
When creating an instance, the factory then does the necessary transformations.
As far as I know, Symfony has no native support for this, however, you could implement it yourself. You could use subset of Symfony Serializer Component in charge of deserialization, but I think it would be an overkill. Especially since I don't see any PublicPropertyDenormalizer, only GetSetMethodNormalizer (which is denormalizer too). Therefor you would have to either make your config objects have get/set methods or roll PublicPropertyDenormalizer on your own. Possible but it really seems like an overkill and doesn't look like helping much:
Symfony Serializer Component
$array = [
'field1' => 'F1',
'subobject' => [
'subfield1' => 'SF1',
],
];
class MyConfigObject implements Symfony\Component\Serializer\Normalizer\DenormalizableInterface
{
private $field1;
private $subobject;
public function getField1()
{
return $this->field1;
}
public function setField1($field1)
{
$this->field1 = $field1;
}
public function getSubobject()
{
return $this->subobject;
}
public function setSubobject(SubObject $subobject)
{
$this->subobject = $subobject;
}
public function denormalize(\Symfony\Component\Serializer\Normalizer\DenormalizerInterface $denormalizer, $data, $format = null, array $context = array())
{
$obj = new static();
$obj->setField1($data['field1']);
$obj->setSubobject($denormalizer->denormalize($data['subobject'], 'SubObject'));
return $obj;
}
}
class SubObject
{
private $subfield1;
public function getSubfield1()
{
return $this->subfield1;
}
public function setSubfield1($subfield1)
{
$this->subfield1 = $subfield1;
}
}
$normalizer = new \Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer();
$obj = (new MyConfigObject())->denormalize($normalizer, $array);
Native PHP Way
Imo this is a lot easier than above as Symfony Serializer wasn't really ment for that.
$array = [
'field1' => 'F1',
'subobject' => [
'subfield1' => 'SF1',
],
];
trait Denormalizable
{
public function fromArray($array)
{
foreach ($array as $property => $value) {
if (is_array($value)) {
if ($this->$property instanceof ArrayDenormalizableInterface) {
$this->$property->fromArray($value);
} else {
$this->$property = $value;
}
} else {
$this->$property = $value;
}
}
}
}
interface ArrayDenormalizableInterface
{
public function fromArray($array);
}
class MyConfigObject implements ArrayDenormalizableInterface
{
use Denormalizable;
public $field1;
public $subobject;
public function __construct()
{
$this->subobject = new SubObject();
}
}
class SubObject implements ArrayDenormalizableInterface
{
use Denormalizable;
public $subfield1;
}
$myConf = new MyConfigObject();
$myConf->fromArray($array);
Whatever way you choose, you can now just take array returned from symfony processor and turn it into a config object you need.