Symfony Service setting NULL data - symfony

I've been having several issues with creating a service in Symfony 2.0, but have found workarounds for everything but the service setting null data.
public function logAction($request, $entityManager)
{
$logs = new Logs();
$logs->setRoute = $request->get('_route');
$logs->setController = $request->get('_controller');
$logs->setRequest = json_encode($request->attributes->all());
$logs->setPath = $request->server->get('PATH_INFO');
$logs->setIp = $request->server->get('REMOTE_ADDR');
$em = $entityManager;
$em->persist($logs);
$em->flush();
}
I'm passing the EntityManager when calling the service in another controller, I'll post that code just in case:
public function pageAction($id = null, Request $request)
{
$log = $this->get('logging');
$log->logAction($request, $this->getDoctrine()->getEntityManager());
return $this->render('AcmeDemoBundle:Demo:viewPage.html.twig', array('name' => $id));
}
I have created a services.yml file following the official docs (in the Log Bundle) and an actual row is inserted, but all fields are set to NULL. If I try to do a die in one of the setters in the entity file it doesn't die, so it seems like something isn't making it. (I have both the EntityManager and Entity files used at the top of the log service file before anyone asks)
Any help would be greatly appreciated.

$logs->setRoute = $request->get('_route');
Should be:
$logs->setRoute($request->get('_route'));
For D2 all the accessors need to be methods.

Related

How to override the PUT operation update process in the Api Platform

I'm trying to override the PUT operation to perform my actions under certain conditions. That is, if the sent object is different from the original object (from the database), then I need to create a new object and return it without changing the original object.
Now when I execute the query I get a new object, as expected, but the problem is that the original object also changes
Entity
#[ApiResource(
operations: [
new Get(),
new GetCollection(),
new Post(controller: CreateAction::class),
new Put(processor: EntityStateProcessor::class),
],
paginationEnabled: false
)]
class Entity
EntityStateProcessor
final class PageStateProcessor implements ProcessorInterface
{
private ProcessorInterface $decorated;
private EntityCompare $entityCompare;
public function __construct(ProcessorInterface $decorated, EntityCompare $entityCompare)
{
$this->decorated = $decorated;
$this->entityCompare = $entityCompare;
}
public function process($data, Operation $operation, array $uriVariables = [], array $context = [])
{
if (($this->entityCompare)($data)) { // checking for object changes
$new_entity = clone $data; // (without id)
// do something with new entity
return $this->decorated->process($new_entity, $operation, $uriVariables, $context);
}
return $data;
}
}
I don't understand why this happens, so I return a clone of the original object to the process. It would be great if someone could tell me what my mistake is.
I also tried the following before returning the process
$this->entityManager->refresh($data); - Here I assumed that the original instance of the object will be updated with data from the database and the object will not be updated with data from the query
$this->entityManager->getUnitOfWork()->detach($data); - Here I assumed that the object would cease to be manageable and would not be updated
But in both cases the state of the original $data changes.
I'm using ApiPlatform 3.0.2
The error is that the main entity is related to an additional entity, so it's not enough to detach the main entity from UnitOfWork. So use the Doctrine\ORM\UnitOfWork->clear(YourEntity::class) method to detach all instances of the entity, and you do the same for relationships.
Once the entity is detach, cloning the entity becomes pointless because the previous entity instance isn't managed by the Doctrine ORM, so my code rearranges itself like this:
public function process($data, Operation $operation, array $uriVariables = [], array $context = [])
{
if (($this->entityCompare)($data)) { // checking for object changes
$this->getEntityManager()->getUnitOfWork()->clear(Entity::class);
$this->getEntityManager()->getUnitOfWork()->clear(EelatedEntity::class);
// do something with new entity
return $this->decorated->process($data, $operation, $uriVariables, $context);
}
return $data;
}

Passing an attribute to Doctrine AbstractIdGenerator

I need to pass LoggerInterface to the MyGenerator used in #ORM\CustomIdGenerator(class=MyGenerator::class)
Doctrine does not use the symfony container to instantiate the generator and I'm ending up with an Exception Too few arguments to function How can I use the LoggerInterface in my id generator ?
Unfortunately, it's not possible to inject LoggerInterface into MyGenerator class, as it's not a service and has nothing to do with the service container. However, in AbstractIdGenerator there is an EntityManager available, which provides a foundation for a workaround solution in order to propagate logs via a database table. After that, you'll be able to fetch log messages from a table via cronjob and write proper logs or do whatever you need.
class MyGenerator extends AbstractIdGenerator
{
public function generate(EntityManager $em, $entity)
{
$identifier = '...'; // generate an identifier
// push a log message to a db
$query = $em->createQuery('INSERT INTO db.logger (id, message, created_at) VALUES (null, :message, NOW())');
$query->setParameter('message', 'Log message...');
$query->execute();
return $identifier;
}
}

Best practice Symfony2 entity management

