Create dynamic function with arguments using Symfony4 - symfony

I am new in symfony 4 and I've done the CRUD. I want to enhance my code by creating a function that will lessen it.
Example:
If you have 2 modules like manage event and announcement(ofcourse you will have here add,get all,delete, and update). Instead of having a long code like this.
$fetch_item = $this->getDoctrine()
->getRepository(Event::class)
->findAll();
I want to short it like $fetch = $this->fetch(Event::class); I created a new file in my Service directory.
Service\Crud.php
<?php
namespace App\Service;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
/**
*
*/
class Crud extends AbstractController
{
public function __construct(){
parent::__construct();
}
public function fetch($table)
{
$fetch_item = $this->getDoctrine()
->getRepository($table)
->findAll();
return $fetch_item;
}
}
?>
Controller
//
...
use App\Service\Crud;
...
class EventController extends AbstractController
public function index()
{
// $fetch_item = $this->getDoctrine()
// ->getRepository(Item::class)
// ->findAll();
$fetch = $this->fetch(Item::class);
return $this->render('base.html.twig',array(
'items' => $fetch_item
));
}
Above is my code but it gives me an error "Attempted to call an undefined method named "fetch" of class "App\Controller\ItemController""
Question: How can I create a function that will lessen my code?

There is no reason for the fetch function to be part of a controller (on the contrary there are lots of reasons not to be). What you need is a simple service:
<?php
namespace App\Service;
use Doctrine\ORM\EntityManagerInterface;
class CrudService {
protected $em;
public function __construct(EntityManagerInterface $em){
$this->em = $em;
}
public function fetch($entityClass) {
return $this->em->getRepository($entityClass)->findAll();
}
}
Then in your controller you just have to inject it through autowiring and use it:
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use App\Service\CrudService;
use App\Entity\Item;
...
class EventController extends AbstractController {
public function index(CrudService $crudService) {
$items = $crudService->fetch(Item::class);
return $this->render('base.html.twig',array(
'items' => $items
));
}
}

Related

Symfony 6 - Attempted to call an undefined method named "getDoctrine" [duplicate]

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.

Symfony Undefined index

I am creating an application that fetches and search for product name from different sources (DB, XML, JSON, ...)(for this code Im testing only with the DB), my idea was to create an interface for that.
I created the interface ProductRepositoryInterface and the class DoctrineProductRepository then I declared them both as services.
In my controller, I call the search function with the product name as param.
Here is my interface ProductRepositoryInterface :
namespace Tyre\TyreBundle\Repository;
interface ProductRepositoryInterface
{
function search(string $needle);
}
My interface DoctrineProductRepository:
namespace Tyre\TyreBundle\Repository;
class DoctrineProductRepository implements ProductRepositoryInterface
{
public function __constructor(EntityManager $em)
{
$this->em = $em;
}
public function search(string $needle)
{
$repository = $this->em->getRepository('TyreTyreBundle:Products');
$query = $repository->createQueryBuilder('u')
->where("u.name LIKE '%".$needle."%' or u.manufacturer LIKE '%".$needle."%'")
->getQuery();
return $query->getArrayResult();
}
}
My Service.yml
services:
Tyre\TyreBundle\Repository\DoctrineProductRepository:
class: Tyre\TyreBundle\Repository\DoctrineProductRepository
Tyre\TyreBundle\Repository\ProductRepositoryInterface:
class: Tyre\TyreBundle\Repository\ProductRepositoryInterface
and finally my controller :
namespace Tyre\TyreBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Tyre\TyreBundle\Repository\DoctrineProductRepository;
use Tyre\TyreBundle\Repository\ProductRepositoryInterface;
class DefaultController extends Controller
{
public function indexAction()
{
return $this->render('TyreTyreBundle:Default:search.html.twig');
}
public function searchAction(Request $request) {
$repositoryMap = [
'db' => DoctrineProductRepository::class,
];
$serviceName = $repositoryMap[$request->get('db')]; /***This is Line 56 ***/
/** #var ProductRepositoryInterface */
$repository = $this->get($serviceName);
$results = $repository->search($request->get('search_for'));
return $this->render('TyreTyreBundle:Default:detail.html.twig', array('results' => $results));
}
public function detailAction()
{
//forward the user to the search page when he tries to access directly to the detail page
return $this->render('TyreTyreBundle:Default:search.html.twig');
}
}
But I get an error :
EDIT
When I try http://localhost:8000/search?db=db , I get other error (I var_dumped $repositoryMap) :
click to view
Am I missing anything?
The reason for your 'ContextErrorException' is :
$request->get('search_for')
is empty because you are passing nothing in the url for that key. Pass 'search_for' also in addition with 'db' like:
http://localhost:8000/search?db=db&search_for=myvalue

Listener not triggered by the event dispatcher

