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
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.
I have some trouble since two days to do a query using a UserRepository outside a controller. I am trying to get a user from the database from a class that I named ApiKeyAuthenticator. I want to execute the query in the function getUsernameForApiKey like in the docs. I think I am suppose to use donctrine as a service but I don't get how to do this.
Thanks for you help in advance!
<?php
// src/AppBundle/Security/ApiKeyUserProvider.php
namespace AppBundle\Security;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\User;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
class ApiKeyUserProvider implements UserProviderInterface
{
public function getUsernameForApiKey($apiKey)
{
// Look up the username based on the token in the database, via
// an API call, or do something entirely different
$username = ...;
return $username;
}
public function loadUserByUsername($username)
{
return new User(
$username,
null,
// the roles for the user - you may choose to determine
// these dynamically somehow based on the user
array('ROLE_API')
);
}
public function refreshUser(UserInterface $user)
{
// this is used for storing authentication in the session
// but in this example, the token is sent in each request,
// so authentication can be stateless. Throwing this exception
// is proper to make things stateless
throw new UnsupportedUserException();
}
public function supportsClass($class)
{
return User::class === $class;
}
}
You have to make your ApiKeyUserProvider a service and inject the UserRepository as a dependency. Not sure if repositories are services in 2.8, so maybe you'll have to inject the EntityManager .
class ApiKeyUserProvider implements UserProviderInterface
{
private $em;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
public function loadUserByUsername($username)
{
$repository = $this->em->getRepository(User::class);
// ...
Now register your class as a service in your services.yml file
services:
app.api_key_user_provider:
class: AppBundle\Security\ApiKeyUserProvider
arguments: ['#doctrine.orm.entity_manager']
I know how to generate an URL for a route. However now I need to generate an URL for a controller or for a controller with a method. I checked the sourced of UrlGenerator but did not find any relevant information. No information in Symfony docs as well.
The method of the controller has an associate url. This url will be used in controller but I need the generator to be a service.
Basically you need to:
1. Add a route to the controller
<?php
declare(strict_types=1);
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
final class BlogController extends AbstractController
{
/**
* #Route(path="blog", name="blog")
*/
public function __invoke(): Response
{
return $this->render('blog/blog.twig');
}
}
2. Generate url route for a route
See Symfony docs
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
class SomeService
{
/**
* #var UrlGeneratorInterface
*/
private $urlGenerator;
public function __construct(UrlGeneratorInterface $urlGenerator)
{
$this->urlGenerator = $urlGenerator;
}
public function go()
{
// ...
// generate a URL with no route arguments
$signUpPage = $this->urlGenerator->generateUrl('sign_up');
// generate a URL with route arguments
$userProfilePage = $this->urlGenerator->generateUrl('user_profile', [
'username' => $user->getUsername(),
]);
// generated URLs are "absolute paths" by default. Pass a third optional
// argument to generate different URLs (e.g. an "absolute URL")
$signUpPage = $this->urlGenerator->generateUrl('sign_up', [], UrlGeneratorInterface::ABSOLUTE_URL);
// when a route is localized, Symfony uses by default the current request locale
// pass a different '_locale' value if you want to set the locale explicitly
$signUpPageInDutch = $this->urlGenerator->generateUrl('sign_up', ['_locale' => 'nl']);
}
}
So, here is the service. At least an example of how it could implemented. SF4
namespace App\Route;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\RouterInterface;
class UrlGenerator
{
/**
* #var RouteCollection
*/
private $collection;
public function __construct(RouterInterface $router)
{
$this->collection = $router->getRouteCollection();
}
public function generate(string $controllerClass, string $method): string
{
foreach ($this->collection as $item) {
$defaults = $item->getDefaults();
$controllerPath = $defaults['_controller'];
$parts = explode('::', $controllerPath);
if ($parts[0] !== $controllerClass) {
continue;
}
if ($parts[1] !== $method) {
continue;
}
return $item->getPath();
}
throw new \RuntimeException(
'Route for such combination of controller and method is absent'
);
}
}
Poorly tested but working solution.
I would like to use entity manager inside entity and no idea for usage.
use Doctrine\Common\Persistence\ObjectManagerAware;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
use SomeBundle\Entity\Boarding;
use SomeBundle\Entity\User;
class Entity extends ApiUserEntity implements ObjectManagerAware
{
private $em;
public function ___construct(User $user)
{
$this->board = $this->getData(123);
}
public function injectObjectManager(ObjectManager $objectManager, ClassMetadata $classMetadata)
{
$this->em = $objectManager;
}
private function getData($leadId)
{
//return gettype($this->em); //return null
$repository =$this->em->getRepository(Boarding::class);
$query = $repository->createQueryBuilder('b')
->where('b.lead = :lead')
->setParameter('lead', $leadId)
->getQuery();
$boards = $query->getResult();
return $boards;
}
}
Using this code get me error
Call to a member function getRepository() on null"
The entity manager is null also
//return gettype($this->em); //return null
Any idea for example usage?
You can try to create a repository like here. Just add
* #ORM\Entity(repositoryClass="App\Repository\EntityRepository")
or to YAML, Xml depends on your configuration and then create the repository file. Like this one:
// src/AppBundle/Repository/ProductRepository.php
namespace AppBundle\Repository;
use Doctrine\ORM\EntityRepository;
class ProductRepository extends EntityRepository
{
public function findAllOrderedByName()
{
return $this->getEntityManager()
->createQuery(
'SELECT p FROM AppBundle:Product p ORDER BY p.name ASC'
)
->getResult();
}
}
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
));
}
}