How can I use doctrine method inside my service (Symfony 4)? - php

I created my first own service in Symfony :
// src/Service/PagesGenerator.php
namespace App\Service;
class PagesGenerator
{
public function getPages()
{
$page = $this->getDoctrine()->getRepository(Pages::class)->findOneBy(['slug'=>$slug]);
$messages = [
'You did it! You updated the system! Amazing!',
'That was one of the coolest updates I\'ve seen all day!',
'Great work! Keep going!',
];
$index = array_rand($messages);
return $messages[$index];
}
}
But I get the error message:
Attempted to call an undefined method named "getDoctrine" of class
"App\Service\PagesGenerator".
I tried then to add in my services.yaml:
PagesGenerator:
class: %PagesGenerator.class%
arguments:
- "#doctrine.orm.entity_manager"
But then I get the error message:
The file "/Users/work/project/config/services.yaml" does not contain
valid YAML in /Users/work/project/config/services.yaml (which is
loaded in resource "/Users/work/project/config/services.yaml").

So, in comments I was saying that is better to let Symfony doing his job and autowiring EntityManager. This is what you should do. Also, can you tell us what Symfony version are you using and if autowiring is enabled (check services.yaml for that)?
<?php
namespace App\Service;
use Doctrine\ORM\EntityManagerInterface;
class PagesGenerator
{
public function __construct(EntityManagerInterface $em) {
$this->em = $em;
}
public function getPages()
{
$page = $this->em->getRepository(Pages::class)->findOneBy(['slug'=>$slug]);
$messages = [
'You did it! You updated the system! Amazing!',
'That was one of the coolest updates I\'ve seen all day!',
'Great work! Keep going!',
];
$index = array_rand($messages);
return $messages[$index];
}
}

With Symfony 4 and the new autowiring you can easily inject certain number of class
To find out, which classes/interface you can use for autowiring, use this command:
bin/console debug:autowiring
We are going to use this one :
Doctrine\ORM\EntityManagerInterface
(doctrine.orm.default_entity_manager)
So let's make it, add this just before getPages function
/**
* #var EntityManagerInterface
*/
private $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
Then you can use it like this:
$page = $this->em->getRepository(Pages::class)->findOneBy(['slug'=>$slug]);
Hope it helps !

make sure you use proper indent using "spaces" for YAML.
A YAML file use spaces as indentation, you can use 2 or 4 spaces for
indentation, but no tab
read more about this
Before symfony 3.3
for example we have service sms_manager in AppBundle/FrontEndBundle/Services
services:
AppBundle.sms_manager:
class: AppBundle\FrontEndBundle\Services\SmsManager
arguments: [ '#service_container' ,'#doctrine.orm.entity_manager' ]
then your service can receive your arguments in constructor
<?php
namespace AppBundle\FrontEndBundle\Services;
use Symfony\Component\DependencyInjection\ContainerInterface as Container;
class SmsManager {
private $container;
private $DM;
public function __construct( Container $container, \Doctrine\ORM\EntityManager $DM )
{
$this->container = $container;
$this->DM = $DM;
}
/**
* #return \Doctrine\ORM\EntityManager
*/
public function getDoctrine() {
return $this->DM;
}
}
With Symfony 3.3 or more,
Is there a way to inject EntityManager into a service
use Doctrine\ORM\EntityManagerInterface
class PagesGenerator
{
private $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
}
// ...
}

Related

How should one implement a custom Doctrine purger when using Symfony 6?