There is a lots of ways to manage entities in Symfony2, but I don't know which is the better
Solution 1: In the controller
public function myAction()
{
$myEntity = new MyEntity();
$form = $this->createForm($myEntityType, $myEntity);
...
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager()
$em->persist($myEntity);
$em->flush()
}
...
}
Solution 2: Using custom entityManager
public function myAction()
{
$myEntityManager = $this->get('manager.my_entity');
$myEntity = $myEntityManager->create();
$form = $this->createForm($myEntityType, $myEntity);
...
if ($form->isValid()) {
$myEntityManager->update($myEntity);
}
...
}
Solution 3: Using factory
public function myAction()
{
$myEntityFactory = $this->get('factory.my_entity');
$myEntity = $myEntityFactory->create();
$form = $this->createForm($myEntityType, $myEntity);
...
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager()
$em->persist($myEntity);
$em->flush()
}
...
}
I prefer the solution 2, but peoples told me this is not a single responsibility pattern because you have a factory and a method to update into it. The solution is maybe to use the factory in the manager, but it brings a lits of complexity.
Controller
public function myAction()
{
$myEntity = $this->get('entity_repository')->getBySomething();
$form = $this->createForm($myEntityType, $myEntity);
...
if ($form->isValid()) {
$this->get('entity_persister')->updateEntity($myEntity);
}
...
}
Your repository has the only responsability to give you model objects.
The domain service 'entity_persister' has the responsability to persist the given data to the model
Note that this solution is not the cleanest way because your form is directly mapped to an object representing the database table. I would recommend if you want to have a cleaner architecture to keep the model object and the object mapped by the form different.
Depends solely on a use case, IMO. There's no single "best way".
The question is: what do you actually do with this entity in your app? And how many times you need to access the same methods?
If only in one place, then creating a service or anything outside a controller could be totally uncessary.
Factory design pattern has its own usages, so it's a separate matter to be analysed (regardless the entity).
I'd go with 1st option for most of use cases, though, when you need to just create an Entity:
$myEntity = new MyEntity();
Why? Because code doesn't hide anything. Come on, MyEntity is just a Plain Object, it doesn't need any service or manager or (in most cases) factory. The other two seem to be a bad practice then, as they hide what is really going on there (unless Factory is needed).

How to define findOneBy($criteria) function?

I am very new to Symfony2, designing a simple login system. The userclass,
router, controlerclass everything is working fine. I am stuck to
userRepository class.
My controller part is:
public function loginProcessAction(Request $request){
if($request->getMethod() == "POST") {
$username = $request->get('username');
$password = $request->get('password');
$em= $this->getDoctrine()->getEntityManager();
$repository = $em->getRepository("LoginLoginBundle:Student");
$user = $repository->findOneBy(array('username'=>$username,
'password'=>$password));
if($user){
return $this->render('loginSuccess twig page') ;
}
else{
return $this->render('error twig page') ;
}
} else{
return $this->render("login error page");
}
}
How to define findOneBy(username, password) function in reopository class.
This is not the best way to handle authentication when using Symfony2. Take a look at the Security component integrated with Symfony2.
So check How Security Works: Authentication and Authorization part of the security documentation, all you need to implement/configure is Firewalls to handle Authentication and
Access Controls for Authorization.
But ...
Here's an answer to the common question: How to a define findOneBy(parameter1, parameter2) function for a given repository class?
First, map your entity to the appropriate repository as follow,
/*
* #ORM\Entity(repositoryClass="YourNamespace\YourBundle\Entity\yourRepository")
*/
class YourEntity
{
// ...
}
You should then add the mapped repository class and implement a findOneBy(parameter1, parameter2) method.
You can then access this class within your controller as follow,
$em= $this->getDoctrine()->getManager();
$yourEntityInstance = $em->getRepository("yourNamespaceYourBundle:YourEntity")
->findOneBy($parameter1, $parameter2);

Alter service (ClientManager) based on configuration inside bundle extension class

I have a bundle named: "ApiBundle". In this bundle I have the class "ServiceManager", this class is responsible for retrieving a specific Service object. Those Service objects needs to be created based on some configuration, so after this piece of code in my bundle extension class:
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
$loader = new Loader\YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
$loader->load('services.yml');
// Create Service objects...
I create those Service objects right after I have processed the configuration, something like this:
foreach ($services as $name => $service) {
$service = new Service();
$service->setName($name);
$manager = $container->get($this->getAlias() . '.service_manager');
$manager->add($service);
}
Unfortunately, this does not work, probably because the container isn't compiled yet. So I tried to add those Service objects the following way:
$manager = $container->getDefinition($this->getAlias() . '.service_manager');
$manager->addMethodCall('add', array($service));
But again, this throws the following exception: RuntimeException: Unable to dump a service container if a parameter is an object or a resource.
I can't seem to get a grasp on how to use the service container correctly. Does someone knows how I can add those Service objects to the ServiceManager (which is a service) inside the bundle extension class?
This is how the configuration of the bundle looks like:
api_client:
services:
some_api:
endpoint: http://api.yahoo.com
some_other_api:
endpoint: http://api.google.com
Every 'service' will be a seperate Service object.
I hope I explained it well enough, my apologies if my english is incorrect.
Steffen
EDIT
I think I may have solved the problem, I made a Compiler Pass to manipulate the container there with the following:
public function process(ContainerBuilder $container)
{
$services = $container->getParameter('mango_api.services');
foreach ($services as $name => $service) {
$clientManager = $container->getDefinition('mango_api.client_manager');
$client = new Definition('Mango\Bundle\ApiBundle\Client\Client', array($name, 'client', 'secret'));
$container->setDefinition('mango_api.client.' .$name, $client);
$clientManager->addMethodCall('add', array($client));
}
}
Is this appropriate?
To create services based on configuration you need to create compiler pass and enable it.
Compiler passes give you an opportunity to manipulate other service
definitions that have been registered with the service container.
I think I may have solved the problem, I made a Compiler Pass to manipulate the container there with the following:
public function process(ContainerBuilder $container)
{
$services = $container->getParameter('mango_api.services');
foreach ($services as $name => $service) {
$clientManager = $container->getDefinition('mango_api.client_manager');
$client = new Definition('Mango\Bundle\ApiBundle\Client\Client', array($name, 'client', 'secret'));
$client->setPublic(false);
$container->setDefinition('mango_api.client.' .$name, $client);
$clientManager->addMethodCall('add', array($client));
}
}

Resources