I created the console command but during the execution I get the following error:
In ControllerTrait.php line 338:
Call to a member function has() on null
DatabaseHelper.php
<?php
namespace App\Controller;
use App\Entity\Currency;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use App\Controller\CurrencyComparison;
class DatabaseHelper extends AbstractController
{
public function insertValues()
{
$curFromAPI = new CurrencyComparison();
$curFromAPI = $curFromAPI->compareCurrencies();
$date = new \DateTime('#'.strtotime('now'));
$entityManager = $this->getDoctrine()->getManager();
$currency = new Currency();
$currency->setName('USD');
$currency->setAsk($curFromAPI->getUSD());
$currency->setLastUpdated($date);
$entityManager->persist($currency);
$entityManager->flush();
}
}
CurrencyComparison.php
<?php
namespace App\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use App\Service\DatabaseHelper;
class CurrencyComparison extends Command
{
private $databaseHelper;
public function __construct(DatabaseHelper $databaseHelper){
$this->$databaseHelper = $databaseHelper;
parent::__construct();
}
protected function configure()
{
$this
->setName('app:currency-comparison')
->setDescription('Inserts currencies into the database.')
->setHelp('Compares currencies from APIs and inserts the lower rates into the database.')
;
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->databaseHelper->insertValues();
$output->writeln([
'Currencies are being inserted into the database',
'============',
'',
]);
$output->writeln('Done.');
}
}
When debugging, noticed that I'm getting this error after the following line in DatabaseHelper.php:
$entityManager = $this->getDoctrine()->getManager();
What should I change or should be looking for?
Thanks.
--UPDATE--
I created a Service and tried to inject EntityManager as construct.
App/Service/DatabaseHelper.php
<?php
namespace App\Service;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class DatabaseHelper extends AbstractController
{
private $entityManager;
public function __construct(EntityManager $entityManager){
$this->entityManager = $entityManager;
}
public function insertValues()
{
$curFromAPI = new CurrencyComparison();
$curFromAPI = $curFromAPI->compareCurrencies();
$date = new \DateTime('#'.strtotime('now'));
$this->entityManager = $this->getDoctrine()->getManager();
return dd($curFromAPI);
$currency = new Currency();
$currency->setName('USD');
$currency->setAsk($curFromAPI->getUSD());
$currency->setLastUpdated($date);
$entityManager->persist($currency);
// actually executes the queries (i.e. the INSERT query)
$entityManager->flush();
}
}
and updated Command\CurrencyComparison.php too. But when I'm trying to call execute function on CurrencyComparison.php, I cannot reach its' $this->databaseHelper->insertValues(); function. Would you mind to give any suggestions?
--UPDATE & Solution--
Removed extends AbstractController from class DatabaseHelper.
Changed __construct of DatabaseHelper and injected as follows: (EntityManagerInterface $entityManager)
removed the following line from insertValues function in DatabaseHelper.php:
$this->entityManager = $this->getDoctrine()->getManager(); and just used persist and flush functions as like above.
As your command has a dependency on DatabaseHelper that has a dependency on the Entity Manager, create the DatabaseHelper class as a service, with the Entity manager injected in via its constructor (autowire it, or specify in its service definition).
then, as per the docs here you can inject your DatabaseHelper service into the command via the constructor as it'll it be autowired by default.
I would also consider not extending the AbstractController as your DatabaseHelper is very tight in scope and wont need the entire container (assuming its only what youve posted). Just inject what you need.
Related
I'm trying to create an SQLFilter for a query in my Symfony app.
The issue is that the filter is not applied on the query (and not called), even though is it enabled correctly (see below).
The repository is not linked to an entity, because the database is external to my app, but it still has access to the data.
Am I missing something ?
Here's the filter:
<?php
namespace App\SQL\Filter;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Query\Filter\SQLFilter;
class UserRoleFilter extends SQLFilter
{
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias)
{
return 'c.roleId = 1';
}
}
I registered it in config/packages/doctrine.yaml:
doctrine:
filters:
user_role: App\SQL\Filter\UserRoleFilter
The controller:
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Doctrine\Persistence\ManagerRegistry;
use App\Repository\CustomerRepository;
class CustomerController extends AbstractController
{
public function myAction(Request $request, ManagerRegistry $doctrine, CustomerRepository $customerRepository)
{
$doctrine->getManager()->getFilters()->enable('user_role');
$customers = $customerRepository->findAll();
}
}
The repository:
<?php
namespace App\Repository;
use Doctrine\DBAL\Connection;
use Doctrine\Persistence\ManagerRegistry;
use Doctrine\Persistence\ObjectManager;
class CustomerRepository
{
protected Connection $conn;
protected ObjectManager $em;
public function __construct(ManagerRegistry $doctrine)
{
$this->em = $doctrine->getManager();
$this->conn = $this->em->getConnection();
}
public function findAll(): array
{
dump($this->em->getFilters()->isEnabled('user_role')); // returns true
return $this->conn->createQueryBuilder()
->select('c.*')
->from('customer', 'c')
->executeQuery()
->fetchAllAssociative();
}
}
From looking at the source for Doctrine/DBAL, it doesn't look like the filter would ever be applied to the query you are executing.
Within your repository class you are creating an instance of Doctrine\DBAL\Query\QueryBuilder which only holds a reference to Doctrine\DBAL\Connection.
Then the select data is set to its private parameter $sqlParts.
When executeQuery() is called is concatenates the content of sqlParts into a string with no mention or reference to any filter objects nor their constraints. This can be seen on line 308 of the QueryBuilder class.
QueryBuilder::executeQuery()
You can also see how the select query is concatenated on line 1320 of QueryBuilder.
QueryBuilder::getSQLForSelect()
The only way I can see to add it easily would be to add it directly to a where clause, e.g.
public function findAll(): array
{
return $this->conn->createQueryBuilder()
->select('c.*')
->from('customer', 'c')
->where("c.roleId = 1") // Or pull it from the filter object in some way
->executeQuery()
->fetchAllAssociative();
}
If you want to see where the filter constraints are added to the queries you can find that data in the ORM package from Doctrine, however these are all linked to entities and table aliases.
SqlWalker::generateFilterConditionSQL()
BasicEntityPersistor::generateFilterConditionSQL()
ManyToManyPersistor::generateFilterConditionSQL()
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.
How can I mock a service in a functional test use-case where a "request"(form/submit) is being made. After I make the request all the changes and mocking I made to the container are lost.
I am using Symfony 4 or 5. The code posted here can be also found here: https://github.com/klodoma/symfony-demo
I have the following scenario:
SomeActions service is injected into the controller constructor
in the functional unit-tests I try to mock the SomeActions functions in order to check that they are executed(it sends an email or something similar)
I mock the service and overwrite it in the unit-tests:
$container->set('App\Model\SomeActions', $someActions);
Now in the tests I do a $client->submit($form); which I know that it terminates the kernel.
My question is: HOW can I inject my mocked $someActions in the container after $client->submit($form);
Below is a sample code I added to the symfony demo app
https://github.com/symfony/demo
in services.yaml
App\Model\SomeActions:
public: true
SomeController.php
<?php
namespace App\Controller;
use App\Model\SomeActions;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
/**
* Controller used to send some emails
*
* #Route("/some")
*/
class SomeController extends AbstractController
{
private $someActions;
public function __construct(SomeActions $someActions)
{
//just dump the injected class name
var_dump(get_class($someActions));
$this->someActions = $someActions;
}
/**
* #Route("/action", methods="GET|POST", name="some_action")
* #param Request $request
* #return Response
*/
public function someAction(Request $request): Response
{
$this->someActions->doSomething();
if ($request->get('send')) {
$this->someActions->sendEmail();
}
return $this->render('default/someAction.html.twig', [
]);
}
}
SomeActions
<?php
namespace App\Model;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Email;
class SomeActions
{
private $mailer;
public function __construct(MailerInterface $mailer)
{
$this->mailer = $mailer;
}
public function doSomething()
{
echo 'doSomething';
}
public function sendEmail()
{
echo 'sendEmail';
$email = (new Email())
->from('hello#example.com')
->to('you#example.com')
->subject('Time for Symfony Mailer!')
->text('Sending emails is fun again!')
->html('<p>See Twig integration for better HTML integration!</p>');
$this->mailer->send($email);
}
}
SomeControllerTest.php
<?php
namespace App\Tests\Controller;
use App\Model\SomeActions;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class SomeControllerTest extends WebTestCase
{
public function testSomeAction()
{
$client = static::createClient();
// gets the special container that allows fetching private services
$container = self::$container;
$someActions = $this->getMockBuilder(SomeActions::class)
->disableOriginalConstructor()
->getMock();
//expect that sendEmail will be called
$someActions->expects($this->once())
->method('sendEmail');
//overwrite the default service: class: Mock_SomeActions_e68f817a
$container->set('App\Model\SomeActions', $someActions);
$crawler = $client->request('GET', '/en/some/action');
//submit the form
$form = $crawler->selectButton('submit')->form();
$client->submit($form);
//after submit the default class injected in the controller is "App\Model\SomeActions" and not the mocked service
$response = $client->getResponse();
$this->assertResponseIsSuccessful($response);
}
}
The solution is to disable the kernel reboot:
$client->disableReboot();
It makes sense if ones digs deep enough to understand what's going on under the hood;
I am still not sure if there isn't a more straight forward answer.
public function testSomeAction()
{
$client = static::createClient();
$client->disableReboot();
...
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.
I have a command which executes some actions that depend on the entity passed in parameter.
checkAlertCommand.php:
<?php
namespace MDB\PlatformBundle\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class checkAlertCommand extends Command {
protected function configure() {
$this
->setName('platform:checkAlert')
->setDescription('Check the alert in in function of the current advert')
->addArgument(
'postedAdvert'
);
}
protected function execute(InputInterface $input, OutputInterface $output) {
$postedAdvert = $input->getArgument('postedAdvert');
$output->writeln($postedAdvert->getTitre());
}
}
?>
So my questions are:
How to get an entity as argument in the checkAlertCommand.php?
How to call this command from a controller and pass the desired entity as argument?
Thanks.
You can't pass an entity directly to a console command. Instead of you should pass "id" of entity as argument, then use repository and pick up desired entity by its id.
<?php
namespace MDB\PlatformBundle\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class checkAlertCommand extends ContainerAwareCommand {
protected function configure() {
$this
->setName('platform:checkAlert')
->setDescription('Check the alert in in function of the current advert')
->addArgument(
'postedAdvertId'
);
}
protected function execute(InputInterface $input, OutputInterface $output) {
$postedAdvertId = $input->getArgument('postedAdvertId');
$em = $this->getContainer()->get('doctrine')->getManager();
$repo = $em->getRepository('MDBPlatformBundle:PostedAdvert');
$postedAdvert = $repo->find($postedAdvertId);
$output->writeln($postedAdvert->getTitre());
}
}
?>
You should use Process component to run the command inside a controller.
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\NullOutput;
use MDB\PlatformBundle\Command\checkAlertCommand;
class MyController extends Controller
{
public function indexAction()
{
// get post $postedAdvertId here
....
$command = new checkAlertCommand();
$command->setContainer($this->container);
$input = new ArrayInput(array('postedAdvertId' => $postedAdvertId));
$output = new NullOutput();
$result = $command->run($input, $output);
...
}
}
Update: Answer to your question
I'm not sure what exactly do you mean "asynchronous", but given example executes the command in synchronous way, so mean the controller will wait till command will be finished and only then will go to next operation. But if you need run it in asynchronous(in background) way,you should use Process component http://symfony.com/doc/current/components/process.html