The Symfony docs shows a solution, but it doesn't appear to work (i.e. Doctrine\Bundle\FixturesBundle\Purger\PurgerFactory needs to be replaced with Doctrine\Bundle\FixturesBundle\Purger\ORMPurgerFactory, and other changes). I modified the code as shown below, but am pretty certain I am not doing it correctly.
<?php
declare(strict_types=1);
namespace App\DataFixtures\Purger;
use Doctrine\Bundle\FixturesBundle\Purger\PurgerFactory;
use Doctrine\Common\DataFixtures\Purger\PurgerInterface;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Bundle\FixturesBundle\Purger\ORMPurgerFactory;
class CustomPurgerFactory implements PurgerFactory
{
public function __construct(private ORMPurgerFactory $purgeFactory)
{
}
public function createForEntityManager(?string $emName, EntityManagerInterface $em, array $excluded = [], bool $purgeWithTruncate = false) : PurgerInterface
{
// Change $excluded, $purgeWithTruncate as desired.
return new CustomPurger($emName, $em, $excluded, $purgeWithTruncate, $this->purgeFactory);
}
}
<?php
declare(strict_types=1);
namespace App\DataFixtures\Purger;
use Doctrine\Common\DataFixtures\Purger\PurgerInterface;
use Doctrine\Common\DataFixtures\Purger\ORMPurgerInterface;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\Bundle\FixturesBundle\Purger\ORMPurgerFactory;
class CustomPurger implements ORMPurgerInterface
{
public function __construct(private ?string $emName, private EntityManagerInterface $entityManager, private array $excluded, private bool $purgeWithTruncate, private ORMPurgerFactory $purgeFactory)
{
}
public function setEntityManager(EntityManagerInterface $entityManager):void
{
// Seems rather redundent doing this even though I earlier inject $entityManager.
$this->entityManager = $entityManager;
}
public function purge() : void
{
// Delete any tables which must be deleted first to prevent FK constraint errors.
// This doesn't seem write.
$purger = $this->purgeFactory->createForEntityManager($this->emName, $this->entityManager, $this->excluded, $this->purgeWithTruncate);
$purger->purge();
}
}
services:
App\DataFixtures\Purger\DoctrinePurgerFactory:
tags:
- { name: 'doctrine.fixtures.purger_factory', alias: 'my_purger' }
arguments:
- '#doctrine.fixtures.purger.orm_purger_factory'
Or should it be done by decorating the default purger as suggested by this post?
Okay. So you do have a few things wrong and the docs are somewhat out of date. From a big picture point of view you want something like:
bin/console doctrine:fixtures:load --purger=my_purger
to use your custom purger factory (aliased as my_purger) to instantiate and execute your custom purger's purge method. The job of the factory is to just create the purger not to execute it.
I followed the docs and implemented PurgerInterface but the purge command complained about it not implementing ORMPurgerInterface which, as you noted, adds a seemingly superfluous method. I think it is still a work in progress. The default ORMPurger has a couple of additional public methods not defined in any interface which is also strange. The fact that Doctrine is inconsistent with it's usage of the Interface suffix does not help. But it is what it is.
This works under 6.1:
# CustomPurger.php
use Doctrine\Common\DataFixtures\Purger\ORMPurgerInterface;
use Doctrine\ORM\EntityManagerInterface;
class CustomPurger implements ORMPurgerInterface
{
private EntityManagerInterface $em;
public function setEntityManager(EntityManagerInterface $em) : void
{
$this->em = $em;
}
public function purge() : void
{
dd(' my purger');
}
}
# CustomPurgerFactory.php
use Doctrine\Bundle\FixturesBundle\Purger\PurgerFactory;
use Doctrine\Common\DataFixtures\Purger\PurgerInterface;
use Doctrine\ORM\EntityManagerInterface;
class CustomPurgerFactory implements PurgerFactory
{
public function createForEntityManager(?string $emName, EntityManagerInterface $em, array $excluded = [], bool $purgeWithTruncate = false) : PurgerInterface
{
return new CustomPurger($em);
}
}
# services.yaml
App\Purger\CustomPurgerFactory:
tags:
- { name: 'doctrine.fixtures.purger_factory', alias: 'my_purger' }
bin/console doctrine:fixtures:load --purger=my_purger
> purging database
^ " my purger"
As far as decorating goes, you decorate a service when you want to modify some methods without extending the original class. There is only one method here and it's quite a doozy so I don't think decorating will help.
If you wanted to always use your purger without the --purger option then you could probably point the default purger factory service id to your factory. I'll leave that as an exercise for the reader.
One final note: I took a look at your decorating link. Don't know what they were trying to do but I do know it has nothing to do with decorating.

Use parameters in Symfonybundle in service class

