I'm learning symfony and I would like to have a search bar to show user with email. But I got and error
Attempted to call an undefined method named "getEntityManager" of class "App\Repository\SearchRepository".
If someone can help me or explain me how to do it's would be very nice. Thanks
In SearchRepository
class SearchRepository
{
public function findAllWithSearch($email){
$entityManager = $this->getEntityManager();
$query = $entityManager->createQuery(
'SELECT u
FROM App\Entity\User u
WHERE u.email :email'
)->setParameter('email', $email);
return $query->execute();
}
}
In SearchController
class SearchController extends AbstractController
{
/**
* #Route("/admin/search/", name="admin_search")
* #Security("is_granted('ROLE_ADMIN')")
*/
public function searchUser(SearchRepository $repository, Request $request)
{
$q = $request->query->get('search');
$comments = $repository->findllWithSearch($q);
return $this->render('admin/search/search.html.twig',
[ 'user' => $repository,
]);
}
}
and search.twig.html
<form action="" method="get">
<input type="search" name="search" value="" placeholder="Recherche.." />
<input type="submit" value="Valider" />
</form>
Quick Answer
SearchRepository needs to extend \Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository.
<?php
namespace App\Repository;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
class SearchRepository extends ServiceEntityRepository
public function findAllWithSearch($email)
{
$entityManager = $this->getEntityManager();
$query = $entityManager->createQuery(
'SELECT u
FROM App\Entity\User u
WHERE u.email :email'
)->setParameter('email', $email);
return $query->execute();
}
Fix your Application's Architecture
Looks like you're at the very beginning of your journey. The above code will fix your issue, but you need to pay attention to the architecture of your code.
Repository Classes are like containers for your entities.
You would not have a repository for Search (unless you're storing Search entities).
You would usually put this into a UserRepository. Which should be charged with the responsibility of being a repo for User Entity objects.
There are magic methods within Repositories that will allow you to find Entities.
Using your specific example, you could use something like
$repoInstance->findByEmail($email);
within your controller and this will return all records entities that
match your email address.
More about Working with Doctrine Repositories
For more information on how repositories work, consume and experiment with the documentation from this link:
https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/reference/working-with-objects.html
From the doctrine documentation:
A repository object provides many ways to retrieve entities of the
specified type. By default, the repository instance is of type
Doctrine\ORM\EntityRepository. You can also use custom repository
classes.
So, if your search is going to deal with User objects, you can use the standard UserRepository by doing the following in your Controller:
/**
* #Route("/admin/search/", name="admin_search")
* #Security("is_granted('ROLE_ADMIN')")
*/
public function searchUser(Request $request)
{
$q = $request->query->get('search');
// Get standard repository
$user = $this->getDoctrine()->getManager()
->getRepository(User::class)
->findBy(['email' => $q]); // Or use the magic method ->findByEmail($q);
// Present your results
return $this->render('admin/search/search_results.html.twig',
['user' => $user]);
}
There is no need for a custom repository for your use case, but if you want to create one and use it for autowiring you must extend ServiceEntityRepository, a container-friendly base repository class provided by Symfony. You can get more details in the documentation. In this case you might want to also review how to annotate your entity to tell the EntityManager that you'll be using a custom repository.
Sidenote: By default, the action attribute of the form defaults to the same route you are visiting, so if that fragment is part of a layout you'll have to set it explicitly to your SearchController action: action="{{ path('admin_search') }}"
If UserRepository already exists and extend ServiceEntityRepository try to move findAllWithSearch to UserRepository.
If not your SearchRepository must looks like this
/**
* #method User|null find($id, $lockMode = null, $lockVersion = null)
* #method User|null findOneBy(array $criteria, array $orderBy = null)
* #method User[] findAll()
* #method User[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class UserSearchRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, User::class);
}
public function findByEmail(string $email)
{
return $this->findBy(['email' => $email]);
}
}
Related
Thanks to the symfony doc https://symfony.com/doc/current/doctrine.html#updating-an-object, i am able to use get/set methods from my entity called 'Produit'.
But when i call the method setProduit() (from Paniers entity) Phpstorm say "expected App\Entity\Paniers, got Object" for the $produitSelected.
i don't know why phpstorm say that because i'm able to use methods, what's the problem ?
find() return an entity object, right ?
class PaniersController extends AbstractController
{
/**
* #Route("/paniers/add/{id}", name="paniers.add")
*/
public function addPanier(Request $request, Environment $twig, RegistryInterface $doctrine, $id)
{
$produitSelected = $doctrine->getRepository(Produit::class)->find($id);
if (!$produitSelected) {
throw $this->createNotFoundException(
'Pas de produit trouvé pour l\' id : '.$id
);
}
$lignePanier=$doctrine->getRepository(Paniers::class)->findOneBy(['produit' => $produitSelected, 'user' =>$this->getUser()]);
if($produitSelected->getStock()>0){
if ($lignePanier){
$quantite =$lignePanier->getQuantite();
$lignePanier->setQuantite($quantite+1);
} else {
$lignePanier = new Paniers();
$lignePanier->setUser($this->getUser())
->setDateAjoutPanier(new \DateTime(date('Y-m-d')))
->setQuantite(1)
->setProduit($produitSelected);
}
$doctrine->getManager()->persist($lignePanier);
$produitSelected->setStock($produitSelected->getStock()-1);
$doctrine->getManager()->persist($produitSelected);
$doctrine->getManager()->flush();
}
}
}
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\PaniersRepository")
* #ORM\Table(name="paniers")
*/
class Paniers
{
//...
/**
* #ORM\ManyToOne(targetEntity="Produit")
* #ORM\JoinColumn(name="produit_id", referencedColumnName="id")
*/
private $produit;
public function getProduit(): ?Produit
{
return $this->produit;
}
public function setProduit(?Produit $produit): self
{
$this->produit = $produit;
return $this;
}
}
In addition to Denis V's correct answer, I want to add that you could also modify your controller like this:
public function addPanier(Request $request, Environment $twig, RegistryInterface $doctrine,ProduitRepository $produitRepository, $id)
{
$produitSelected = $produitRepostiory->find($id);
//rest of the code
}
This way phpstorm also knows the type of the returned object, as every returned object is type hinted in the corresponding repository.
PhpStorm is apparently not that smart to understand that the actual type of the return value of find($id) in this case will be Produit. But you can help it:
/** #var Produit $produitSelected */
$produitSelected = $doctrine->getRepository(Produit::class)->find($id);
To make it working you should either use Produit with the full namespace or add the namespace directly within the type hint.
Of course this doesn't guarantee or ensure that the actual type will be Produit. So, if you make a mistake, PhpStorm will report the type incorrectly.
You first need to fix code :
Don't inject $id, you only need to typehint your entity: https://symfony.com/doc/current/best_practices/controllers.html#using-the-paramconverter
Don't inject $doctrine, use $em = $this->getDoctrine()->getManager();
Don't inject $twig, use return $this->render('...template', []);
Use English, it is always the rule. Not only other people could help you but also because Symfony understands it and you will need that when you start using form collections: https://symfony.com/doc/current/form/form_collections.html
Inject repository and you will have autocomplete and spot errors easier. Use ```make:entity`` command and you will see what I think, hard to explain.
I'm trying to call a method in the repository ArticleRepository from my controller ArticleController. However it says :
Undefined method 'afficheArticle'. The method name must start with either findBy or findOneBy!
my Entity Article : (Entity\Article.php)
/**
* Article
*
* #ORM\Table(name="Article", indexes={#ORM\Index(name="I_FK_Article_TypeArticle", columns={"idTypeArticle"})})
* #ORM\Entity(repositoryClass="erp-gkeep\new_erp\gkeepBundle\Repository\ArticleRepository")
*/
class Article
{
my ArticleController (Controller\ArticleController)
/**
* #Route("viewArticle2", name="viewArticle2")
*/
public function listAction2()
{
$data = $this->getDoctrine()->getRepository('gkeepBundle:Article')->afficheArticle();
my ArticleRepository
<?php
/**
* Created by PhpStorm.
*/
namespace gkeepBundle\Repository;
use Doctrine\ORM\EntityRepository;
class ArticleRepository extends EntityRepository
{
public function afficheArticle(){
$em=$this->getEntityManager();
$query = $em->createQuery(
'SELECT a.reference, a.designationfr, a.designationen, a.plan, a.url, a.datecreation, a.idtypearticle
FROM gkeepBundle:Article a
'
);
$articles = $query->getArrayResult();
return $articles;
}
}
if someone can help me please ! I'm pretty sure it's a stupid error :/
The value of repositoryClass in your mapping annotation needs to be the namespace + class name of your repository, it looks like you've maybe added some of the directory structure as well. - actually isn't a valid character in a PHP namespace, so the value you've got now definitely isn't right.
Try
#ORM\Entity(repositoryClass="gkeepBundle\Repository\ArticleRepository")
For reference: when this classname is not valid, Doctrine falls back to the default repository - this is what's throwing the error you're seeing.
I have a BaseController that provides the foundation for most HTTP methods for my API server, e.g. the store method:
BaseController.php
/**
* Store a newly created resource in storage.
*
* #return Response
*/
public function store(Request $request)
{
$result = $this->repo->create($request);
return response()->json($result, 200);
}
I then extend on this BaseController in a more specific controller, such as the UserController, like so:
UserController.php
class UserController extends BaseController {
public function __construct(UserRepository $repo)
{
$this->repo = $repo;
}
}
This works great. However, I now want to extend UserController to inject Laravel 5's new FormRequest class, which takes care of things like validation and authentication for the User resource. I would like to do this like so, by overwriting the store method and using Laravel's type hint dependency injection for its Form Request class.
UserController.php
public function store(UserFormRequest $request)
{
return parent::store($request);
}
Where the UserFormRequest extends from Request, which itself extends from FormRequest:
UserFormRequest.php
class UserFormRequest extends Request {
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
'name' => 'required',
'email' => 'required'
];
}
}
The problem is that the BaseController requires a Illuminate\Http\Request object whereas I pass a UserFormRequest object. Therefore I get this error:
in UserController.php line 6
at HandleExceptions->handleError('2048', 'Declaration of Bloomon\Bloomapi3\Repositories\User\UserController::store() should be compatible with Bloomon\Bloomapi3\Http\Controllers\BaseController::store(Illuminate\Http\Request $request)', '/home/tom/projects/bloomon/bloomapi3/app/Repositories/User/UserController.php', '6', array('file' => '/home/tom/projects/bloomon/bloomapi3/app/Repositories/User/UserController.php')) in UserController.php line 6
So, how can I type hint inject the UserFormRequest while still adhering to the BaseController's Request requirement? I cannot force the BaseController to require a UserFormRequest, because it should work for any resource.
I could use an interface like RepositoryFormRequest in both the BaseController and the UserController, but then the problem is that Laravel no longer injects the UserFormController through its type hinting dependency injection.
In contrast to many 'real' object oriented languages, this kind of type hinting design in overridden methods is just not possible in PHP, see:
class X {}
class Y extends X {}
class A {
function a(X $x) {}
}
class B extends A {
function a(Y $y) {} // error! Methods with the same name must be compatible with the parent method, this includes the typehints
}
This produces the same kind of error as your code. I would just not put a store() method in your BaseController. If you feel that you are repeating code, consider introducing for example a service class or maybe a trait.
Using a service class
Below a solution that makes use of an extra service class. This might be overkill for your situation. But if you add more functionality to the StoringServices store() method (like validation), it could be useful. You can also add more methods to the StoringService like destroy(), update(), create(), but then you probably want to name the service differently.
class StoringService {
private $repo;
public function __construct(Repository $repo)
{
$this->repo = $repo;
}
/**
* Store a newly created resource in storage.
*
* #return Response
*/
public function store(Request $request)
{
$result = $this->repo->create($request);
return response()->json($result, 200);
}
}
class UserController {
// ... other code (including member variable $repo)
public function store(UserRequest $request)
{
$service = new StoringService($this->repo); // Or put this in your BaseController's constructor and make $service a member variable
return $service->store($request);
}
}
Using a trait
You can also use a trait, but you have to rename the trait's store() method then:
trait StoringTrait {
/**
* Store a newly created resource in storage.
*
* #return Response
*/
public function store(Request $request)
{
$result = $this->repo->create($request);
return response()->json($result, 200);
}
}
class UserController {
use {
StoringTrait::store as baseStore;
}
// ... other code (including member variable $repo)
public function store(UserRequest $request)
{
return $this->baseStore($request);
}
}
The advantage of this solution is that if you do not have to add extra functionality to the store() method, you can just use the trait without renaming and you do not have to write an extra store() method.
Using inheritance
In my opinion, inheritance is not so suitable for the kind of code reuse that you need here, at least not in PHP. But if you want to only use inheritance for this code reuse problem, give the store() method in your BaseController another name, make sure that all classes have their own store() method and call the method in the BaseController. Something like this:
BaseController.php
/**
* Store a newly created resource in storage.
*
* #return Response
*/
protected function createResource(Request $request)
{
$result = $this->repo->create($request);
return response()->json($result, 200);
}
UserController.php
public function store(UserFormRequest $request)
{
return $this->createResource($request);
}
You can move your logic from BaseController to trait, service, facade.
You can not override existing function and force it to use different type of argument, it would break stuff. For example, if you later would write this:
function foo(BaseController $baseController, Request $request) {
$baseController->store($request);
}
It would break with your UserController and OtherRequest because UserController expects UserController, not OtherRequest (which extends Request and is valid argument from foo() perspective).
As others have mentioned, you cannot do what you want to do for a host of reasons. As mentioned, you can solve this problem with traits or similar. I am presenting an alternative approach.
At a guess, it sounds like you are trying to follow the naming convention put forth by Laravel's RESTful Resource Controllers, which is kind of forcing you to use a particular method on a controller, in this case, store.
Looking at the source of ResourceRegistrar.php we can see that in the getResourceMethods method, Laravel does either a diff or intersect with the options array you pass in and against the default values. However, the those defaults are protected, and include store.
What this means is that you can't pass anything to Route::resource to force some override of the route names. So let's rule that out.
A simpler approach would be to simply set up a different method just for this route. This can be achieved by doing:
Route::post('user/save', 'UserController#save');
Route::resource('users', 'UserController');
Note: As per the documentation, the custom routes must come prior to the Route::resource call.
The declaration of UserController::store() should be compatible with BaseController::store(), which means (among other things) that the given parameters for both the BaseController as well as UserController should be exactly the same.
You actually cán force the BaseController to require a UserFormRequest, it's not the prettiest solution, but it works.
By overwriting there is no way you can replace Request with UserFormRequest, so why not use both? Giving both methods an optional parameter for injecting the UserFormRequest object. Which would result in:
BaseController.php
class BaseController {
public function store(Request $request, UserFormRequest $userFormRequest = null)
{
$result = $this->repo->create($request);
return response()->json($result, 200);
}
}
UserController.php
class UserController extends BaseController {
public function __construct(UserRepository $repo)
{
$this->repo = $repo;
}
public function store(UserFormRequest $request, UserFormRequest $userFormRequest = null)
{
return parent::store($request);
}
}
This way you can ignore the parameter when using BaseController::store() and inject it when using UserController::store().
The easiest and cleanest way I found to circumvent that problem was to prefix the parent methods with an underscore. For example:
BaseController:
_store(Request $request) { ... }
_update(Request $request) { ... }
UserController:
store(UserFormRequest $request) { return parent::_store($request); }
update(UserFormRequest $request) { return parent::_update($request); }
I feel like creating service providers is an overkill. What we're trying to circumvent here is not the Liskov substitution principle, but simply the lack of proper PHP reflection. Type-hinting methods is, in itself, a hack after all.
This will force you to manually implement a store and update in every child controller. I don't know if that's bothersome for your design, but in mine, I use custom requests for each controller, so I had to do it anyway.
I have this method in my Controller that from indexAction() is called several times. Should I keep it as it is, or should I create Helper Class (Service) for it and other reusable methods, since I could avoid passing arguments such as $em in this case? I can't understand a context of services and when it is comfortable to use them.
public function getProfile($em, $username, $password) {
$dql = $em->createQuery('SELECT Profiles FROM ProjectProjectBundle:Profiles AS Profiles WHERE Profiles.email = :email AND Profiles.password = :password')
->setParameters(array(
'email' => $username,
'password' => $password
));
return $dql->getArrayResult();
}
First of all, know this: You should never, never, never have SQL/DQL in your controllers. Never.
Secondly, you have a few options, but I'm only going to outline one. I'm assuming you have Entities defined so let's start with an options based on that.
Tell doctrine where the Profiles entity's repository is located
src/Project/ProjectBundle/Entity/Profiles.php
/**
* #ORM\Table()
* #ORM\Entity(repositoryClass="Project\ProjectBundle\Entity\ProfilesRepository")
*/
class Profiles {}
Create the ProfilesRepository class and imlement a custom finder
src/Project/ProjectBundle/Entity/ProfilesRepository.php
namespace Project\ProjectBundle\Entity;
use Doctrine\ORM\EntityRepository;
class ProfilesRepository extends EntityRepository
{
/**
* Find a Profiles entity with the given credentials
*
* #return \Project\ProjectBundle\Entity\Profiles|null
*/
public function findByCredentials($username, $password)
{
return $this->findBy(array('email' => $username, 'password' => $password ));
}
}
Update the convenience method in your controller
/**
* Fetch a user's profile
*
* #return \Project\ProjectBundle\Entity\Profiles
*/
private function fetchProfile($username, $password)
{
try { // Exception-safe code is good!
$profilesRepo = $this->getDoctrine()->getEntityRepository('ProjectProjectBundle:Profiles');
return $profilesRepo->findByCredentials($username, $password);
}
catch (\Exception $e)
{
// Do whatever you want with exceptions
}
return null;
}
A few notes on the proposal
I ditched the DQL in favor of Repository finders
I ditched the array representation of rows in favor of Entity representations. As long as you're actually using Entities, this is preferable.
I renamed getProfile() to fetchProfile() since it's a better description for what the method does. I also made it private because that's just good secure coding practice.
Use the EntityRepositories
// src/YourProjectName/BundleName/Entity/ProfileRepository.php
<?php
namespace YourProjectName\BundleName\Entity;
use Doctrine\ORM\EntityRepository;
class ProfileRepository extends EntityRepository
{
public function myGetProfile($username, $password)
{
$dql = $this->getEntityManager()->createQuery('SELECT Profiles FROM ProjectProjectBundle:Profiles AS Profiles WHERE Profiles.email = :email AND Profiles.password = :password')
->setParameters(array(
'email' => $username,
'password' => $password
));
return $dql->getArrayResult();
}
}
Then from any controllers you'll be able to do
$repository = $em->getRepository('YourBundle:Profile');
$repository->myGetProfile('username', 'password');
I would use an entity reposity (Just posted this and realised Isaac has just posted so please refer)
/** Repository Class **/
<?php
namespace YourProjectName\BundleName\Entity;
use Doctrine\ORM\EntityRepository;
class ProfileRepository extends EntityRepository {
public function myGetProfile($username, $password) {
return $this->getEntityManager()
->createQueryBuilder('p')
->from('ProjectProjectBundle:Profiles','p')
->where('p.email = :email')
->andWhere('p.password = :password')
->setParameters(['email' => $email, 'password' => $password])
->getQuery()
->getArrayResult();
}
}
/** In a controller **/
$results = $em->getRepository('ABundle:Profile')->myGetProfile($username, $password);
Also don't forget to add this repository to your enity class or it will not work
If you have one main function that is called I would also recommend shorting this with #ParamConverter using repository method.
The first distinction I would make is that you grouped Helper Classes and Services as the same thing. You might have a global helper class for formatting strings or working with filepaths, but that wouldn't make a good service.
Re-reading http://symfony.com/doc/current/book/service_container.html provided a good analogy with a Mailer service. Maybe in your case you'd want a Profiler service to manage your Profile objects.
$profiler = $this->get('profile_manager');
$current_profile = $profiler->getProfile();
$other_profile = $profiler->getProfile('username','password');
Namespaces omitted for brevity...
I have written the following service provider and registered in config/app.php:
class OfferServiceProvider extends ServiceProvider
{
public function register()
{
$this->registerLossControlManager();
}
protected function registerLossControlManager()
{
$this->app->bind('LossControlInterface', 'LossControl');
}
}
Here is my LossControlInterface
interface LossControlInterface
{
/**
* #param int $demandId
* #param float $offerTotal
* #param float $productTotal
* #param null|int $partnerId
* #return mixed
*/
public function make($demandId, $offerTotal, $productTotal, $partnerId = null);
/**
* #return float
*/
public function getAcceptableLoss();
/**
* #return bool
*/
public function isAcceptable();
/**
* #return bool
*/
public function isUnacceptable();
/**
* #return null
*/
public function reject();
}
Now within the controller, I can inject the LossController as follows:
use LossControlInterface as LossControl;
class HomeController extends BaseController {
public function __construct(LossControl $lossControl)
{
$this->lossControl = $lossControl;
}
public function getLossThresholds()
{
$lossControl = $this->lossControl->make(985, 1000, null);
var_dump('Acceptable Loss: ' . $lossControl->getAcceptableLoss());
var_dump('Actual Loss: ' . $lossControl->calculateLoss());
var_dump('Acceptable? ' . $lossControl->isAcceptable());
}
}
However if I try to dependency inject the LossControlInterface from within a custom class called by a command:
[2014-09-02 13:09:52] development.ERROR: exception 'ErrorException' with message 'Argument 11 passed to Offer::__construct() must be an instance of LossControlInterface, none given, called in /home/vagrant/Code/.../ProcessOffer.php on line 44 and defined' in /home/vagrant/Code/.../Offer.php:79
It appears as though I am unable to dependency inject the interface into a custom class, but I can when dependency injecting into a controller.
Any thoughts on what Im doing wrong or have omitted to get the automatic resolution working?
The IoC is automatic within controllers, and you don't see the injection because Laravel handles the construction of controllers for you. When creating any other custom class by using the new keyword, you will still need to send in all of the parameters needed to it's constructor:
$myClass = new ClassWithDependency( app()->make('Dependency') );
You can hide this, to a degree, by funneling creation of your custom class through a service provider:
// Your service provider
public function register()
{
$this->app->bind('ClassWithDependency', function($app) {
return new ClassWithDependency( $app->make('Dependency') );
});
}
Then just have the IoC make it whenever you need it:
$myClass = app()->make('ClassWithDepenency');
In your case, you can change your code to look like this:
private function setOffer(Offer $offer = null) {
$this->processOffer = $offer ?:
new Offer( app()->make('LossControlInterface') );
}
A perhaps cleaner approach could be to create a service provider and an OfferFactory which gets injected into your controller. The controller can then request the factory to create the offer whenever it needs one:
// Controller
public function __construct(OfferFactory $offerFactory)
{
$this->offerFactory = $offerFactory;
}
public function setOffer(Offer $offer = null)
{
$this->processOffer = $offer ?: $this->offerFactory->createOffer();
}
// OfferFactory
class OfferFactory
{
public function createOffer()
{
return app()->make('Offer');
}
}
This has the benefit of completely decoupling your controller from the logic behind the creation of the offer, yet allowing you to have a spot to add any amount of complexity necessary to the process of creating offers.
In Laravel 5.2 the simplest solution for your particular problem would be to replace
new Offer();
with
App::make('Offer');
or even shorter
app('Offer');
which will use Laravel Container to take care of dependencies.
If however you want to pass additional parameters to the Offer constructor it is necessary to bind it in your service provider
App::bind('Offer', function($app, $args) {
return new Offer($app->make('LossControl'), $args);
});
And voila, now you can write
app('Offer', [123, 456]);
In laravel 5.4 (https://github.com/laravel/framework/pull/18271) you need to use the new makeWith method of the IoC container.
App::makeWith( 'App\MyNameSpace\MyClass', [ $id ] );
if you still use 5.3 or below, the above answers will work.