Doctrine Entity Repository how to add 'andWhere' to all 'find*' functions? - symfony

For legacy reasons I have table that is used for many purposes. Only subset of rows is relevant to Entity I'm writing. Criteria is simple, just check 'project' field for specific value.
Instead of reimplementing find, findAll, findByName, findByID, findBy.... Just notify doctrine to append single condition to them all. Is that possible without reimplementing each and every find* ?
Or maybe it can be done on lover level still?
UPDATE:
Reworked question, to specify what kind of solution would be acceptable.

An available easy-to-use solution is to create a Repository with your custom find function.
Then, if all your entities has a specific Repository, make them (Repository) extending from yours (which contains the custom find method), otherwise (you doesn't have a Repository per entity), assign the repository to all your entities with the repositoryClass option of the #ORM\Entity annotation like :
#ORM\Entity(repositoryClass="YourMainRepository")
Otherwise, if you doesn't want put any repository in your entities, override the whole default repository and customise the find method.
I already used the last option because of a specific need, also I invite you to see the following question :
Abstract repository and #Entity annotation inheritance
Look at the solution wich contains a gist of all required steps for override the default doctrine repository.
See Custom Repository classes

Entity:
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* Phrase
*
* #ORM\Table(name="User")
* #ORM\Entity(repositoryClass="AppBundle\Repository\UserRepository")
*/
class User
{
/**
* #var int
*
* #ORM\Column(name="id", type="bigint")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
.............................................................
.............................................................
.............................................................
Your Repository:
namespace AppBundle\Repository;
use Doctrine\ORM\EntityRepository;
class UserRepository extends EntityRepository
{
/** For example **/
public function getByName($name)
{
$qb = $this->createQueryBuilder('u')
->where('u.name = :name')->setParameter('name', $name)
->andWhere('u.lastname LIKE :name')->setParameter('lastname', '%'.$name.'%');
$query = $qb->getQuery();
return $query->getResult();
}
}
In Your Controller:
/**
* #Route("/", name="index")
*/
public function indexAction(Request $request)
{
$userRepository = $this->getDoctrine()->getRepository('AppBundle:User');
$userName = $userRepository->getByName($name);
..................................................................................
..................................................................................

Related

Symfony Accessing to parent entity inside entity class

In Symfony 5, I've created 2 entities related with a ManyToOne relation : Project is the parent, Serie is the child.
Project entity :
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\SerieRepository")
*/
class Serie
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=100)
*/
private $name;
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Project", inversedBy="series")
* #ORM\JoinColumn(nullable=false)
*/
private $project;
[...]
}
Serie entity :
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\ProjectRepository")
*/
class Project
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=100)
*/
private $name;
/**
* #ORM\OneToMany(targetEntity="App\Entity\Serie", mappedBy="Project", orphanRemoval=true)
*/
private $series;
[...]
}
I didn't write down here, but you also have all the getter and setter for each class.
I need to access to the Project entity in the Serie entity. For example : accessing to the name property of Project entity by adding a getProjectName method in Serie class.
public function getProjectName()
{
return $this->project->getName();
}
But this is not working as the Project entity is not loaded (only the id). How can I get this value, without adding a repository in the entity class or passing any argument to the getProjectName method ? (maybe a Doctrine annotation...).
In doctrine entities in relations are lazy-loaded, that means, when you have not accessed anything on $this->project (or the referenced project), it will just be of type Project^ (notice the ^) and it will have an attribute called __initialized__ (or similar) with the value false (check by dump($this->project);). This means, that the entity is NOT loaded, yet.
Lazy-loading means, it will be loaded if it's actually needed (thus reducing database accesses), and before that, a proxy object will take the place of the entity. It'll register all calls done to it, load the entity if necessary and forward all calls to it.
So, to load a lazy-loaded entity, you just call one of its methods. So $this->project->getName() should already work nicely. (verify by calling dump($this->project); afterwards).
If it doesn't, something else is missing/wrong/dysfunctional.
Ok, thank you Jakumi. You are right, on that way, it's working fine.
To complete your explanation, if you want to get the child elements, like :
$series = $project->getSeries();
You will have an empty table (a foreach loop won't get any item). This is because $series is a Doctrine Collection. You need to use :
$series = $project->getSeries()->getValues();
to have a fully completed array.
I spend 2 hours on the topic, I hope this will help somebody else.

Check whether a newly created object is unique by several fields

I have to check a submitted form against the existing database to make sure it is not duplicated by the combination of several fields, say supplier_id, invoice_no and amount.
Is there a built-in method or should I write a code myself? If so - what are guidelines: where to put it, what are the good practices?
At the moment I am extending the CRUD controller and overwriting the createAction adding the condition there. Not sure whether this method is a good practice.
Example:
<?php
namespace AppBundle\Entity\User;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
/**
* #ORM\Entity
* #UniqueEntity({"name", "email"}, message="This value is in a database.")
*/
class User
{
/**
* #var string
*/
protected $name;
/*
* #var string
*/
protected $email;
...
}

Symfony2 - Share Entity Between Bundles with different relationships

How do you share an entity between multiple bundles with different relationships?
For example both the ZooAnimalBundle and FarmAnimalBundle need a User Entity. A third Bundle AccountUserBundle has the User Entity.
In both the Zoo and Farm AnimalBundles I create a User Entity like so:
use Account\UserBundle\Entity\User as BaseUser;
class User extends BaseUser
{
}
I then have a Hospital entity in Zoo:
class Hospital {
/**
* #ORM\ManyToMany(targetEntity="Zoo\AnaimalBundle\Entity\User")
* #ORM\JoinTable(name="users_zoo_animals")
*/
protected $users;
And a Room entity in Farm:
class Room {
/**
* #ORM\ManyToMany(targetEntity="Farm\AnaimalBundle\Entity\User")
* #ORM\JoinTable(name="users_farm_animals")
*/
protected $users;
Everything works so far in that I can call Zoo.Room->getUsers() or Farm.Hospital->getUsers()
However the problem is I'm not sure on how to set up the inverse relationship in their respective User entities.
If for example I update the FarmAnimal User Entity and run doctrine:generate:entities
/**
* #ORM\Entity
*/
class User extends BaseUser
{
/**
* #ORM\ManyToMany(targetEntity="Room", mappedBy="users", cascade={"persist"})
*/
protected $rooms;
}
It will copy the protected $properties from BaseUser and create all the set and get methods which is not what I want. What is the correct way of setting up these relationships?
Update
If you don't setup the inverse relationship, how would you select all users where hospital.id = 1
$qb = $this->getEntityManager()->createQueryBuilder()
->select(
'u'
)
->from('Account\UserBundle\Entity\User','u')
->leftJoin('u.hospitals', 'h')
->andWhere('h.id = :hospital_id')
->setParameter('hospital_id',$hospital_id);
This gives the error:
Class Account\UserBundle\Entity\User has no association named hospitals
I know I could select from hospital and join user because that relationship does exist but I need to select users because I am using them with Doctrine\ORM\Tools\Pagination\Paginator
The query would be
$qb = $this->createQueryBuilder('a')
->select(
'h', 'u'
)
->leftJoin('h.users', 'u')
The problem with this is Paginator only sees one result Hospital because the Users are attached to it.
You can define abstract entity dependencies and implement them with other bundles.
First, each of the bundles depending on a user entity should define a User interface. For example:
namespace Foo\BarBundle\Entity;
interface UserInterface
{
public function getId();
public function getEmail();
// other getters
}
Then, in each entity depending on the user, define the relationship, e.g.:
namespace Foo\BarBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
/**
* #ORM\Entity
*/
class Something
{
/**
* #ORM\ManyToOne(targetEntity="UserInterface")
* #Assert\NotNull
*/
protected $User;
// add other fields as required
}
Now you need to register the User entity as an implementation of the UserInterfaces:
namespace Foo\UserBundle\Entity;
use Foo\BarBundle\Entity\UserInterface as BarUserInterface;
use Foo\FoobarBundle\Entity\UserInterface as FoobarUserInterface;
/**
* #ORM\Entity
*/
class User implements BarUserInterface, FoobarUserInterface
{
// implement the demanded methods
}
Then add the following to app/config/config.yml:
doctrine:
orm:
resolve_target_entities:
Foo\BarBundle\Entity\UserInterface: Foo\UserBundle\Entity\User
Foo\FooarBundle\Entity\UserInterface: Foo\UserBundle\Entity\User
(Heads up: there will usually already be a doctrine.orm node which you'll have to extend.)
This is not a perfect solution, because you cannot really say which fields the user entity should have. On the other hand, it's strictly OOP, as you don't have to know about internals of the User implementation – you just need it to return the right values.
Creating multiple definitions of the account is the wrong way to do it, unless you want to create 3 seperate user tables (even then it's better not to do it this way).
Really you want your other entities to map to the your user entity in the account bundle.
I.e.,
class Hospital {
/**
* #ORM\ManyToMany(targetEntity="Zoo\AccountBundle\Entity\User")
*/
protected $users;
Now, there is no need to create the inverse relationship. In fact, this is a bad practice since you have a bi-directional dependency. Users don't know about hospitals, but hospital knows about it's users. Now, any bundle can map to the user entity and reuse it.

Doctrine arrayCollections and relationship

I'm quite new with Doctrine, so I hope someone can help me or redirect me to the good documentation page.
I'm building an app with two entity (I reduce for explanations) :
- Tender
- File
For each tender, we can have one or more files. So I made the following objects.
Tender:
<?php
namespace TenderBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="tender")
*/
class Tender
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $tender_id;
/**
* #ORM\Column(type="array")
* #ORM\ManyToOne(targetEntity="File", inversedBy="tenders")
* #ORM\JoinColumn(name="tender_files", referencedColumnName="file_id")
*/
private $tender_files;
}
File:
<?php
namespace TenderBundle\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\UploadedFile;
/**
* #ORM\Entity
* #ORM\Table(name="file")
*/
class File
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $file_id;
/**
* #ORM\OneToMany(targetEntity="Tender", mappedBy="tender_files", cascade={"persist", "remove"})
*/
private $file_tender;
}
First question: is it the right way to do this?
(of course, i've created the methods to get and set attributes, but they're basic).
When I persist each of my File object i'm trying to add then to my Tender instance. But to do this, I need to make $tender_files public and do this:
$tender->tender_files[]
This is not a viable solution for me because I need all my fields are private and I want to recover my object when I try to call this:
$this->getDoctrine()->getManager()->getRepository('TenderBundle:Tender')->find($id)->getTenderFiles()->getFileName();
So, I'm explaining and asking to find the right way to do what I want. I hope what i need is clear and i'm here to answers questions or show more code if needed.
Thanks!
Like Richard has mentioned, you're missing getters and setters which are declared to be public. They'll have access to your private variables. The quick way to do this with symfony:
php app/console doctrine:generate:entities
It'll generate something like this:
public function addTenderFile(\TenderBundle\Entity\File $file)
{
$this->tender_files[] = $file;
return $this;
}
/**
* Remove
*/
public function removeTenderFile(\TenderBundle\Entity\File $file)
{
$this->tender_files->removeElement($file);
}
/**
* Get
*/
public function getTenderFiles()
{
return $this->tender_files;
}
It's good practice if you're a beginner to see how your code lines up with the auto generator. Once you understand what's going on, just let the generator do the grunt work.
You should have a setter and getter in your File entity similar to this:
public function setTender(\Your\Namespace\Tender $tender)
{
$this->tender = $tender;
return $this;
}
public function setTender()
{
return $this->tender;
}
So when you instance (or create) File, you can go like so:
$file = new File(); // or file fetched from DB, etc.
// here set $file properties or whatever
$tender->setFile($file);
$entityManager->persist($tender);
$entityManager->flush();
Then your tender will be properly associated with your file.
Similarly from the File end, you should be able to do:
$file->addTender($tender);
$entityManager->persist($file);
$entityManager->flush();
And your tender will be added to your File->tenders collection.
For more information the documentation is very useful and has more or less everything you need to get started.
Also, save yourself manually creating getters and setters by using generate:doctrine:entity
This is incorrect:
/**
* #ORM\Column(type="array")
* #ORM\ManyToOne(targetEntity="File", inversedBy="tenders")
* #ORM\JoinColumn(name="tender_files", referencedColumnName="file_id")
*/
private $tender_files;
You can't persist an array to your database. A database row is one entity and it's corresponding attributes. If a tender can have many files, then this relationship should be:
* #ORM\OneToMany
Likewise for the File entity. If many files can have one Tender then it's relationship should be:
* #ORM\ManyToOne
For relationship mapping using Doctrine, it's helpful to read left-to-right with the entity YOU'RE CURRENTLY IN being on the left, and the entity you're setting as a variable being on the right.
If you're in Tender reading left-to-right Tender may have "OneToMany" files. And File(s) may have ManyToOne Tender. Doctrine Association Mapping

How do you extend an entity in Symfony2 like you used to be able to in Symfony 1?

In older versions of Symfony you used to be able to build new objects within a data object by extending a model class with an extended subclass.
For example, I had a questionnaire model that had a results table. That results table had a Result.php model class that used to set and get the results through Doctrine. I then used the ResultPeer.php model subclass to add a new function to the Result object that took the result and depending on a fixed set of thresholds calculated a score and corresponding colour.
In the new Symfony2 version using Doctrine2 I am struggling to work out the best way to do this. When creating an entity I can only find in the documentation the ability to add objects based on the data structure relationships.
I looked at the entity repositories, but that does not appear to extend or add functionality to an original object. It seems to bring back data objects based on queries that are more complex than the standard query functions.
I also looked at services, which I can use to collect the object and then using the object create a new array that includes this object and the newly created data, but this just does not seem right or follow the philosophy that Symfony is all about.
Does anyone know how functions can be added to an existing data object. I found it really useful in the older version of Symfony, but cannot seem to find the alternative in the new version of Symfony2.
Extending an entity is the way to go. In the Doctrine2 world, they talk about inheritance mapping. Here a code example. It defines a BaseEntity then it is extendsed to create a BaseAuditableEntity and finally there is a User entity extending BaseAuditableEntity. The trick is to use the #Orm\MappedSuperclass annotation. This inheritance scheme will create a single table even if there is three entities in my relationships graph. This will then merge all properties into a single table. The table created will contains every property mapped through the relations, i.e. properties from BaseAuditableEntity and from User. Here the code examples:
Acme\WebsiteBundle\Entity\BaseEntity.php
namespace Acme\WebsiteBundle\Entity;
use Doctrine\ORM\Mapping as Orm;
/**
* #Orm\MappedSuperclass
*/
class BaseEntity {
}
Acme\WebsiteBundle\Entity\BaseAuditableEntity.php
namespace Acme\WebsiteBundle\Entity;
use Doctrine\ORM\Mapping as Orm;
/**
* #Orm\MappedSuperclass
*/
class BaseAuditableEntity extends BaseEntity {
private $createdBy;
/**
* #Orm\Column(type="datetime", name="created_at")
*/
private $createdAt;
/**
* #Orm\ManyToOne(targetEntity="User")
* #Orm\JoinColumn(name="updated_by", referencedColumnName="id")
*/
private $updatedBy;
/**
* #Orm\Column(type="datetime", name="updated_at")
*/
private $updatedAt;
// Setters and getters here
}
Acme\WebsiteBundle\Entity\User.php
namespace Acme\WebsiteBundle\Entity;
use Acme\WebsiteBundle\Entity\BaseAuditableEntity;
use Doctrine\ORM\Mapping as Orm;
/**
* #Orm\Entity(repositoryClass="Acme\WebsiteBundle\Entity\Repository\UserRepository")
* #Orm\Table(name="acme_user")
*/
class User extends BaseAuditableEntity implements AdvancedUserInterface, \Serializable
{
/**
* #Orm\Id
* #Orm\Column(type="integer")
* #Orm\GeneratedValue
*/
private $id;
/**
* #Orm\Column(type="string", name="first_name")
*/
private $firstName;
/**
* #Orm\Column(type="string", name="last_name")
*/
private $lastName;
/**
* #Orm\Column(type="string", unique="true")
*/
private $email;
// Other properties
// Constructor
// Setters and getters
}
Here a link to the official inheritance mapping documentation of Doctrine 2.1: here
Hope this helps, don't hesitate to comment if you need more information.
Regards,
Matt

Resources