I am not sure if this is even best practice or possible at all.
So I have a situation where I use DataTables and I need to change a boolean value to text in order to display true/false instead of numbers. But I also need to do that in different languages.
Since I need this in several places in the app i was thinking that I should make an app specific Repository class that extends EntityRepository and use it as extended class for the repositories I am building. For this i want to inject translator object in in order to translate some keys, but translation is never set:
CustomRepository class
class CustomRepository extends EntityRepository
{
/**
* #var Translator
*/
protected $translator;
/**
* #param Translator $translator
*/
public function setTranslator(Translator $translator)
{
$this->translator = $translator; //*******this one is not set...
}
/**
* Replace bool results into string values
*
* #param $aRes
* #param $sField
*
* #return mixed
*/
protected function _replaceBoolToStringResult(&$aRes, $sField)
{
if (1 == $aRes[$sField]) {
$aRes[$sField] = str_replace('1', $this->translator->trans('site.true'), $aRes[$sField]);
} else {
$aRes[$sField] = str_replace('0', $this->translator->trans('site.false'), $aRes[$sField]);
}
return $aRes;
}
}
services.yml
app.custom.repository:
class: App\CommonBundle\Repository\CustomRepository
#should i call here all the constructor vars from EntityRepository class as arguments?
calls:
- [setTranslator, ["#translator.default"]]
Repository with custom DQL
class SettingsRepository extends CustomRepository
{
public function findOverviewSettingsAsJson()
{
$aResult = $this->createQueryBuilder('s')
->select('s.identifier, s.type, s.isActive')
->getQuery()
->getScalarResult();
// ******** HERE I WANT TO USE _replaceBoolToStringResult
return json_encode($aResult);
}
}
I found this article by Matthias to be useful on this issue. (I know link only answers are frowned on...)
You must use the factory pattern when you use a repository as a service.
See possible duplicates :
Symfony 2: Creating a service from a Repository
How to inject a repository into a service in Symfony2?
Note : the syntax changed in latest SF version : http://symfony.com/doc/current/components/dependency_injection/factories.html
Edit :
You should use your repository as a service :
app.custom.repository:
class: App\CommonBundle\Repository\CustomRepository
factory: ["#doctrine.orm.entity_manager", getRepository]
arguments:
- App\CommonBundle\Entity\CustomEntity
calls:
- [setTranslator, ["#translator.default"]]
Then call this service as any other service in your code. For example from inside a controller :
$this->get('app.custom.repository')->...
Related
I am adherent of Action Class approach using instead of Controller. The explanation is very simple: very often Controller includes many actions, when following the Dependency Injection principle we must pass all required dependencies to a constructor and this makes a situation when the Controller has a huge number of dependencies, but in the certain moment of time (e.g. request) we use only some dependencies. It's hard to maintain and test that spaghetti code.
To clarify, I've already used to work with that approach in Zend Framework 2, but there it's named Middleware. I've found something similar in API-Platform, where they also use Action class instead of Controller, but the problem is that I don't know how to cook it.
UPD:
How can I obtain the next Action Class and replace standard Controller and which configuration I should add in regular Symfony project?
<?php
declare(strict_types=1);
namespace App\Action\Product;
use App\Entity\Product;
use Doctrine\ORM\EntityManager;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class SoftDeleteAction
{
/**
* #var EntityManager
*/
private $entityManager;
/**
* #param EntityManager $entityManager
*/
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
/**
* #Route(
* name="app_product_delete",
* path="products/{id}/delete"
* )
*
* #Method("DELETE")
*
* #param Product $product
*
* #return Response
*/
public function __invoke(Request $request, $id): Response
{
$product = $this->entityManager->find(Product::class, $id);
$product->delete();
$this->entityManager->flush();
return new Response('', 204);
}
}
The question is a bit vague for stackoverflow though it's also a bit interesting. So here are some configure details.
Start with an out of the box S4 skeleton project:
symfony new --version=lts s4api
cd s4api
bin/console --version # 4.4.11
composer require orm-pack
Add the SoftDeleteAction
namespace App\Action\Product;
class SoftDeleteAction
{
private $entityManager;
public function __construct(EntityManagerInterface $entityManager)
{
$this->entityManager = $entityManager;
}
public function __invoke(Request $request, int $id) : Response
{
return new Response('Product ' . $id);
}
}
And define the route:
# config/routes.yaml
app_product_delete:
path: /products/{id}/delete
controller: App\Action\Product\SoftDeleteAction
At this point the wiring is almost complete. If you go to the url you get:
The controller for URI "/products/42/delete" is not callable:
The reason is that services are private by default. Normally you would extend from AbstractController which takes care of making the service public but in this case the quickest approach is to just tag the action as a controller:
# config/services.yaml
App\Action\Product\SoftDeleteAction:
tags: ['controller.service_arguments']
At this point you should have a working wired up action.
There of course many variations and a few more details. You will want to restrict the route to POST or fake DELETE.
You might also consider adding an empty ControllerServiceArgumentsInterface and then using the services instanceof functionality to apply the controller tag so you no longer need to manually define your controller services.
But this should be enough to get you started.
The approach I was trying to implement is named as ADR pattern (Action-Domain-Responder) and Symfony has already supported this started from 3.3 version. You can refer to it as Invokable Controllers.
From official docs:
Controllers can also define a single action using the __invoke() method, which is a common practice when following the ADR pattern (Action-Domain-Responder):
// src/Controller/Hello.php
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* #Route("/hello/{name}", name="hello")
*/
class Hello
{
public function __invoke($name = 'World')
{
return new Response(sprintf('Hello %s!', $name));
}
}
I'm trying to validate my entity via static callback.
I was able to make it work following the Symfony guide but something isn't clear to me.
public static function validate($object, ExecutionContextInterface $context, $payload)
{
// somehow you have an array of "fake names"
$fakeNames = array(/* ... */);
// check if the name is actually a fake name
if (in_array($object->getFirstName(), $fakeNames)) {
$context->buildViolation('This name sounds totally fake!')
->atPath('firstName')
->addViolation()
;
}
}
It works fine when I populate my $fakeNames array but what if I want to make it "dynamic"? Let's say I want to pick that array from the parameters or from the database or wherever.
How am I supposed to pass stuff (eg. the container or entityManager) to this class from the moment that the constructor doesn't work and it has to be necessarily static?
Of course my approach may be completely wrong but I'm just using the symfony example and few other similar issues found on the internet that I'm trying to adapt to my case.
You can create a Constraint and Validator and register it as service so you can inject entityManager or anything you need, you can read more here:
https://symfony.com/doc/2.8/validation/custom_constraint.html
or if you are on symfony 3.3 it is already a service and you can just typehint it in your constructor:
https://symfony.com/doc/current/validation/custom_constraint.html
This is the solution I was able to find in the end.
It works smoothly and I hope it may be useful for someone else.
I've set the constraint on my validation.yml
User\UserBundle\Entity\Group:
constraints:
- User\UserBundle\Validator\Constraints\Roles\RolesConstraint: ~
Here is my RolesConstraint class
namespace User\UserBundle\Validator\Constraints\Roles;
use Symfony\Component\Validator\Constraint;
class RolesConstraint extends Constraint
{
/** #var string $message */
public $message = 'The role "{{ role }}" is not recognised.';
public function getTargets()
{
return self::CLASS_CONSTRAINT;
}
}
and here is my RolesConstraintValidator class
<?php
namespace User\UserBundle\Validator\Constraints\Roles;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class RolesConstraintValidator extends ConstraintValidator
{
/** #var ContainerInterface */
private $containerInterface;
/**
* #param ContainerInterface $containerInterface
*/
public function __construct(ContainerInterface $containerInterface)
{
$this->containerInterface = $containerInterface;
}
/**
* #param \User\UserBundle\Entity\Group $object
* #param Constraint $constraint
*/
public function validate($object, Constraint $constraint)
{
if (!in_array($object->getRole(), $this->containerInterface->getParameter('roles'))) {
$this->context
->buildViolation($constraint->message)
->setParameter('{{ role }}', $object->getRole())
->addViolation();
}
}
}
Essentially, I set up a constraint which, every time a new user user is registered along with the role, that role must be among those set in the parameters. If not, it builds a violation.
I would like to know how to extend a custom repository and call the extended repository from doctrine entity manager in doctrine.
My Entity class :
/**
* #ORM\Entity(repositoryClass="Vendor\MyBundle\Repository\MyEntityRepository")
*/
class MyEntity
{
...
My Entity Repository class:
class MyEntityRepository extends EntityRepository
{
...
My Extended Repository class:
class MyExtendedEntityRepository extends MyEntityRepository
{
...
Call of MyEntityRepository:
class MyEntityManager
{
protected $emr;
/**
*
* #return MyEntityRepository
*/
public function getRepository()
{
return $this->emr->getRepository('MyVendorBundle:MyEntity');
}
...
Call of MyExtendedEntityRepository?
class MyOtherEntityManager
{
protected $emr;
/**
*
* #return MyExtendedEntityRepository
*/
public function getRepository()
{
//This is what i want to know: How to access to the extended repository?
}
...
Thanks
You can't, doctrine getRepository() works with the entity and resolve the repository related. I does not understand what is the logical or use case, but if you need reuse some part of repository throw other entities its recommended the use of traits. In the other hand if you really need use that scenario you can simply build MyExtendedEntityRepository in the getRepository method.
/**
*
* #return MyExtendedEntityRepository
*/
public function getRepository()
{
$class = $this->emr->getClassMetadata(MyEntity::class);
return new MyExtendedEntityRepository($this->emr, $class);
}
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.
I´ve created my own user Bundle, extending FOSUserBundle.
In my UserBundle, I have a memberID field, with its setters and getters.
I can already find users by memberID using EntityManager, and then I could find the user through UserManager matching the username/email/... obtained with that EntityManager query, but...
Is there a way to findUserByMemberID using UserManager?
Thank you.
Thank you for your replies. It seems it´s easier than all this stuff.
OwnUserBundle:
/**
* #ORM\Column(type="string")
*
*/
protected $memberID;
public function getMemberID()
{
return $this->memberID;
}
public function setMemberID($memberID)
{
$this->memberID = $memberID;
}
You can query FOSUserBundle:
$userManager = $this->get('fos_user.user_manager');
$user = $userManager->findUserBy(array('memberID' => '123'));
Then, using method findUserBy(array(*OwnUserBundle_field* => *search_parameter_value*)) you´ll get the user/s instance.
Every query that is "not standard" one has to be written into a repository class
You have also to relate this class with one that represent you data model.
Suppose that your entity is called User, you have to do something like this
/**
* VendorName\UserBundle\Entity\User
*
* #ORM\Table(name="users")
* #ORM\Entity(repositoryClass="VendorName\UserBundle\Repository\UserRepository")
*/
class User implements AdvancedUserInterface
{
[...]
This says that every "custom" query for that entity will be fit into that repository class.
Now you have to create the repository
class UserRepository extends EntityRepository implements UserProviderInterface
{
public function findUserByMemberID($id)
{
/* your logic here */
}
[...]
and you can use that in the following way
$userRepo = $this->em->getRepository('VendorUserBundle:User');
$userRepo->findUserByMemberID();
You could extend the UserManager from the FOSUserBundle in your bundle and write your own method. And you could follow these instructions http://symfony.com/doc/current/book/doctrine.html#custom-repository-classes