How to get the latest plants by category, ManyToMany relationship, using doctrine can help me I'm struggling a lot
Entity Plant
Entity plant_category
Entity category
$sql = "SELECT category_id, c.name, c.slug, i.name
FROM category AS c
LEFT JOIN plant_category AS pc
ON pc.category_id = c.id
INNER JOIN (SELECT id, name FROM plant
ORDER BY id DESC LIMIT 1) AS i
ON pc.plant_id = i.id
GROUP BY c.id, c.id, i.name
ORDER BY c.id ASC";
I did a search but I couldn't find anything yet with doctrine
$t = $this->createQueryBuilder("c")
->select("c")
->from(Plant::class, "p")
->leftJoin('p.categories', 'cc')
->leftJoin('(SELECT id, name FROM plant
ORDER BY id DESC LIMIT 1)
ON pc.plant_id = i.id', "i");
Doctrine ORM works a bit differently than working with a standard SQL query.
To work with the entities, the separation of concerns changes from an abstraction (non-deterministic array) to a static model (deterministic object).
In that your Category entity is expected to have an association for the i.name column of your query for the desired end-result, otherwise a partial object is implicated, which is highly discouraged due it causing an invalid state of the entity.
You're attempting to solve for a special circumstance where you want to show a single specific record from the ManyToMany association of the Category::$plants collection, which goes against the Object Model design pattern. The object models are expected to be Valid at all times, so accounting for those special circumstances from the context of the ORM breaks that core principal.
In short, the ORM should not be doing that, you're most likely looking for an array result (as given in the answer by #JeanCapelle) or a native query.
Criteria on the Object Model
The simplest workaround is to utilize the object model with a criteria applied to the collection to filter the results and obtain the desired results. However, the use of criteria on the model is also highly discouraged as it heavily impacts performance and has implications of an invalid entity state.
use Doctrine\Common\Collections\Criteria;
class Category
{
/**
* #return Collection|Plants[]
*/
private $plants;
public function __construct()
{
// ...
$this->plants = new ArrayCollection();
}
public function getPlants(): Collection
{
return $this->plants;
}
public function getLatestPlant(): ?Plant
{
$criteria = Criteria::create()->setMaxResults(1);
return $this->plants->matching($criteria->orderBy(['id', 'DESC']))->first() ? : null;
}
}
Then the query would be changed to the following.
$qb = $this->_em->createQueryBuilder();
$t = $qb->select('c')
->from(Category::class, 'c')
->addSelect('p')
->leftJoin('c.plants', 'p')
->orderBy('c.id', 'ASC');
$categories = $t->getQuery()->getResult();
foreach ($categories as $category) {
$latestPlant = $category->getLatestPlant();
echo $latestPlant->getName();
}
Object Model Association on Latest
The more favorable solution considered best-practice, would be to add the foreign key association to the Category table and a ManyToOne relationship on the Category Entity, instead of relying on a subquery to filter from the association. Where the latest plant is maintained through the application and the object model lifecycle.
Category table: id, name, description, slug, latest_plant
class Category
{
/**
* #return Collection|Plants[]
*/
private $plants;
/**
* #return null|Plant
* #ORM\ManyToOne(targetEntity=Plant::class)
* #ORM\JoinColumn(name="latest_plant", referencedColumnName="id", nullable=true)
*/
private ?Plant $latestPlant = null;
public function getPlants(): Collection
{
return $this->plants;
}
// simple example on how to maintain relationship
public function addPlant(Plant $plant)
{
if (!$this->plants->contains($plant)) {
$this->plants->add($plant);
$plant->addCategory($this);
$this->setLatestPlant($this->plants->last() ?: null);
}
}
// simple example on how to maintain relationship
public function removePlant(Plant $plant)
{
if ($this->plants->contains($plant)) {
$this->plants->removeElement($plant);
$plant->removeCategory($this);
if ($this->latestPlant && $this->latestPlant->getId() === $plant->getId()) {
$this->setLatestPlant($this->plants->last() ?: null);
}
}
}
public function getLatestPlant(): ?Plant
{
return $this->latestPlant;
}
public function setLatestPlant(?Plant $plant)
{
$this->latestPlant = $plant;
}
}
Then the query would look like the below.
$qb = $this->_em->createQueryBuilder();
$t = $qb->select('c')
->from(Category::class, 'c')
->addSelect('lp')
->leftJoin('c.latestPlant', 'lp') // Category::$latestPlant
->orderBy('c.id', 'ASC');
$categories = $t->getQuery()->getResult();
foreach ($categories as $category) {
$latestPlant = $category->getLatestPlant();
echo $latestPlant->getName();
}
If your SQL querie is what you expect, I'll suggest you to try something like :
$qb->select('c.id AS category_id', 'c.name', 'c.slug', 'i.name')
->from('Category', 'c')
->leftJoin('c.plantCategories', 'pc')
->innerJoin(
'(SELECT p.id, p.name FROM Plant p ORDER BY p.id DESC LIMIT 1)',
'i',
Join::WITH,
'pc.plant = i.id'
)
->groupBy('c.id', 'c.name', 'c.slug', 'i.name')
->orderBy('c.id', 'ASC');
$results = $qb->getQuery()->getResult();
Related
Good morning,
Is it exist an function where I pass an entity and the propertyName and return me the mappedBy,inversedBy and absoluteClassName of an Entity.
The goal is to use the __call to create automatic getteur/setteur and addFucntion bidirectionnal.
I don't want to use generates Entities I want all getteur,setteur and add Function use __call.
But i can"t do an addBirectionnal if i don't know if the relation is many to many or one to many and if i don't know the name of the mappedBy.
my code:
public function __get($p){
return $this->$p;
}
public function __set($p,$v){
$this->$p = $v;
return $this;
}
public function __call($name,$arguments){
if(substr($name,1,3)=='et')
$name2 = substr(3);
if($name[0] == 'g'){
return $this->$name2;
}else{//substr($name,0,1) == 's'
$this->$name2 = $arguments[0];
/*for a one to one*/
/*$mappedByName= getmappedByOrInversedBy(get_class($name),$name2);
if($mappedByName){
$this->$name->$mappedByName = $this;/
}*/
return $this;
}
}
}
I need getmappedByOrInversedBy, thanks.
edit: I try this
public function test(){
$str = "AppBundle\Entity\Group";
$mapping = new \Doctrine\ORM\Mapping\ClassMetadataInfo($str);
$d = $mapping->getAssociationMappedByTargetField('trad');
var_dump($d);
return $this->render('default/index.html.twig', array(
'base_dir' => realpath($this->getParameter('kernel.root_dir').'/..'),
));
}
class Group
{
...
/**
* #ORM\OneToOne(targetEntity="Traduction",inversedBy="grp")
*/
protected $trad;
}
Result : Undefined index: trad
The ClassMetadataInfo is what you are looking for.
Creates an instance with the entityName :
$mapping = new \Doctrine\ORM\Mapping\ClassMetadataInfo($entityNamespaceOrAlias);
Then, get the informations you want :
Get all association names: $mapping->getAssociationNames();
Get the join column of an association:
$mapping->getSingleAssociationJoinColumnName($fieldName);
Get the mappedBy column of an association:
$mapping->getAssociationMappedByTargetField($fieldName);
...
Look at the class to know which method you can access.
Hopes it's what you expect.
EDIT
As you can access the EntityManager (i.e. from a controller), use :
$em = $this->getDoctrine()->getManager();
$metadata = $em->getClassMetadata('AppBundle:Group');
To be sure there is no problem with your entity namespace, try :
print $metadata->getTableName();
To retrieve the associations of the entity, use :
$metadata->getAssociationNames();
And to get the mapping informations of an existing association, use :
$metadata->getAssociationMapping($fieldName);
And to get all the association mappings of your entity, use:
$metadata->getAssociationMappings();
I want to check if the current user has already a record for the current date.The User entity has many Timerecord and Timerecord has one User. So far,
This two entities resides in two different bundles
Project
UserBundle
TimerecordBundle
controller
$user = $this->container->get('security.context')->getToken()->getUser();
$userr = $user->getUsername();
$alreadyLoggedIn = $em->getRepository('EmployeeBundle:Timerecord')->findAlreadyTimedInToday($userr);
var_dump($alreadyLoggedIn);
die();
Respository
public function findAlreadyTimedInToday($userr)
{
return $this
->createQueryBuilder('t')
->select('u.username')
->from('User u')
->join('u.Timerecord t')
->where('u.username LIKE :currentuser')
->setParameter('currentuser',$userr)
->getQuery()
->getSingleResult()
;
}
I got this exception
Warning: Missing argument 2 for Doctrine\ORM\QueryBuilder::from()
How do you fetch the related user in this case?
The alias for the from part should be given to the second argument.
Change your method to this:
public function findAlreadyTimedInToday($userr)
{
return $this
->createQueryBuilder('t')
->select('u.username')
->from('User', 'u')
->join('u.Timerecord', 't')
->where('u.username LIKE :currentuser')
->setParameter('currentuser', $userr)
->getQuery()
->getSingleResult()
;
}
Laravel 4 is giving me unexpected results on a new project. To try and help me better understand the results I tried what I thought would be a simple exercise and use a friends old WordPress blog on his web hosting.
Post model:
class Post extends Eloquent {
protected $table = 'wp_posts';
public function meta()
{
return $this->hasMany('Meta', 'post_id');
}
}
Meta model
class Meta extends Eloquent {
protected $table = 'wp_postmeta';
public function post()
{
return $this->belongsTo('Post');
}
}
I have tried all of these variations with no avail...
TestController:
public function get_index()
{
// $test = Post::with('Meta')->get();
// $test = Post::with('Meta')->where('id', '=', '219')->first();
// $test = Post::find(219)->Meta()->where('post_id', '=', '219')->first();
// $test = Post::find($id)->Meta()->get();
// $test = Meta::with('Post')->get();
$id = 219;
$test = Post::find($id)->meta;
return $test;
}
returned in the event listener:
string(47) "select * from `wp_posts` where `id` = ? limit 1" string(65) "select * from `wp_postmeta` where `wp_postmeta`.`post_id` is null" []
Please tell me I am just overlooking something really minor and stupid and I just need some sleep.
Though is that while the SQL is case-insensitive, the array of attributes the model gets populated with is a PHP array where indexes are case-sensitive. The schema had the primary key as ID
I have a onetomany relationship between event and participant entities.
In my controller i can do the following:
$participants = $event->getParticipant();
But now i only want the Participants where the property visible has the value 1.
how can i get those collection? because participant is an arraycollection of all participants with participant.event_id = event.id in the event entity.
You can easily create a method in your EventRepository that joins only the visible participants with the event:
// YourBundle/Entity/EventRepository.php
public function getEventFilterVisibleParticipants($event_id)
{
return $repository->createQueryBuilder('event')
->where('event.id = :event_id')
->leftJoin('event.participants', 'participant', 'WITH', 'participant.visible = :visibility')
->setParameter('event_id', $event_id)
->setParameter('visibility', 1)
->orderBy('event.startDate', 'DESC')
->getQuery()
->getResult()
;
}
... Now in your controller do something like this:
$event = $this
->getDoctrine()
->getRepository('YourBundle:Event')
->getEventFilterVisibleParticipants($id)
;
$participants = $event->getParticipants(); // returns the filtered collection
I have a small symfony2 application where a user can create pages. Each page should be accessible via the route /{user_slug}/{page_slug}. I have the entities user and page and I use the sluggable behavior for both entities. In order to find the correct page the combination of user_slug and page_slug has to be unique.
What is the best way to check that the combination of user_slug and page_slug is uniqe?
Try this in your prepository:
public function findByUsernameAndSlug($username, $slug)
{
$em = $this->getEntityManager();
$query = $em->createQuery("
SELECT g
FROM Acme\PagesBundle\Entity\Page p
JOIN p.owner u
WHERE u.username = :username
AND p.slug = :slug
")
->setParameter('username', $username)
->setParameter('slug', $slug);
foreach ($query->getResult() as $goal) {
return $goal;
}
return null;
}
Before you persist the entity in your service-layer check whether given combination of user and page slug are unique, if not modify the page slug (append -2 or something like that) or throw an exception:
public function persistPage(Page $page) {
$userSlug = $page->getUser()->getSlug();
$pageSlug = $page->getSlug();
if ($this->pagesRepository->findOneBySlugs($userSlug, $pageSlug) != null) {
// given combination already exists
throw new NonUniqueNameException(..);
// or modify the slug
$page->setSlug($page->getSlug() . '-2');
return $this->persistPage($page);
}
return $this->em->persist($page);
}
// PagesRepository::findOneBySlugs($userSlug, $pageSlug)
public function findOneBySlugs($userSlug, $pageSlug) {
$query = $this->em->createQueryBuilder('p')
->addSelect('u')
->join('p.user', 'u')
->where('p.slug = :pageSlug')
->where('u.slug = :userSlug;)
->getQuery();
$query->setParameters(combine('userSlug', 'pageSlug'));
return $query->getSingleResult();
}