Using Symfony AttributeBags in a Controller - symfony

I've read about sessions here:
https://symfony.com/doc/current/components/http_foundation/sessions.html
If I use the code directly, Symfony logins using Guard don't work on the first attempt (first time always fails, second time succeeds). So I'm guessing that I have to use the session at $this->container->get('session'). But adding bags to that baffles me.
I've tried this:
/**
* #Route("/", name="homepage")
*/
public function indexAction(Request $request) {
$session=$this->container->get('session');
$cart=new AttributeBag('ShoppingCartItems');
$session->registerBag($cart);
$session->start();
...but it tells me
Cannot register a bag when the session is already started.
I've tried this:
...in the bundle:
public function build(ContainerBuilder $container) {
parent::build($container);
$container->addCompilerPass(new \Website\DependencyInjection\Compiler\InjectShoppingCart());
}
...and then this for InjectShoppingCart
namespace Website\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
class InjectShoppingCart implements CompilerPassInterface {
public function process(ContainerBuilder $container) {
$bagDefinition = new Definition();
$bagDefinition->setClass(AttributeBag::class);
$bagDefinition->addArgument("ShoppingCartItems");
$bagDefinition->addMethodCall("setName", ["ShoppingCartItems"]);
$container->setDefinition("ShoppingCartItemsService",$bagDefinition);
$container->getDefinition("session")->addMethodCall("registerBag",[new Reference("ShoppingCartItemsService")]);
}
}
...which I tried to use, Cargo Cult style, from question 44723613. Whatever it does, it does not result in the registration of an attribute bag.
No matter what I've tried so far, either Guard authentication breaks, or else the bag doesn't get registered. I want to do both, though, hopefully without installing Megs and Megs of external libraries.
It can't be this hard. What have I missed?

Okay, I figured it out.
In the end, it was just one line from the example I was using that was making it fail for me:
$bagDefinition->addArgument("ShoppingCartItems");
...has to be deleted.
What this all amounts to is that in the compiler pass, instead of making it do this:
$bag = new AttributeBag('ShoppingCartItems');
$bag->setName('ShoppingCartItems');
$session->registerBag($bag);
I needed to make it do this:
$bag = new AttributeBag();
$bag->setName('ShoppingCartItems');
$session->registerBag($bag);
So InjectShoppingCart ends up looking like this:
namespace Website\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
class InjectShoppingCart implements CompilerPassInterface {
public function process(ContainerBuilder $container) {
$bagDefinition = new Definition();
$bagDefinition->setClass(AttributeBag::class);
$bagDefinition->addMethodCall("setName", ["ShoppingCartItems"]);
$container->setDefinition("ShoppingCartItemsService",$bagDefinition);
$container->getDefinition("session")->addMethodCall("registerBag",[new Reference("ShoppingCartItemsService")]);
}
}

Related

Symfony get current User entity in controller

I'm beginner in Symfony (6.1) and sometimes I need to get the current User in my controllers.
The way I use for the moment is :
$user = $userRepository->find($this->getUser()->getId());
But they are a better way ?
Because $this->getUser() give me the UserInterface and I need the User entity. screenshot example
Thanks to read me
Using $this->getUser() will get you the current user (that implements UserInterface). The getUser() method lives in AbstractController.php that is part of the Symfony FrameworkBundle. You could extend this controller if you really want to change the getUser() method to not use the UserInterface, but I think it would be better to simply change the typehint of the function (setUser) you call (in your screenshot -please write this out next time-) to use the UserInterface. Something like this:
public function setUser(UserInterface $user)
{
//....
}
Edit:
After consideration, thanks to Cerad's valid point below, it is probably best to use setUser(User $user) given that you might use another class that implements UserInterface. Then either use var annations to tell your IDE which class you use, like so:
/** #var User $user */
$user = $this->getUser();
Or extend AbstractController so that it has a getUser method with a User return type like so:
namespace App\Controller;
use App\Entity\User;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class BaseController extends AbstractController
{
protected function getUser(): ?User
{
if (!$this->container->has('security.token_storage')) {
throw new \LogicException('The SecurityBundle is not registered in your application. Try running "composer require symfony/security-bundle".');
}
if (null === $token = $this->container->get('security.token_storage')->getToken()) {
return null;
}
return $token->getUser();
}
}
or maybe even better, like this:
/**
* #return User|null
*/
protected function getUser(): ?UserInterface
{
return parent::getUser();
}
I'm a bit puzzled by some of the other answers so it might be that I am the one who is confused. The basic problem is that getUser is typehinted to return an UserInterface but the actual User object is needed. My solution is to explicitly tell the IDE what getUser is actually returning:
use App\Entity\User; # or whetever the application defined user is
class MyController extends AbstractController
{
public function someMethod()
{
/** #var User */
$user = $this->getUser();
And everyone, especially the IDE, is happy.
Once again you need to do what is basically a typecast because the security system really only cares about the UserInterface and does not worry about anything else the user might have. Hence we have:
class AbstractController {
protected function getUser(): ?UserInterface {
A bit off-topic but I am looking forward to the day when this bit of annotation is no longer needed and replace with something like:
User $user = $this->getUser();
You have many ways to get your current user, please try this one:
$creditCard= new creditCard();
$creditCard->setUser($this->getUser());
Or this one:
$creditCard= $this->get('security.token_storage')->getToken()->getUser();

Symfony 4 using Logger and Dump inside a Controller

I'm new to Symfony 4, I'm using profiler to debug in a dev environment.
I'm trying to debug a function in controller/ But I can't use a logger or a dump nothing appear.
<?php
namespace App\Controller;
use Sonata\AdminBundle\Controller\CRUDController as Controller;
use Sonata\AdminBundle\Datagrid\ProxyQueryInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Psr\Log\LoggerInterface;
class CRUDController extends Controller
{
private $logger;
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
public function batchActionDownload(ProxyQueryInterface $selectedModelQuery)
{
$this->logger->debug('exampleMethod :: $ourVar at interesting time', [
'our_var' => $selectedModelQuery
]);
var_dump($selectedModelQuery);
dump($selectedModelQuery);
return new RedirectResponse(
$this->admin->generateUrl('list', $this->admin->getFilterParameters())
);
}
I have no error, but simply my debug isn't showing up.
Any Idea of what I'm doing bad ?
Normally the best way to define the route is to use the annotation. You are new and i don't how know you have defined your route.
But if seems that your functions isn't called. So try to set a route and call it in your browser.
/**
* #Route("/mycrud", name="mycrud")
*/
public function batchActionDownload(ProxyQueryInterface $selectedModelQuery)
Then you should be able to open your function in the browser under http://yourdomain/mycrud. Then you should see the debug bar and your var_dump.
The next thing is you redirect in your function. So you output something and redirect. You can't see the output in that place. You have to stop the code before output with an exit for example.

Symfony 4 AbstractController Issue with Parameter Count

I'm trying to write an API in Symfony 4. I've hit a problem with my controller methods when trying to use DependencyInjection for a service API class I created. I've tried several different ways to write the code and can not figure it out.
https://symfony.com/doc/current/components/dependency_injection.html
I can make a getNext() (instead of get() below) method and the code will function as expected, but if I try to use a get() method I will get an error. These are the basic classes involved. Most of the code has been removed.
class AppointmentController extends AbstractController
{
/**
* #Route("/appointment/getNext", name="appointment/getNext")
*
*/
public function get(string $id = null, CernerFhir $fhirApi)
{
$request = Request::createFromGlobals();
...more code...
}
}
class CernerFhir
{
public function __construct(LoggerInterface $logger, ParameterBagInterface $params)
{
$this->logger = $logger;
$this->params = $params;
}
}
}
Warning: Declaration of App\Controller\AppointmentController::get(?string $id, App\Service\CernerFhir $fhirApi) should be compatible with Symfony\Bundle\FrameworkBundle\Controller\AbstractController::get(string $id)
AbstractController uses an interface that defines a get() method with a specific number of parameter and return type. If you wan't to overwrite it's get method (which i do no recommend), you have to write it so that it's compatible with it's definition in the interface.
http://php.net/manual/en/language.oop5.interfaces.php

Best way to create a test database and load fixtures on Symfony 2 WebTestCase?

I have a WebTestCase that executes some basic routes in my application.
I want to, on the setUp method of PHPUnit, create a test database identical to my main database, and load fixtures into it.
I'm currently doing some workaround and executing some console commands, something like this:
class FixturesWebTestCase extends WebTestCase
{
protected static $application;
protected function setUp()
{
self::runCommand('doctrine:database:create');
self::runCommand('doctrine:schema:update --force');
self::runCommand('doctrine:fixtures:load --purge-with-truncate');
}
protected static function runCommand($command)
{
$command = sprintf('%s --quiet', $command);
return self::getApplication()->run(new StringInput($command));
}
protected static function getApplication()
{
if (null === self::$application) {
$client = static::createClient();
self::$application = new Application($client->getKernel());
self::$application->setAutoExit(false);
}
return self::$application;
}
}
But I'm quite sure this is not the best approach, especially because the doctrine:fixtures:load expects the user to hit a Y char to confirm the action.
How can I solve that?
If you want to use doctrine:fixtures:load, you can use the --append option to avoid the user confirmation. Since you are recreating the database every time, purging is unnecessary. I used to use doctrine fixtures alone for testing, but have since switched to using fixtures & LiipFunctionalTestBundle to avoid DRY. This bundle makes fixtures easier to manage.
EDIT: David Jacquel's answer is the correct one for loading Doctrine Fixtures:
doctrine:fixtures:load --no-interaction
or
doctrine:fixtures:load -n
In order to bypass user confirmation you can use
doctrine:fixtures:load --no-interaction
or
doctrine:fixtures:load -n
UPDATED ANSWER
You can create a base class for your test cases which makes fixture loading easy by leveraging some classes from the Doctrine Data Fixtures library. This class would look pretty much like this:
<?php
use Doctrine\Common\DataFixtures\Executor\ORMExecutor;
use Doctrine\Common\DataFixtures\FixtureInterface;
use Doctrine\Common\DataFixtures\Purger\ORMPurger;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
abstract class FixtureAwareTestCase extends KernelTestCase
{
/**
* #var ORMExecutor
*/
private $fixtureExecutor;
/**
* #var ContainerAwareLoader
*/
private $fixtureLoader;
public function setUp()
{
self::bootKernel();
}
/**
* Adds a new fixture to be loaded.
*
* #param FixtureInterface $fixture
*/
protected function addFixture(FixtureInterface $fixture)
{
$this->getFixtureLoader()->addFixture($fixture);
}
/**
* Executes all the fixtures that have been loaded so far.
*/
protected function executeFixtures()
{
$this->getFixtureExecutor()->execute($this->getFixtureLoader()->getFixtures());
}
/**
* #return ORMExecutor
*/
private function getFixtureExecutor()
{
if (!$this->fixtureExecutor) {
/** #var \Doctrine\ORM\EntityManager $entityManager */
$entityManager = self::$kernel->getContainer()->get('doctrine')->getManager();
$this->fixtureExecutor = new ORMExecutor($entityManager, new ORMPurger($entityManager));
}
return $this->fixtureExecutor;
}
/**
* #return ContainerAwareLoader
*/
private function getFixtureLoader()
{
if (!$this->fixtureLoader) {
$this->fixtureLoader = new ContainerAwareLoader(self::$kernel->getContainer());
}
return $this->fixtureLoader;
}
}
Then, in your test case, simply extend the above class and before your test add all the needed fixtures and execute them. This will automatically purge your database before loading fixtures. Example follows:
class MyTestCase extends FixtureAwareTestCase
{
public function setUp()
{
parent::setUp();
// Base fixture for all tests
$this->addFixture(new FirstFixture());
$this->addFixture(new SecondFixture());
$this->executeFixtures();
// Fixtures are now loaded in a clean DB. Yay!
}
}
OLD ANSWER
(I decided to "deprecate" this answer because it only explains how to clean up the database without telling how to load fixtures after).
There's an even cleaner way of accomplishing this without having to run commands. It basically consists in using a combination of the SchemaTool and the ORMPurger. You can create an abstract base class which performs this kind of operations to avoid repeating them for each specialized test case. Here's a code example of a test case class which sets up database for a generic test case:
use Doctrine\Common\DataFixtures\Purger\ORMPurger;
use Doctrine\ORM\Tools\SchemaTool;
abstract class DatabaseAwareWebTestCase extends WebTestCase {
public static function setUpBeforeClass() {
parent::setUpBeforeClass();
$kernel = static::createKernel();
$kernel->boot();
$em = $kernel->getContainer()->get('doctrine')->getManager();
$schemaTool = new SchemaTool($em);
$metadata = $em->getMetadataFactory()->getAllMetadata();
// Drop and recreate tables for all entities
$schemaTool->dropSchema($metadata);
$schemaTool->createSchema($metadata);
}
protected function tearDown() {
parent::tearDown();
$purger = new ORMPurger($this->getContainer()->get('doctrine')->getManager());
$purger->setPurgeMode(ORMPurger::PURGE_MODE_TRUNCATE);
$purger->purge();
}
}
This way, before running each test case which inherits from the above class, the database schema will be rebuilt from scratch, then cleaned up after every test run.
Hope this helps.
I've stumbled upon a really neat bundle named Doctrine-Test-Bundle
Instead of creating and dropping schema on every test it simply rollback.
My Tests went from 1m40s to.. 2s. And it's isolated.
All you need is a clear test database and it'll do the trick.
I used this command:
yes | php app/console doctrine:fixtures:load --purge-with-truncate
But of course LiipFunctionalTestBundle looks promising.
I wanted to load all your fixtures like the doctrine:fixtures:load command does. I didn't want to run exec from inside the test case because it seemed like a messy way to do things. I looked at how the doctrine command does this itself and just copied over the relevant lines.
I extended from the Symfony WebTestCase and after the Kernel was created I just called my method which works exactly like the Doctrine load-fixtures command.
/**
* Load fixtures for all bundles
*
* #param Kernel $kernel
*/
private static function loadFixtures(Kernel $kernel)
{
$loader = new DataFixturesLoader($kernel->getContainer());
$em = $kernel->getContainer()->get('doctrine')->getManager();
foreach ($kernel->getBundles() as $bundle) {
$path = $bundle->getPath().'/DataFixtures/ORM';
if (is_dir($path)) {
$loader->loadFromDirectory($path);
}
}
$fixtures = $loader->getFixtures();
if (!$fixtures) {
throw new InvalidArgumentException('Could not find any fixtures to load in');
}
$purger = new ORMPurger($em);
$executor = new ORMExecutor($em, $purger);
$executor->execute($fixtures, true);
}
Just recently the bundle hautelook/AliceBundle expose two traits to help you solve the use case of loading fixtures in functional tests: RefreshDatabaseTrait and ReloadDatabaseTrait.
From the doc:
namespace App\Tests;
use Hautelook\AliceBundle\PhpUnit\RefreshDatabaseTrait;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class NewsTest extends WebTestCase
{
use RefreshDatabaseTrait;
public function postCommentTest()
{
$client = static::createClient(); // The transaction starts just after the boot of the Symfony kernel
$crawler = $client->request('GET', '/my-news');
$form = $crawler->filter('#post-comment')->form(['new-comment' => 'Symfony is so cool!']);
$client->submit($form);
// At the end of this test, the transaction will be rolled back (even if the test fails)
}
}
And you are good !

How to inject service into Symfony 2 Data Fixtures?

How can I inject a service into Symfony2/Doctrine2 Data Fixtures? I want to create dummy users and need the security.encoder_factory service to encode my passwords.
I tried defining my Data Fixture as a service
myapp.loadDataFixture:
class: myapp\SomeBundle\DataFixtures\ORM\LoadDataFixtures
arguments:
- '#security.encoder_factory'
Then in my Data Fixture
class LoadDataFixtures implements FixtureInterface {
protected $passwordEncoder;
public function __construct($encoderFactory) {
$this->passwordEncoder = $encoderFactory->getEncoder(new User());
}
public function load($em) {
But got something like
Warning: Missing argument 1 for
...\DataFixtures\ORM\LoadDataFixtures::__construct(), called in ...
Want to improve this post? Provide detailed answers to this question, including citations and an explanation of why your answer is correct. Answers without enough detail may be edited or deleted.
The Using the Container in the Fixtures section describes exactly what you need.
All you need to do is to implement the ContainerAwareInterface in your fixture. This will cause the Symfony to inject the container via Setter-Injection. An example entity would look like this:
class LoadDataFixtures implements FixtureInterface, ContainerAwareInterface {
/**
* #var ContainerInterface
*/
private $container;
public function setContainer(ContainerInterface $container = null)
{
$this->container = $container;
}
public function load($em) {
You don't need to register the fixture as a service. Make sure to import the used classes via use.
For DoctrineFixturesBundle v. 3, you don't need to inject the container to inject a simple service. You can use normal dependency injection instead:
// src/DataFixtures/AppFixtures.php
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
// ...
private $encoder;
public function __construct(UserPasswordEncoderInterface $encoder)
{
$this->encoder = $encoder;
}
However if you do need the container, you can access it via the $this->container property.
Documentation here.

Resources