This is a two part question.
First, I can't seem to find out how to make PhpStorm correctly hint Doctrine repositories:
Entity looks like this:
/**
* #ORM\Entity(repositoryClass="UserRepository")
* #Entity #Table(name="users")
*/
class User
{
Repository looks like this:
use Doctrine\ORM\EntityRepository;
class UserRepository extends EntityRepository
{
public function getUser() {}
}
But when access the repository methods like so:
$userRepository = $em->getRepository(User::class);
$user = $userRepository->getU... // getUser() is not suggested
PhpStorm doesn't suggest the getUser() method.
The only workaround I found is:
/** #var UserRepository $userRepository */
$userRepository = $em->getRepository(User::class);
But this seems to be a bit bothersome to do it every time I need some repository.
Also, second part of the question is that I can't use repository as class property:
private EntityManager $em;
private UserRepository $userRepository;
public function __construct(EntityManager $em) {
$this->em = $em;
$this->userRepository = $em->getRepository(User::class);
}
Because it says:
Incompatible types: Expected property of type '\MyProject\UserRepository', '\Doctrine\Persistence\ObjectRepository|\MyProject\User|string' provided
How to set this up correctly for both cases?
Related
I have an abstract class called AbstractMediaService and a some specific implementations of this abstract class:
abstract class AbstractMediaService
{
private $em;
private $media;
public function __construct(EntityManagerInterface $em, Media $media)
{
$this->em = $em;
$this->media = $media;
}
public function dosomethingInCommon();
abstract public function dosomethingSpecific();
}
class PhotoMediaService extends AbstractMediaService
{
public function dosomethingSpecific()
{
echo 'i am a photo service';
}
}
class VideoMediaService extends AbstractMedia
{
public function dosomethingSpecific()
{
echo 'i am a video service';
}
}
These objects require a Media entity to work with
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\Collection;
use Doctrine\Common\Collections\Criteria;
class Media
{}
Controller
/**
* #Route("/{_locale}/infos/{idMedia}.html", name="info", methods={"GET"}, requirements={
* "idMedia" = "\d+",
* })
*/
public function infosPhotoAction(RequestStack $requestStack, Media $media)
{
$request = $requestStack->getCurrentRequest();
$session = $requestStack->getSession();
$media = new PhotoMedia($media);
// return response
}
Problem is that I need some dependencies like the Security service or the EntityManager.
I would like to know how autowire AbstractMediaService service.
This is wrong. You cannot autowire Media to be injected into a service, because entities are not services.
public function __construct(EntityManagerInterface $em, Media $media)
If VideoMediaService and PhotoMediaService (I renamed them for clarity, since sharing the name with your entity made it look like it were related) need an instance of Media to perform some work, just make that a parameter for the corresponding methods.
public function dosomethingInCommon(Media $media);
abstract public function dosomethingSpecific(Media $media);
Or alternatively, simply have a setMedia(Media $media) method on that class for that:
public function setMedia(Media $media) {
$this->media = $media;
}
Frankly, this latter approach does not seem like a great idea. You would need to make the methods that work on $media aware of the possibility of setMedia() not having been called yet, or subsequent calls to setMedia() would change how the service behaved. Just making it a parameter of the appropriate method is much cleaner, clearer and safer.
Injecting those services is done like any other service. That they extend an abstract class is irrelevant.
/**
* #Route("/{_locale}/infos/{idMedia}.html", name="info", methods= {"GET"}, requirements={
* "idMedia" = "\d+",
* })
*/
public function infosPhotoAction(RequestStack $requestStack, Media $media, PhotoMediaService $photoMediaService): Response
{
$request = $requestStack->getCurrentRequest();
$session = $requestStack->getSession();
$photoMediaService->doSomethingSpecific($media)
return new Response('all done');
}
I have a repository class called EmailRepository
class EmailRepository extends EntityRepository implements ContainerAwareInterface { ... }
I need to get a parameter injected into this repository class but I dont know how...
This is what I currently have inside of the repository, which is being called from my controller:
Controller:
$em->getRepository(Email::class)->getEmailApi();
Repository
class EmailRepository extends EntityRepository implements ContainerAwareInterface {
protected $container;
public function setContainer(ContainerInterface $container = null) {
$this->container = $container;
}
/**
* #param $array
*/
public function getEmailApi($array)
{
echo $this->container->getParameter('email_api');
}
}
I always get this error:
Call to a member function getParameter() on null
The parameter is not null, it does have a value. I know it's telling me that $this->container is null. How do I fix this?
If I run this inside of my controller, it works fine and returns Google
echo $this->getParameter('email_api');
Inject container not a good idea. Try this
services.yaml
App\Repository\EmailRepository:
arguments:
$emailApi: '%env(EMAIL_API)%'
Repository
class EmailRepository
{
protected $emailApi;
public function __construct(string $emailApi)
{
$this->emailApi = $emailApi;
}
/**
* #param $array
*/
public function getEmailApi($array)
{
return $this->emailApi;
}
}
Or via setter injection
services.yaml
App\Repository\EmailRepository:
calls:
- method: setEmailApi
arguments:
$emailApi: '%env(EMAIL_API)%'
Repository
class EmailRepository extends EntityRepository implements ContainerAwareInterface
{
protected $emailApi;
public function setEmailApi(string $emailApi)
{
$this->emailApi = $emailApi;
}
/**
* #param $array
*/
public function getEmailApi($array)
{
return $this->emailApi;
}
}
Your original code is not going to work because there is nothing calling EmailRepository::setContainer. Furthermore, using ContainerAware and injecting the full container is discouraged.
Fortunately, the Doctrine bundle has a new base repository class that the entity manager can use to pull the repository from container and allow you to inject additional dependencies as needed. Something like:
namespace App\Repository;
use App\Entity\Email;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Symfony\Bridge\Doctrine\RegistryInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
class EmailRepository extends ServiceEntityRepository // Different class to extend from
{
private $emailApi;
public function __construct(RegistryInterface $registry, ParameterBagInterface $parameterBag)
{
parent::__construct($registry, Email::class);
$this->emailApi = $parameterBag->get('email_api');
}
So in this case we inject all the parameters and then store the ones we need.
Even injecting the parameter bag is a bit frowned upon. Better to inject individual parameters though this takes just a bit more configuration as we need to use services.yaml to explicitly inject the needed parameters:
public function __construct(RegistryInterface $registry, string $emailApi)
{
parent::__construct($registry, Email::class);
$this->emailApi = $emailApi;
}
#services.yaml
App\Repository\EmailRepository:
$emailApi: 'email_api_value'
In my case I have a class such as:
class Logger
{
/**
* #var EntityManager
*/
protected $entityManager;
public function __construct(EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
/**
* #param Model $model
*/
public function log(Model $model)
{
$logEntity = new LogEntity();
$logEntity->setOrder($model->getOrder());
$logEntity->setType($model->getType());
$logEntity->setScore($model->getScore());
$logEntity->setCreated(new DateTime());
$logEntity->setModified(new DateTime());
$this->entityManager->persist($logEntity);
$this->entityManager->flush();
return $logEntity;
}
Logger class is not testable because in my code 'new' keyword exists, in other hand Logger class and EntityManager class registered singleton in container and can't inject model as dependency.
How to change class for change to testable class?
LoggerModel is a Doctrine entity and use in Laravel framework.
I solve this problem with a sample solution: Factory Pattern.
I need assertion, so when get a new model from factory assert it as mock.
And how?
I create a class with a method that can be a singleton service:
class LogFactory
{
public function makeLogEntity()
{
return new LogEntity();
}
}
In another service, inject factory class:
class Logger
{
/**
* #var EntityManager
*/
protected $entityManager;
/**
* #var LogFactory
*/
protected $logFactory;
public function __construct(EntityManager $entityManager, LogFactory $logFactory)
{
$this->entityManager = $entityManager;
$this->logFactory = $logFactory
}
/**
* #param Model $model
*/
public function log(Model $model)
{
$logEntity = $this->logFactory->makeLogEntity();
$logEntity->setOrder($model->getOrder());
$logEntity->setType($model->getType());
$logEntity->setScore($model->getScore());
$logEntity->setCreated(new DateTime());
$logEntity->setModified(new DateTime());
$this->entityManager->persist($logEntity);
$this->entityManager->flush();
return $logEntity;
}
Now I have a service that is mock able and call $mock->willReturn() function in test.
I'm new in Symfony 2.
I have a function called "addNewTarjeta" in a personalized entity respository.
<?php
namespace Elkanogroup\ClientesBundle\Repository;
/**
* ClienteRepository
*
* This class was generated by the Doctrine ORM. Add your own custom
* repository methods below.
*/
class ClienteRepository extends \Doctrine\ORM\EntityRepository {
/**
* Asigna una tarjeta a este cliente.
*/
public function addNewTarjeta(Cliente $cliente) {
$tarjeta = new \Elkanogroup\ClientesBundle\Entity\Tarjeta();
$tarjeta->setNumeroTarjeta('5555 5555 5555 5555');
$tarjeta->setCliente($cliente);
$tarjeta->setFechaExpedicion(new \DateTime());
$em = $this->getDoctrine()->getManager();
$em->persist($tarjeta);
$flush = $em->flush();
if ($flush != null) {
return false;
}
return true;
}
I have a listener waiting for a doctrine event postPersist. I would like to call to "addNewTarjeta" from a postPersist function.
I'm trying to do something like this:
<?php
namespace Elkanogroup\ClientesBundle\EventListener;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Elkanogroup\ClientesBundle\Entity\Cliente;
use Elkanogroup\ClientesBundle\Repository\ClienteRepository;
class ClienteListener {
public function postPersist(Cliente $cliente, LifecycleEventArgs $args) {
$cliente->addNewTarjeta($cliente);
}
But it doesnt work. Symfony says:
Attempted to call an undefined method named "addNewTarjeta" of class
"Elkanogroup\ClientesBundle\Entity\Cliente".
Can anyone help me ?? Thanks and sorry for my bad english.
Everyone here says that you need to inject the entity manager but to me it's not true: you can retrive it from LifecycleEventArgs without inject anything.
Just do
$args->getObjectManager();
and you're done.
Just a note: usually repos are used to keep custom queries (via DQL or plain SQL or query builder). A logic like this should be fitted inside a service (a manager, helper or whatever).
As #dragoste said, you need to inject the entitymanager service into your listener.
It can be done in services.yml:
name.of.your.listener:
class: AppBundle\Listener\MyListener
arguments: ["#doctrine.orm.entity_manager"]
And then, add a public function __construct(\Doctrine\ORM\EntityManager $entityManager) method in your listener:
<?php
namespace AppBundle\Listener;
class MyListener
{
/**
* #var \Doctrine\ORM\EntityManager
*/
private $entityManager;
public function __construct(\Doctrine\ORM\EntityManager $entityManager)
{
$this->entityManager = $entityManager;
}
class CustomSawException extends Exception implements ServiceLocatorAwareInterface, SawExceptionInterface
{
protected $serviceLocator;
public function test(){
$this->serviceLocator->get('SomeThing');
}
/**
* Set service locator
*
* #param ServiceLocatorInterface $serviceLocator
*/
public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
{
$this->serviceLocator = $serviceLocator;
}
/**
* Get service locator
*
* #return ServiceLocatorInterface
*/
public function getServiceLocator()
{
return $this->serviceLocator;
}
The exception that is thrown for this is:
call of get() on null
I could not figure out why it is throwing exception?
ServiceLocatorAwareInterface should have injected the ServiceLocator?
ServiceLocatorAwareInterface should have injected the ServiceLocator?
I am not so sure about that, in the latest versions of ZF2 this interface is deprecated.
Read also this post on GitHub or this stackoverflow question for more information.
I think this is happening because you don't use the ServiceManager to instantiate this service.
The ServiceLocatorAwareInterface works only if this service is called via the ServiceManager.
If you use
new CustomSawException();
Outside of the ServiceManager, then the AwareInterface can't set the ServiceLocator.
You should declare this service as an invokable if it doesn't have dependencies, or factories in the other case.