I want to split my codebase to simple one purpose specific classes like:
class AddKeyword
{
/**
* #var KeywordRepository
*/
private $keywordRepository;
public function __construct(KeywordRepository $keywordRepository)
{
$this->keywordRepository = $keywordRepository;
}
public function __invoke(string $name): Keyword
{
$entity = $this->keywordRepository->findOneByName($name);
if ($entity)
return $entity;
$entity = Keyword::create(KeywordId::create(), $name);
$this->keywordRepository->save($entity);
return $entity;
}
}
But for using that class I have to resolve DI. How to do it?
Thank you in advance.
Not sure about what you want to achieve but if you want add/get keyword everywhere in your code base you have 2 choices:
Use autowiring
Declare your class as service and get it from the container.
Symfony encourage DI by autowiring.
namespace App\Controller;
use App\AddKeyword;
class DefaultController
{
public function __construct(Addkeyword $keyword) {
$keyword('keyword');
}
}
Related
As my IDE points out, the AbstractController::getDoctrine() method is now deprecated.
I haven't found any reference for this deprecation neither in the official documentation nor in the Github changelog.
What is the new alternative or workaround for this shortcut?
As mentioned here:
Instead of using those shortcuts, inject the related services in the constructor or the controller methods.
You need to use dependency injection.
For a given controller, simply inject ManagerRegistry on the controller's constructor.
use Doctrine\Persistence\ManagerRegistry;
class SomeController {
public function __construct(private ManagerRegistry $doctrine) {}
public function someAction(Request $request) {
// access Doctrine
$this->doctrine;
}
}
You can use EntityManagerInterface $entityManager:
public function delete(Request $request, Test $test, EntityManagerInterface $entityManager): Response
{
if ($this->isCsrfTokenValid('delete'.$test->getId(), $request->request->get('_token'))) {
$entityManager->remove($test);
$entityManager->flush();
}
return $this->redirectToRoute('test_index', [], Response::HTTP_SEE_OTHER);
}
As per the answer of #yivi and as mentionned in the documentation, you can also follow the example below by injecting Doctrine\Persistence\ManagerRegistry directly in the method you want:
// src/Controller/ProductController.php
namespace App\Controller;
// ...
use App\Entity\Product;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\HttpFoundation\Response;
class ProductController extends AbstractController
{
/**
* #Route("/product", name="create_product")
*/
public function createProduct(ManagerRegistry $doctrine): Response
{
$entityManager = $doctrine->getManager();
$product = new Product();
$product->setName('Keyboard');
$product->setPrice(1999);
$product->setDescription('Ergonomic and stylish!');
// tell Doctrine you want to (eventually) save the Product (no queries yet)
$entityManager->persist($product);
// actually executes the queries (i.e. the INSERT query)
$entityManager->flush();
return new Response('Saved new product with id '.$product->getId());
}
}
Add code in controller, and not change logic the controller
<?php
//...
use Doctrine\Persistence\ManagerRegistry;
//...
class AlsoController extends AbstractController
{
public static function getSubscribedServices(): array
{
return array_merge(parent::getSubscribedServices(), [
'doctrine' => '?'.ManagerRegistry::class,
]);
}
protected function getDoctrine(): ManagerRegistry
{
if (!$this->container->has('doctrine')) {
throw new \LogicException('The DoctrineBundle is not registered in your application. Try running "composer require symfony/orm-pack".');
}
return $this->container->get('doctrine');
}
...
}
read more https://symfony.com/doc/current/service_container/service_subscribers_locators.html#including-services
In my case, relying on constructor- or method-based autowiring is not flexible enough.
I have a trait used by a number of Controllers that define their own autowiring. The trait provides a method that fetches some numbers from the database. I didn't want to tightly couple the trait's functionality with the controller's autowiring setup.
I created yet another trait that I can include anywhere I need to get access to Doctrine. The bonus part? It's still a legit autowiring approach:
<?php
namespace App\Controller;
use Doctrine\Persistence\ManagerRegistry;
use Doctrine\Persistence\ObjectManager;
use Symfony\Contracts\Service\Attribute\Required;
trait EntityManagerTrait
{
protected readonly ManagerRegistry $managerRegistry;
#[Required]
public function setManagerRegistry(ManagerRegistry $managerRegistry): void
{
// #phpstan-ignore-next-line PHPStan complains that the readonly property is assigned outside of the constructor.
$this->managerRegistry = $managerRegistry;
}
protected function getDoctrine(?string $name = null, ?string $forClass = null): ObjectManager
{
if ($forClass) {
return $this->managerRegistry->getManagerForClass($forClass);
}
return $this->managerRegistry->getManager($name);
}
}
and then
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use App\Entity\Foobar;
class SomeController extends AbstractController
{
use EntityManagerTrait
public function someAction()
{
$result = $this->getDoctrine()->getRepository(Foobar::class)->doSomething();
// ...
}
}
If you have multiple managers like I do, you can use the getDoctrine() arguments to fetch the right one too.
From the examples I'm finding in the Symfony docs, it looks like the typical thing to do when needing to save data is something like in the controller class:
public function createAction(){
$product = new Product();
$product->setName('Amy Keyboard');
$product->setPrice(24.99);
$product->setDescription('Ergonomic and stylish!');
$em = $this->getDoctrine()->getManager();
$em->persist($product);
$em->flush();
return $this->render('index.html.twig');
}
It would be really great to not have to type those 3 $em lines in every single controller method! And it would be even sweeter to move all of this logic to a class somewhere else and then just call $product->saveProduct($data)! What is the best option here?
I usually create a manager class e.g. ProductManager and register it as service. I inject the EntityManager via setter injection and implement all the methods I need.
In your case this would look similar to this:
AppBundle/Product/ProductManager
namespace AppBundle\Product;
use Doctrine\ORM\EntityManager;
class ProductManager {
/** #var EntityManager */
private $entityManager;
public function setEntityManager (EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
public function getAll()
{
return $this->entityManager->createQuery('SELECT p FROM '.Product::class.' p')
->getResult();
}
public function add(Product $product, $flush = true)
{
$this->entityManager->persist($product);
if ( $flush ) {
$this->entityManager->flush($product);
}
}
public function byId($id)
{
// Fetch a product by id (note: No need to use DQL or the EntityRepository here either!)
return $this->entityManager->find(Product::class, $id);
}
}
app/config/services.yml
services:
app.product_manager:
class: AppBundle\Product\ProductManager
calls:
- [setEntityManager, ['#doctrine.orm.entity_manager']]
Controller
public function createAction(){
$product = new Product();
$product->setName('Amy Keyboard');
$product->setPrice(24.99);
$product->setDescription('Ergonomic and stylish!');
// add the product
$this->get('app.product_manager')->add($product);
return $this->render('index.html.twig');
}
Take a look at the Propel project if you want something like $product->save() but it is a totally different approach. This is the official bundle https://github.com/propelorm/PropelBundle/blob/3.0/README.markdown
In Symfony 2.8/3.0, with our fancy new security components, how do I get the currently logged User (i.e. FOSUser) object in a service without injecting the whole container?
Is it even possible in a non-hacky way?
PS: Let's not consider the "pass it to the service function as a parameter" for being trivially obvious. Also, dirty.
Inject security.token_storage service into your service, and then use:
$this->token_storage->getToken()->getUser();
as described here: http://symfony.com/doc/current/book/security.html#retrieving-the-user-object and here: http://symfony.com/doc/current/book/service_container.html#referencing-injecting-services
Works with Symfony 3.4, 4.x, 5.x & above. The Security utility class was introduced in Symfony 3.4.
use Symfony\Component\Security\Core\Security;
public function indexAction(Security $security)
{
$user = $security->getUser();
}
https://symfony.com/doc/3.4/security.html#always-check-if-the-user-is-logged-in
Using constructor dependency injection, you can do it this way:
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
class A
{
private $user;
public function __construct(TokenStorageInterface $tokenStorage)
{
$this->user = $tokenStorage->getToken()->getUser();
}
public function foo()
{
dump($this->user);
}
}
In symfo 4 :
use Symfony\Component\Security\Core\Security;
class ExampleService
{
private $security;
public function __construct(Security $security)
{
$this->security = $security;
}
public function someMethod()
{
$user = $this->security->getUser();
}
}
See doc : https://symfony.com/doc/current/security.html#retrieving-the-user-object
From Symfony 3.3, from a Controller only, according this blog post: https://symfony.com/blog/new-in-symfony-3-2-user-value-resolver-for-controllers
It's easy as:
use Symfony\Component\Security\Core\User\UserInterface
public function indexAction(UserInterface $user)
{...}
With Symfony 5.2+ and PHP 8.0+ you can also get the logged user using the #[CurrentUser] attribute
namespace App\Controller;
use App\Entity\User;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Security\Http\Attribute\CurrentUser;
class FooController extends AbstractController
{
public function index(#[CurrentUser] ?User $user)
{
// ...
}
}
Blog post: https://symfony.com/blog/new-in-symfony-5-2-controller-argument-attributes
Documentation: https://symfony.com/doc/current/security.html
Symfony does this in Symfony\Bundle\FrameworkBundle\ControllerControllerTrait
protected function getUser()
{
if (!$this->container->has('security.token_storage')) {
throw new \LogicException('The SecurityBundle is not registered in your application.');
}
if (null === $token = $this->container->get('security.token_storage')->getToken()) {
return;
}
if (!is_object($user = $token->getUser())) {
// e.g. anonymous authentication
return;
}
return $user;
}
So if you simply inject and replace security.token_storage, you're good to go.
if you class extend of Controller
$this->get('security.context')->getToken()->getUser();
Or, if you has access to container element..
$container = $this->configurationPool->getContainer();
$user = $container->get('security.context')->getToken()->getUser();
http://symfony.com/blog/new-in-symfony-2-6-security-component-improvements
We've built up a set of data fixtures to seed the database with all our reference values. We are also using the DoctrineMigrationsBundle to manage schema updates. We would like to trigger the fixture load within our initial schema migration class so the system gets populated before running any additional schema updates.
I found in the docs that you can make migration classes container aware, but I can't figure out how to jump from that to calling/running the data fixtures. I haven't found any good answers on Stackoverflow or via google. Has anyone done this and can point me in the right direction? (or have suggestions on a better way to manage seed data in conjunction with schema migrations). Thanks.
This is using Symfony Version: 2.4
This is interesting question. I've found the "dirty" solution, but it works well.
namespace Application\Migrations;
use Doctrine\DBAL\Migrations\AbstractMigration;
use Doctrine\DBAL\Schema\Schema;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
class Version20140811164659 extends AbstractMigration implements ContainerAwareInterface
{
private $container;
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
public function up(Schema $schema)
{
// ... your code here
}
public function postUp(Schema $schema)
{
// here you have to define fixtures dir
$this->loadFixtures('src/Acme/BlogBundle/DataFixtures/ORM');
}
public function down(Schema $schema)
{
// ... your code here
}
public function loadFixtures($dir, $append = true)
{
$kernel = $this->container->get('kernel');
$application = new \Symfony\Bundle\FrameworkBundle\Console\Application($kernel);
$application->setAutoExit(false);
//Loading Fixtures
$options = array('command' => 'doctrine:fixtures:load', "--fixtures" => $dir, "--append" => (boolean) $append);
$application->run(new \Symfony\Component\Console\Input\ArrayInput($options));
}
}
This solution simply running console command php app/console doctrine:fixtures:load --fixtures=src/Acme/BlogBundle/DataFixtures/ORM --append after "up" migration.
Sorry for poore English. If you'll find clear solution, share it ;)
I've made a migration class to address this very problem. The code is essentially inspired from the doctrine:fixtures:load command.
<?php
namespace AppBundle\Migrations;
use Doctrine\Common\DataFixtures\Executor\ORMExecutor;
use Doctrine\Common\DataFixtures\FixtureInterface;
use Doctrine\Common\DataFixtures\Purger\ORMPurger;
use Doctrine\DBAL\Migrations\AbstractMigration;
use Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
abstract class AbstractFixturesAwareMigration extends AbstractMigration implements ContainerAwareInterface
{
private $container;
private $fixtures;
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
protected function getContainer()
{
return $this->container;
}
protected function addFixture(FixtureInterface $fixture)
{
if(null === $this->fixtures) {
$this->fixtures = new ContainerAwareLoader($this->getContainer());
}
$this->fixtures->addFixture($fixture);
return $this;
}
protected function executeFixtures($em = null, $append = true, $purgeMode = ORMPurger::PURGE_MODE_DELETE)
{
$em = $this->getContainer()->get('doctrine')->getManager($em);
$purger = new ORMPurger($em);
$purger->setPurgeMode($purgeMode);
$executor = new ORMExecutor($em, $purger);
$executor->execute($this->fixtures->getFixtures(), $append);
$this->fixtures = null;
return $this;
}
}
Usage is pretty straightforward:
<?php
namespace Application\Migrations;
use AppBundle\Migrations\AbstractFixturesAwareMigration
use Doctrine\DBAL\Schema\Schema;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Auto-generated Migration: Please modify to your needs!
*/
class Version20170726102103 extends AbstractFixturesAwareMigration
{
/**
* #param Schema $schema
*/
public function up(Schema $schema)
{
// this up() migration is auto-generated, please modify it to your needs
// [...]
}
public function postUp(Schema $schema)
{
// LoadMyData can be any fixture class
$this->addFixture(new LoadMyData());
$this->executeFixtures();
}
/**
* #param Schema $schema
*/
public function down(Schema $schema)
{
// this down() migration is auto-generated, please modify it to your needs
// [...]
}
}
Can somebody show me how I would inject validator into a regular class using dependency injection.
In my controller I have :
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Form;
class FormController extends Controller {
public function indexAction()
{
$form = new Form();
$email = $request->request->get('email');
$valid = $form->isValid($email);
}
}
and I want to use this custom class - but I need it got have access to validator.
class Form {
public function isValid($value)
{
// This is where I fail
$validator = $this->get('validator');
... etc
}
}
To do this, your custom class will need to be defined as a service, and you'll access it from the controller using $form = $this->get('your.form.service'); instead of instantiating it directly.
When defining your service, make sure to inject the validator service:
your.form.service:
class: Path\To\Your\Form
arguments: [#validator]
Then you'll need to handle this in the construct method of your Form service:
/**
* #var \Symfony\Component\Validator\Validator
*/
protected $validator;
function __construct(\Symfony\Component\Validator\Validator $validator)
{
$this->validator = $validator;
}
From Symfony2.5 on
Validator is called RecursiveValidator so, for injection
use Symfony\Component\Validator\Validator\RecursiveValidator;
function __construct(RecursiveValidator $validator)
{
$this->validator = $validator;
}
In Symfony 4+, if you use the default configuration (with auto wiring enabled), the easiest way is to inject a ValidatorInterface.
For instance:
<?php
use Symfony\Component\Validator\Validator\ValidatorInterface;
class MySuperClass
{
private $validator;
public function __construct(
ValidatorInterface $validator
) {
$this->validator = $validator;
}
Your Form class can inherit from ContainerAware (Symfony\Component\DependencyInjection\ContainerAware). Then you will have access to the container and you will be able to get the validator service like this:
$validator = $this->container->get('validator');