I have Symfony bundle called upload images:
I want to use parameters in my bundle in my class.
This is my parameter file:
upload-images:
image:
crop_size: 300
My files:
Configuration.php
class Configuration implements ConfigurationInterface
{
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder('upload-images');
$treeBuilder->getRootNode()
->children()
->arrayNode('image')
->children()
->integerNode('save_original')->end()
->scalarNode('crop_size')->end()
->end()
->end() // twitter
->end();
return $treeBuilder;
}
}
UploadImagesExtension.php
class UploadImagesExtension extends Extension
{
public function load(array $configs, ContainerBuilder $container)
{
$loader = new YamlFileLoader($container, new FileLocator(dirname(__DIR__).'/Resources'));
$loader->load('services.yaml');
$configuration = new Configuration();
$config = $this->processConfiguration($configuration, $configs);
}
}
And final my service class:
Rotate.php
And in this class I want the parameter: crop_size
I tried the ParameterBagInterface:
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
class Rotate
{
private $params;
public function __construct(ParameterBagInterface $params)
{
$this->params = $params;
}
public function Rotate()
{
$cropSize = $params->get('crop_size');
}
}
UserController.php
use verzeilberg\UploadImagesBundle\Service\Rotate;
class UserController extends AbstractController
{
/** #var UserProfileService */
private $service;
private $userService;
public function __construct(
UserProfileService $service,
UserService $userService
) {
$this->service = $service;
$this->userService = $userService;
}
/**
* #param UserInterface $user
* #return Response
*/
public function profile(UserInterface $user)
{
$rotate = new Rotate();
$rotate->Rotate();
.....
...
}
Getting this error:
Too few arguments to function verzeilberg\UploadImagesBundle\Service\Rotate::__construct(), 0 passed in /home/vagrant/projects/diabetigraph-dev/src/Controller/User/UserController.php on line 62 and exactly 1 expected
I have search for a solution. But did not came accross the right one.
According to the latest edit, the error is pretty obvious: if you want to use dependency injection, you have to use it. Calling $rotate = new Rotate(); without any constructor parameters will fail, as Symfony cannot inject them for you.
Instead, inject it through the action:
public function profile(UserInterface $user, Rotate $rotate)
... this will use Symfony's container and inject the ParameterBagInterface, if you have enabled autowiring. If not, you have to write the proper service definitions to get this done
The error you are getting is not directly related to your question.
Either it is an autowiring issue, or maybe you are trying to instanciate the service manually? Please share what you are doing here: UserController.php on line 62
Anyway, to answer your question:
To access the parameters from the parameter bag you will have to set them in the extension.
$container->setParameter('my_bundle.config', $config);
Also, injecting the whole ParameterBag is fine for a project, but should be avoided for a bundle.
Use the DI config to inject just your parameter, OR make your extension implement CompilerPassInterface, and override the definition there. (It may be overkill for such a simple task)

Symfony 2.8 Services issue

Since the last 4 hours I'm trying to understand the logic of Symfony 2 services and how they integrate in the application...
Basically I'm trying to set my EntityManager via a service and use it in a controller
I have the following structure
Bundle1/Controller/Bundle1Controller.php
Bundle1/Services/EntityService.php
Bundle2/Controller/Bundle2Controller.php
Bundle3/Controller/Bundle3Controller.php
....
I'm trying to make a REST API with different entry points, that's why I use multiple bundles bundle2,bundle3....
The logic is the following:
A POST is fired to Bundle2/Controller/Bundle2Controller.php
Bundle2Controller.php instances a new() Bundle1Controller.php
Inside Bundle1Controller I want to access a service entity_service in order to get my EntityManager
I have 2 cases in which I manage to land...
In Bundle1/Controller/Bundle1Controller if I try $this->container or $this->get('entity_service') I get a null everytime
If I set the container in Bundle2/Controller/Bundle2Controller and try $this->get('entity_service') I get You have requested a non-existent service "entity_service"
I will place all the code below
Bundle1/Controller/Bundle1Controller
<?php
namespace Bundle1\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use EntityBundle\Entity\TestEntity;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
class Bundle1Controller extends Controller
{
/**
* #param $response
* #return array
*/
public function verifyWebHookRespone($response){
$em = $this->get('entity_service')->getEm();
$array = json_decode($response);
$mapping = $em->getRepository('EntityBundle:TestEntity')
->findBy(["phone" => $array['usernumber']]);
return $mapping;
}
}
Bundle2/Controller/Bundle2Controller.php
<?php
namespace Bundle2\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Bundle1\Controller\Bundle1Controller;
class Bundle2Controller extends Controller
{
public function webhookAction(Request $request)
{
$data = $request->request->get('messages');
$model = new Bundle1Controller();
$responseMessage = $model->verifyWebHookRespone($data);
return new Response($responseMessage, Response::HTTP_CREATED, ['X-My-Header' => 'My Value']);
}
}
Bundle1/Services/EntityService.php
<?php
namespace EntityBundle\Services;
use Doctrine\ORM\EntityManager;
use Symfony\Component\DependencyInjection\Container;
class EntityService
{
protected $em;
private $container;
public function __construct(EntityManager $entityManager, Container $container)
{
$this->em = $entityManager;
$this->container = $container;
}
/**
* #return EntityManager
*/
public function getEm()
{
return $this->em;
}
}
services.yml
services:
entity_service:
class: Bundle1\Services\EntityService
arguments: [ "#doctrine.orm.entity_manager" , "#service_container" ]
Can anyone please help me with something regarding this issue?
How can I register a service and call it from anywhere no matter the bundle or another service?
You should check where your services.yml is located and whether it is imported in the config.yml
You can't just instantiate a controller and expect it to work, you need to set the container.
But you can call EntityManager without needing any other service by using;
$this->get('doctrine.orm.entity_manager');
I can't understand your structure or what you are trying to achieve, but those are the options to go about if you want to keep this structure.

Access Container or securityContext or EntityManager from MenuBuilder through RequestVoter

I found this piece of code shared in a Gist (somewhere I lost the link) and I needed something like that so I started to use in my application but I have not yet fully understood and therefore I am having some problems.
I'm trying to create dynamic menus with KnpMenuBundle and dynamic means, at some point I must verify access permissions via database and would be ideal if I could read the routes from controllers but this is another task, perhaps creating an annotation I can do it but I will open another topic when that time comes.
Right now I need to access the SecurityContext to check if the user is logged or not but not know how.
I'm render the menu though RequestVoter (I think) and this is the code:
namespace PlantillaBundle\Menu;
use Knp\Menu\ItemInterface;
use Knp\Menu\Matcher\Voter\VoterInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Security\Core\SecurityContextInterface;
class RequestVoter implements VoterInterface {
private $container;
private $securityContext;
public function __construct(ContainerInterface $container, SecurityContextInterface $securityContext)
{
$this->container = $container;
$this->securityContext = $securityContext;
}
public function matchItem(ItemInterface $item)
{
if ($item->getUri() === $this->container->get('request')->getRequestUri())
{
// URL's completely match
return true;
}
else if ($item->getUri() !== $this->container->get('request')->getBaseUrl() . '/' && (substr($this->container->get('request')->getRequestUri(), 0, strlen($item->getUri())) === $item->getUri()))
{
// URL isn't just "/" and the first part of the URL match
return true;
}
return null;
}
}
All the code related to securityContext was added by me in a attempt to work with it from the menuBuilder. Now this is the code where I'm making the menu:
namespace PlantillaBundle\Menu;
use Knp\Menu\FactoryInterface;
use Symfony\Component\DependencyInjection\ContainerAware;
class MenuBuilder extends ContainerAware {
public function mainMenu(FactoryInterface $factory, array $options)
{
// and here is where I need to access securityContext
// and in the near future EntityManger
$user = $this->securityContext->getToken()->getUser();
$logged_in = $this->securityContext->isGranted('IS_AUTHENTICATED_FULLY');
$menu = $factory->createItem('root');
$menu->setChildrenAttribute('class', 'nav');
if ($logged_in)
{
$menu->addChild('Home', array('route' => 'home'))->setAttribute('icon', 'fa fa-list');
}
else
{
$menu->addChild('Some Menu');
}
return $menu;
}
}
But this is complete wrong since I'm not passing securityContext to the method and I don't know how to and I'm getting this error:
An exception has been thrown during the rendering of a template
("Notice: Undefined property:
PlantillaBundle\Menu\MenuBuilder::$securityContext in
/var/www/html/src/PlantillaBundle/Menu/MenuBuilder.php line 12") in
/var/www/html/src/PlantillaBundle/Resources/views/menu.html.twig at
line 2.
The voter is defined in services.yml as follow:
plantilla.menu.voter.request:
class: PlantillaBundle\Menu\RequestVoter
arguments:
- #service_container
- #security.context
tags:
- { name: knp_menu.voter }
So, how I inject securityContext (I'll not ask for EntityManager since I asume will be the same procedure) and access it from the menuBuilder?
Update: refactorizing code
So, following #Cerad suggestion I made this changes:
services.yml
services:
plantilla.menu_builder:
class: PlantillaBundle\Menu\MenuBuilder
arguments: ["#knp_menu.factory", "#security.context"]
plantilla.frontend_menu_builder:
class: Knp\Menu\MenuItem # the service definition requires setting the class
factory_service: plantilla.menu_builder
factory_method: createMainMenu
arguments: ["#request_stack"]
tags:
- { name: knp_menu.menu, alias: frontend_menu }
MenuBuilder.php
namespace PlantillaBundle\Menu;
use Knp\Menu\FactoryInterface;
use Symfony\Component\HttpFoundation\RequestStack;
class MenuBuilder {
/**
* #var Symfony\Component\Form\FormFactory $factory
*/
private $factory;
/**
* #var Symfony\Component\Security\Core\SecurityContext $securityContext
*/
private $securityContext;
/**
* #param FactoryInterface $factory
*/
public function __construct(FactoryInterface $factory, $securityContext)
{
$this->factory = $factory;
$this->securityContext = $securityContext;
}
public function createMainMenu(RequestStack $request)
{
$user = $this->securityContext->getToken()->getUser();
$logged_in = $this->securityContext->isGranted('IS_AUTHENTICATED_FULLY');
$menu = $this->factory->createItem('root');
$menu->setChildrenAttribute('class', 'nav');
if ($logged_in)
{
$menu->addChild('Home', array('route' => 'home'))->setAttribute('icon', 'fa fa-list');
}
else
{
$menu->addChild('Some Menu');
}
return $menu;
}
}
Abd ib my template just render the menu {{ knp_menu_render('frontend_menu') }} but now I loose the FontAwesome part and before it works, why?
Your menu builder is ContainerAware, so I guess that in it you should access the SecurityContext via $this->getContainer()->get('security.context').
And you haven't supplied any use cases for the voter class, so I'm guessing you're not using the matchItem method.
You should definitely try to restructure your services so that the dependencies are obvious.
Per your comment request, here is what your menu builder might look like:
namespace PlantillaBundle\Menu;
use Knp\Menu\FactoryInterface;
class MenuBuilder {
protected $securityContext;
public function __construct($securityContext)
{
$this->securityContext = $securityContext;
}
public function mainMenu(FactoryInterface $factory, array $options)
{
// and here is where I need to access securityContext
// and in the near future EntityManger
$user = $this->securityContext->getToken()->getUser();
...
// services.yml
plantilla.menu.builder:
class: PlantillaBundle\Menu\MenuBuilder
arguments:
- '#security.context'
// controller
$menuBuilder = $this->container->get('plantilla.menu.builder');
Notice that there is no need to make the builder container aware since you only need the security context service. You can of course inject the entity manager as well.
================================
With respect to the voter stuff, right now you are only checking to see if a user is logged in. So no real need for voters. But suppose that certain users (administrators etc) had access to additional menu items. You can move all the security checking logic to the voter. Your menu builder code might then look like:
if ($this->securityContext->isGranted('view','homeMenuItem')
{
$menu->addChild('Home', array('route' ...
In other words, you can get finer controller over who gets what menu item.
But get your MenuBuilder working first then add the voter stuff if needed.

FOSUserBundle : get repository from inside FormHandler

I need to set a default value to a new user before saving it.
The problem is that I can't find a way to get an object through its repository from inside the FormHandler.
<?php
namespace Acme\UserBundle\Form\Handler;
use FOS\UserBundle\Form\Handler\RegistrationFormHandler as BaseHandler;
use FOS\UserBundle\Model\UserInterface;
class RegistrationFormHandler extends BaseHandler
{
protected function onSuccess(UserInterface $user, $confirmation)
{
$repository = $this->container->get('doctrine')->getEntityManager()->getRepository('AcmeUserBundle:Photo');
if($user->isMale()){
$photo = $repository->getDefaultForMale();
$user->setPhoto($photo);
}
else {
$photo = $repository->getDefaultForFemale();
$user->setPhoto($photo);
}
parent::onSuccess($user, $confirmation);
}
}
The problem comes from the following line :
$repository = $this->container->get('doctrine')->getEntityManager()->getRepository('AcmeUserBundle:Photo');
... and I can't find a way to get this repository, or the entity manager from this FormHandler.
Many thanks for your help !
A
You have to define a service that reference your extended handler class and point it in app/config.yml. e.g
The class,
//namespace definitions
class MyHandler extends RegistrationFormHandler{
private $container;
public function __construct(Form $form, Request $request, UserManagerInterface $userManager, MailerInterface $mailer, ContainerInterface $container)
{
parent::__construct($form, $request, $userManager, $mailer);
$this->container = $container;
}
protected function onSuccess(UserInterface $user, $confirmation)
{
$repository = $this->container->get('doctrine')->getEntityManager()->getRepository('AcmeUserBundle:Photo');
// your code
}
The service,
my.registration.form.handler:
scope: request
class: FQCN\Of\MyHandler
arguments: [#fos_user.registration.form, #request, #fos_user.user_manager, #fos_user.mailer, #service_container]
Lastly in app/config.yml,
fos_user:
#....
registration:
#...
form:
handler: my.registration.form.handler
FOS got his own UserManager. Try to use this.

Categories