I'm trying to work with Entity Repository to write my custom functions.
I have an Entity and his Repository generated from yaml file
Yaml file
Bluesys\WeekupBundle\Entity\Event:
type: entity
repositoryClass: Bluesys\WeekupBundle\Repository\Event
fields:
id:
id: true
type: integer
generator:
strategy: AUTO
...
Entity code automatically generated
namespace Bluesys\WeekupBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Event
*/
class Event
{
/**
* #var integer
*/
private $id;
...
}
Repository code automatically generated
I juste wrote the function isHidden
namespace Bluesys\WeekupBundle\Repository;
use Doctrine\ORM\EntityRepository;
/**
* Event
*
* This class was generated by the Doctrine ORM. Add your own custom
* repository methods below.
*/
class Event extends EntityRepository
{
/**
* isHidden
*
* #return bool
*/
public function isHidden()
{
return true;
}
}
The Controller code
namespace Bluesys\WeekupBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Bluesys\WeekupBundle\Event\Event;
...
class TimelineController extends Controller
{
public function testAction(){
$repository = $this->getDoctrine()->getManager()->getRepository('BluesysWeekupBundle:Event');
$event = $repository->findOneById( 73 );
return $this->render('BluesysWeekupBundle::test.html.twig', array( 'event' => $event ));
}
...
And the view code
{{ event.isHidden }}
I get this error :
Method "isHidden" for object "Bluesys\WeekupBundle\Entity\Event" does not exist in BluesysWeekupBundle::test.html.twig at line 1
Can somebody help me by telling me what is missing ?
Repository classes are used only for selecting/fetching data. They are not the part of entity/object.
If you really want to call isHidden method by repository you can acheive this by passing the whole repository to template (return $this->render('BluesysWeekupBundle::test.html.twig', array( 'event' => $repository ));), but this is very bad idea.
Instead you can put isHidden() method into your entity class and call it as event.isHidden..
Related
I have an abstract class:
/**
* #ORM\Entity
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="type", type="string")
* #ORM\DiscriminatorMap({
* "LegalInsuranceProof" = "LegalInsuranceProofDocument",
* "SalesReceipt" = "SalesReceiptDocument"
* })
* #ORM\HasLifecycleCallbacks()
* #ORM\Table(name="document_abstract")
* #ORM\Entity(repositoryClass="App\Repository\DocumentRepository")
*/
abstract class AbstractDocument implements CreateFolderInterface
{
.
.
.
}
and the class, that extends this abstract class:
/**
* #ORM\Entity
* #ORM\HasLifecycleCallbacks()
* #ORM\Table(name="document_sales_receipt")
*/
class SalesReceiptDocument extends AbstractDocument
{
.
.
.
}
In the repo, I have defined the method getReviewListPaginator:
class DocumentRepository extends ServiceEntityRepository {
use PaginatorTrait;
public function __construct(RegistryInterface $registry) {
parent::__construct($registry, AbstractDocument::class);
}
public function getReviewListPaginator($limit, $offset) {
$this->assertQueryParameters($offset, $limit, "asc");
$qb = $this
->createQueryBuilder('d')
->select('PARTIAL d.{id, pageCount}')
->innerJoin('d.case', 'c')
->addSelect('PARTIAL c.{id}')
->setFirstResult($offset)
->setMaxResults($limit);
return new Paginator(
$qb->getQuery()->setHydrationMode(Query::HYDRATE_ARRAY),
true
);
}
}
If I do
$this->em->getRepository(AbstractDocument::class)->getReviewListPaginator(5,2);
the method getReviewListPaginator is called.
But If I do
$paginator = $this->em->getRepository(SalesReceiptDocument::class)->getReviewListPaginator(5,2);
I get en error message:
BadMethodCallException : Undefined method 'getReviewListPaginator'. The method name must start with either findBy, findOneBy or countBy!
But why? Should I define a repo for the SalesReceiptDocument entity, that extends the App\Repository\DocumentRepository?
I don't think the Repository are extended by default.
I think you need to do a SalesReceiptReporsitory that explicitly exteands your DocumentRepository
And add the repositoryClass options to your #Entity on SalesReceiptDocument.
Your #Entity annotations do not have the repository specified, change them to:
#Entity(repositoryClass="..namespace..\DocumentRepository")
See the #Entity documentation.
Edit 1:
I just noticed your AbstractDocument has duplicate #Entity annotation, you can just delete the empty one
Edit 2:
To select different document types you need separate repositories, to keep your code simple and non-repeating, you can use the $_entityName attribute of EntityRepository if you are extending it or have your own private attribute that would indicate the entity name for a repository and then use this entity name in the getReviewListPaginator to dynamically query the type of entity you want.
As far as I can tell, you cannot achieve this without having separate repositories for each type of document - even if each is empty, just extending the base repository and doing the parametrized query building as I described.
I have read the Symfony Documentation on Entity Listeners and Doctrine Documentation on Entity Listeners in addition to this answer and this blog post by Eric Geloen but I can not get my listener to do what I want. In this entity I have a field called status, and when this field changes, an email is going to be sent notifying the receiver of the change.
Entity
I have an entity shipmentLine with the following annotations:
<?php
namespace Acme\Bundle\ShipmentBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use JMS\Serializer\Annotation\ExclusionPolicy;
use JMS\Serializer\Annotation\Expose;
/**
* #ORM\Entity
* #ORM\HasLifecycleCallbacks
* #ORM\EntityListeners({"\Acme\Bundle\ShipmentBundle\Listener\ShipmentLineListener"})
* #ExclusionPolicy("all")
*/
class ShipmentLine
{
. . .
}
Listener
The ShipmentLineListener has implemented the __construct and prePersist functions:
<?php
namespace Acme\Bundle\ShipmentBundle\Listener;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Acme\Bundle\ShipmentBundle\Entity\ShipmentLine;
class ShipmentLineListener
{
private $mailer;
private $twig;
public function __construct(\Swift_Mailer $mailer, \Twig_Environment $twig)
{
$this->mailer = $mailer;
$this->twig = $twig;
}
public function prePersist(ShipmentLine $shipmentLine, LifecycleEventArgs $event)
{
/** #var \Swift_Mime_Message $message */
$message = \Swift_Message::newInstance()
->setSubject('Entity Listener test')
->setFrom(array('my#email.address' => 'Acme'))
->setTo(array('my.other#email.address' => 'Tommy (Acme)'))
->setBody(json_encode($event), 'text/html');
$this->mailer->send($message);
}
}
services.yml
Lastly I have registered my listener in services.yml
entity.entity_listener.shipmentline_update:
class: Acme\Bundle\ShipmentBundle\Listener\ShipmentLineListener
arguments: [ "#mailer", "#twig" ]
tags:
- { name: doctrine.orm.entity_listener, event: prePersist }
The listener fires, and the mail is sent. But both the $shipmentLine and $event argument is empty. Can someone see if I do anything wrong? I have a hard time seeing any errors from what I have read.
Thank you for your time.
I have 2 bundles and i want to override the repository of one of them in the other :
I have a source bundle : SourceBundle.
I have my override bundle : OverrideBundle
First, in OurVendorOverrideBundle.php, I added :
public function getParent()
{
return 'SomeVendorSourceBundle';
}
Then
I wanted to add a custom method for the repository of an entity of SourceBundle.
The source entity is Response.php, and its repository is ResponseRepository.php
So i did :
<?php
namespace OurVendor\OverrideBundle\Repository;
use Doctrine\ORM\EntityRepository;
use SomeVendor\SourceBundle\Repository\ResponseRepository as BaseRepository;
class ResponseRepository extends BaseRepository
{
/**
*
* #return array
*/
public function getQueryExerciseAllResponsesForAllUsers($exoId)
{
$qb = $this->createQueryBuilder('r');
$qb->join('r.paper', 'p')
->join('p.exercise', 'e')
->where('e.id = ?1')
->andWhere('p.interupt = ?2')
->setParameters(array(1 => $exoId, 2 => 0));
return $qb->getQuery();
}
}
If i dont set the Entity in the OverrideBundle i have this error :
The autoloader expected class "CPASimUSante\ExoverrideBundle\Entity\Response" to be defined in file "/home/www/myproject/vendor/ourvendor/override-bundle/OurVendor/OverrideBundle/Entity/Response.php". The file was found but the class was not in it, the class name or namespace probably has a typo.
The SourceBundle entity is :
<?php
namespace SomeVendor\SourceBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* SomeVendor\SourceBundle\Entity\Response
*
* #ORM\Entity(repositoryClass="SomeVendor\SourceBundle\Repository\ResponseRepository")
* #ORM\Table(name="source_response")
*/
class Response
{
...
}
So i add the Entity in the OverrideBudle :
<?php
namespace OurVendor\OverrideBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use SomeVendor\SourceBundle\Entity\Response as BaseEntity;
/**
* SomeVendor\SourceBundle\Entity\Response
*
* #ORM\Entity(repositoryClass="OurVendor\OverrideBundle\Repository\ResponseRepository")
* #ORM\Table(name="override_response")
*/
class Response extends BaseEntity
{
public function __construct()
{
parent::__construct();
}
}
But then i have this error :
An exception occurred while executing 'SELECT u0_.id AS id0, u0_.ip AS ip1, u0_.mark AS mark2, u0_.nb_tries AS nb_tries3, u0_.response AS response4, u0_.paper_id AS paper_id5, u0_.interaction_id AS interaction_id6 FROM ujm_exoverride_response u1_ INNER JOIN ujm_paper u2_ ON u0_.paper_id = u2_.id INNER JOIN ujm_exercise u3_ ON u2_.exercise_id = u3_.id WHERE u3_.id = ? AND u2_.interupt = ?' with params ["1", 0]:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'u0_.id' in 'field list'
This seems that fields in the table are not found. So i changed to
<?php
namespace OurVendor\OverrideBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use SomeVendor\SourceBundle\Entity\Response as BaseEntity;
/**
* SomeVendor\SourceBundle\Entity\Response
*
* #ORM\Entity(repositoryClass="OurVendor\OverrideBundle\Repository\ResponseRepository")
* #ORM\Table(name="source_response")
*/
class Response extends BaseEntity
{
public function __construct()
{
parent::__construct();
}
}
And it worked.
...But when i reinstalled the bundle, to test if everything was ok i had this fatal error stating that source_response is already defined (which is, indeed).
So what can i do then ?
I have also read that i can't override an entity unless the source extends MappedSuperclass, in my case, it doesn't.
But i am doomed if i only want to override its repository ? Is it possible ? If then, what is the right way ?
If i remove altogether the annotation for the override entity, i have :
Class "OurVendor\OverrideBundle\Entity\Response" sub class of "SomeVendor\SourceBundle\Entity\Response" is not a valid entity or mapped super class.
500 Internal Server Error - MappingException
Doctrine mapping in another bundles can be overriden on entity class metadata loading.
EventListener
<?php
namespace Lol\RandomBundle\EventListener;
use Doctrine\ORM\Event\LoadClassMetadataEventArgs;
class ClassMetadataListener
{
/**
* Run when Doctrine ORM metadata is loaded.
*
* #param LoadClassMetadataEventArgs $eventArgs
*/
public function loadClassMetadata(LoadClassMetadataEventArgs $eventArgs)
{
$classMetadata = $eventArgs->getClassMetadata();
if ('AnotherLol\AnotherRandomBundle\Entity\Response' === $classMetadata->name) {
// Do whatever you want...
$classMetadata->customRepositoryClassName = 'ThirdLol\SomeBundle\Repository\NewResponseRepository';
}
}
}
Service configuration
services:
lol.random.listener.class_metadata:
class: Lol\RandomBundle\EventListener\ClassMetadataListener
tags:
- { name: doctrine.event_listener, event: loadClassMetadata }
I would like use 2 repository for 1 entity.
The reason is : I have 2 bundles, both bundle use the same entity. I wish to separate both functionalities.
Sometimes I need specific queries into bundle.
It is possible to have 1 repository into a bundle and a second repository in the other one ?
Maybe it's a wrong way ?
If someone have an idea.
Thx !
2019 Update
I'd create 2 repositories. It makes no sense to add all methods to one repository, just because they share the entity. We could end up with 30 methods per repository this way.
First repository
namespace App\Repository;
use App\Entity\Post;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
final class FrontendPostRepository
{
/**
* #var EntityRepository
*/
private $repository;
public function __construct(EntityManagerInterface $entityManager)
{
$this->repository = $entityManager->getRepository(Post::class);
}
/**
* #return Post[]
*/
public function getAll(): array
{
// ...
}
}
...and 2nd repository
namespace App\Repository;
use App\Entity\Post;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
final class AdminPostRepository
{
/**
* #var EntityRepository
*/
private $repository;
public function __construct(EntityManagerInterface $entityManager)
{
$this->repository = $entityManager->getRepository(Post::class);
}
/**
* #return Post[]
*/
public function getUnpublished(): array
{
// ...
}
}
You can read more about this concept and whys in How to use Repository with Doctrine as Service in Symfony post
Well I don't really know it's a good practice but you can create a repository without linked entity (I mean, not with ORM annotation)
So I just create this in my service.yml :
renting.metadata.car:
class: Doctrine\ORM\Mapping\ClassMetadata
arguments: [ %car% ]
And this :
repair.repository.car:
class: carRepository
arguments: [#doctrine.orm.entity_manager, #renting.metadata.car]
That's works
I'd like to use, something like:
$em = $this->getEntityManager();
Inside a Entity.
I understand I should do this as a service but for some testing purposes, I want to access it from an Entity.
Is it possible to achieve that?
I've tried to:
$em = $this->getEntityManager();
$profile_avatar = $em->getRepository('bundle:Perfils')->findOneByUser($this-getId());
But isn't working.
Fatal error: Call to undefined method
Proxies\webBundleEntityUserProxy::getEntityManager() in
/opt/lampp/htdocs/web/src/Pct/bundle/Entity/User.php on line
449
Why am I trying to do it this way?
I've 3 kinds of users: Facebook, Twitter and MyOwnWebsite users. Each of them have differents avatar which links facebook's profile, twitter's or otherwise, if its myownwebsite user, I retrieve the avatar from a URL in a database. For now, I don't want to create a service, because I'm just trying to make it working, to test it, not to create a final deployment. So this is why I'm trying to call Entity manager from an Entity. I don't want, by now, to modify configuration files, just this entity.
As pointed out (again) by a commenter, an entity manager inside an entity is a code smell. For the OP's specific situation where he wished to acquire the entity manager, with the least bother, a simple setter injection would be most reliable (contrary to my original example injecting via constructor).
For anyone else ending up here looking for a superior solution to the same problem, there are 2 ways to achieve this:
Implementing the ObjectManagerAware interface as suggested by https://stackoverflow.com/a/24766285/1349295
use Doctrine\Common\Persistence\ObjectManagerAware;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\Common\Persistence\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
*/
class Entity implements ObjectManagerAware
{
public function injectObjectManager(
ObjectManager $objectManager,
ClassMetadata $classMetadata
) {
$this->em = $objectManager;
}
}
Or, using the #postLoad/#postPersist life cycle callbacks and acquiring the entity manager using the LifecycleEventArgs argument as suggested by https://stackoverflow.com/a/23793897/1349295
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\HasLifecycleCallbacks()
*/
class Entity
{
/**
* #ORM\PostLoad
* #ORM\PostPersist
*/
public function fetchEntityManager(LifecycleEventArgs $args)
{
$this->setEntityManager($args->getEntityManager());
}
}
Original answer
Using an EntityManager from within an Entity is VERY BAD PRACTICE. Doing so defeats the purpose of decoupling query and persist operations from the entity itself.
But, if you really, really, really need an entity manager in an entity and cannot do otherwise then inject it into the entity.
class Entity
{
private $em;
public function __contruct($em)
{
$this->em = $em;
}
}
Then invoke as new Entity($em).
Best way is to use Life Cycle: #ORM\HasLifecycleCallbacks
And you can use the appropriate Event as you want to get result:
#postLoad
#postPersist
...
Calling the Entity Manager from inside an Entity is a bad practice! You should keep your entities as simple as possible.
For what purpose do you need to call the Entity Manager from an Entity?
What I think you should do is, instead of using the Entity Manager inside your entity, is to create a custom repository for your entity.
In your entity ORM file, add an entry as follows (or in your entity class annotations if not using YML):
App\Bundle\Profils:
# Replace the above as appropiate
type: entity
table: (your table)
....
repositoryClass: App\Bundle\CustomRepos\ProfilsRepository
# Replace the above as appropiate.
# I always put my custom repos in a common folder,
# such as CustomRepos
Now, create a new PHP class that has the namespace above:
//Your ProfilsRepository.php
<?php
namespace App\Bundle\CustomRepos;
use Doctrine\ORM\EntityRepository;
class ProfilsRepository extends EntityRepository
{
/**
* Will return the user url avatar given the user ID
* #param integer $userID The user id.
#return string The avatar url
*/
public function getUserProfile($userId)
{
$em = $this->getEntityManager();
$qb = $em->createQueryBuilder();
$qb->select... (your logic to retrieve the profil object);
$query = $qb->getQuery();
$result = $query->getResult();
return $result;
}
}
Finally, in your Controller:
// Your controller
<?php
namespace <class namespace>;
...
use App\Bundle\CustomRepos\ProfilsRepository;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
...
class YourClassNameController extends Controller
{
public function yourAction()
{
$userId = <get the user ID>;
// Pass the name of your entity manager to the
// getManager function if you have more than one and
// didn't define any default
$em = $this->getDoctrine()->getManager();
$repo = $em->getRepository('Profils');
$avatar = $repo->getUserProfile($userId);
...
}
}
You need to set the services.yml with:
services:
your_service_name:
class: AppBundle\Controller\ServiceController
arguments: [ #doctrine.orm.entity_manager ]
You need to set also the Controller with the following constructor:
public function __construct(\Doctrine\ORM\EntityManager $em)
{
$this->em = $em;
}
and use $this->em in the controller
(for example $connection = $this->em->getConnection();)