I want to execute symfony function after returning response in controller.
How can I do that ?
Here are all classes I used:
Define all events : here we have only one event defined inside this class
<?php
# AcmeEvents.php
namespace test\apiBundle;
final class AcmeEvents
{
const AFTER_RETURN_RESPONSE = "acme.after_return_response";
}
Define the listener
<?php
#TestSubscriber.php
namespace test\apiBundle\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use test\apiBundle\AcmeEvents;
use Symfony\Component\Filesystem\Filesystem;
class TestSubscriber implements EventSubscriberInterface
{
public function __construct()
{
}
public static function getSubscribedEvents()
{
return array(
AcmeEvents::AFTER_RETURN_RESPONSE => 'processingFunction'
);
}
public function processingFunction(Event $event)
{
$fs = new Filesystem();
$fs->touch('files/myfile.json', 0700);
}
}
Define the controller where the event is triggered
<?php
#WelcomeController.php
namespace test\apiBundle\Controller;
#use all other components
use Symfony\Component\EventDispatcher\EventDispatcher;
use test\apiBundle\AcmeEvents;
class WelcomeController extends Controller
{
public function getWelcomeAction()
{
# all other processes
$event = new UploadEvent();
$dispatcher = new EventDispatcher();
$dispatcher->dispatch(
'AcmeEvents::AFTER_RETURN_RESPONSE', $event
);
return $response;
}
}
try to get a service dispatcher and not a new instance in your action :
Replace
$dispatcher = new EventDispatcher();
by
$this->get('event_dispatcher')->dispatch(....)

Definition : addMethodCall works but methods are never invoked

I want to use custom tags on my services, so I followed the instructions in the documentation: http://symfony.com/doc/2.8/components/dependency_injection/tags.html
I have a RulesHydrator class:
<?php
namespace TestBundle\Thruway;
class RulesHydrator
{
private $container;
private $manualChecks = [];
public function __construct($container)
{
$this->container = $container;
}
public function addManualCheck($service, $rule, $method)
{
echo 'addManualCheck invoked!'.PHP_EOL;
exit;
$this->manualChecks[$rule] = $service;
}
}
Here is the compiler pass:
<?php
namespace TestBundle\Thruway;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\Reference;
class ThruwayCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->has('thruway.rules_hydrator')) {
return;
}
$definition = $container->findDefinition('thruway.rules_hydrator');
foreach ($container->findTaggedServiceIds('thruway.manual_check') as $id => $tags) {
foreach ($tags as $attributes) {
$definition->addMethodCall('addManualCheck', [new Reference($id), $attributes['rule'], $attributes['method']]);
}
}
}
}
Here is my bundle's class:
<?php
namespace TestBundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use TestBundle\Thruway\ThruwayCompilerPass;
class TestBundle extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new ThruwayCompilerPass());
}
}
My services.yml file look like this:
services:
thruway.rules_hydrator:
class: TestBundle\Thruway\RulesHydrator
thruway.customer_checker:
class: TestBundle\Thruway\MyChecker
tags:
- { name: thruway.manual_check, rule: some.rule1, method: someMethod1 }
- { name: thruway.manual_check, rule: some.rule2, method: someMethod2 }
The process method is called and the different calls to addMethodCall on my Definition object work fine (the property "calls" of the definition is correctly filled). The problem is that the calls to my method addManualCheck never occur. Any idea why?
The case could be that you did not instantiated the service. By default from what I remember the services are lazy loaded, and until you actually fetch one from the container, or it is injected into an other service, it will not be initialized.
Can you look into your appProdProjectContainer.php inside app/cache/prod for the "TestBundle\Thruway\MyChecker" and post back how it is used?
Also try a fast check by getting the thruway.customer_checker from the container.
A fast command like this could help
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class TestCommand extends ContainerAwareCommand
{
protected function configure()
{
$this->setName('service:test')->setDescription('Test service functionalities');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$service = $this->getContainer()->get('thruway.customer_checker');
}
}

Attempted to load class "Sm_Image_HandlerController" from namespace Did you forget a "use" statement for another namespace?

create class Tools\MainBundle\Controller\Sm_Image_HandlerController
<?php
namespace Tools\MainBundle\Controller;
class Sm_Image_HandlerController{
public function test($param) {
return $param;
}
}
other class :
<?php
namespace FoodBundle\Controller;
use FoodBundle\Entity\FoodMaterialMapping;
use Symfony\Component\BrowserKit\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use FoodBundle\Entity\Food;
use Symfony\Component\Validator\Constraints\NotBlank;
use Tools\MainBundle\Controller\Sm_Image_HandlerController;
/**
* Food controller.
*
*/
class FoodController extends Controller {
public function indexAction() {
$param="sample";
$sm=new Sm_Image_HandlerController();
$sample=$sm->test($param);
return new \Symfony\Component\HttpFoundation\Response(sample);
}
if run function indexAction in class foodController
return error
Attempted to load class "Sm_Image_HandlerController" from namespace "Tools\MainBundle\Controller".
Did you forget a "use" statement for another namespace?
src/FoodBundle/Controller/FoodController.php at line 23
<?php
public function createAction(Request $request) {
$param = "sample";
$sm = new Sm_Image_HandlerController();
$sample = $sm->test($param);
return new \Symfony\Component\HttpFoundation\Response(sample);
}
you must use CamelCase naming for class and file name in symfony
rename your controller and its file like this
class SmImageHandlerController{
public function test($param) {
return $param;
}
}

Resources