I have "Person" entity that has a property "Status" and this property is an OneToMany relationship in Doctrine.
/**
*
* #ORM\OneToMany(targetEntity="\My\Bundle\Entity\Status", mappedBy="person")
*
**/
protected $status;
What I need to do is to display in my view is just the last status.
How can I get just the last status in my twig view? Is there something like, for exmample, {{ person.status.last }} ?
Or should I query the last status in my controller and pass it to view as another var?
Yes, you can do it exactly like this {{ person.status.last.someField }} to echo a someField property of last status (in natural query order) for person object.
This is possible because person.status is a Doctrine Collection which has methods like first or last. You can check this for more information.
Related
Question really relates to best practice and whether what I have in my head is possible. I am querying for a collection of Member entities using a Repository function, for example (simplified)
/**
* #return Query
*/
public function findAllMembersOrderedByNameResult()
{
return $this->createQueryBuilder('m')
->orderBy('m.lastName', 'ASC')
->getQuery()
->getResult()
;
}
I am calling this in my Controller:
$members = $em->getRepository(Member::class)->findAllMembersOrderedByNameResult();
Now I am passing the result this to my twig template file and can loop through this and display information about each member as part of a foreach loop:
{% for member in members %}
{{member.firstName}}
{% endfor %}
This obviously works fine, however I have a need now to add some additional data to each member to pass to the twig template. So for example using the DateOfBirth in the Member entity I want to run this through a function to determine the age of the member to display in the Twig template. So at the moment I am making this call in the Controller, and then passing it over to the template by creating a MembersArray, looping through the returned $members Collection and adding in the new age and whole Member result as two separate values in the array, for example:
foreach($members as $member)
{
$membersArray[] = array(
'member' => $member,
'age' => $age
)
}
This does work, however in my Twig template I now have to use
{% for member in membersArray %}
{{member.member.firstName}}
{{member.age}
{% endfor %}
It would be much nicer to be able to just add age into the Collection without creating the array so I can just use member.firstName and member.age but for the life of me can't find a way how without having to loop through all values manually and set them to a new array, which seems a huge waste of code.
I will want to add more than just age, this is just a simplified example. Using Symfony 4.4 and PHP 7.3 in case anything that would help requires it
Edit: Entity structure as requested (cutdown):
namespace App\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="App\Repository\MemberRepository")
*/
class Member
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255, nullable=true)
*/
private $firstName;
/**
* #ORM\Column(type="date", nullable=true)
*/
private $dateOfBirth;
...
The simplest way would be adding custom getter methods to an entity itself, like:
public function getAge()
{
return (new DateTime())->diff($this->dateOfBirth)->y;
}
Then you can calculate the age in a twig template by calling member.age.
But usually, it's not recommended to have any logic in an entity itself.
Another way is using twig itself to format data. I would say it is preferable with the age example. Because age looks more like the view format of the dataOfBirth field than extra data, you can use a twig filter to calculate that. The built-in like {{ date().diff(member.dateOfBirgth)).format('%y') }}, or define a custom one with a twig extension, so the syntax would be more straightforward, like {{ member.dateOfBirgth|age }} or even {{ member|age }}.
In case you have to reuse formatting not only in twig but also in some services and don't want to put logic into an entity - you can decouple twig extension from the previous example to use the shared service that is responsible for formatting age. Other parts of the system can use the same formatting service.
Also, it's a common practice to put new methods to work with an entity to a custom entity manager service, like MemberManager. Usually, we use entity manager to manipulate entities, but you can add there the method to format age, like MemberManager->getAge($member). This would violate the single responsibility principle, so I can't recommend it.
If you are looking for extending doctrine entities with some extra methods with listeners or so, it's not possible by doctrine design. Anyway, it's the most, not obvious way.
To summarise, in most cases, when the custom function can be considered as a formatting one, I would recommend using the second option, with decoupling it to the third option whenever you have to reuse the formatting code. But if the project isn't complex, the first option also worth checking, as it's the simplest one.
I have two separate entities that I want to link by a one-to-many relationship. But I want this relationship to be ordered, meaning every time I call on the first entity, the elements of the second entity come in a pre-ordered way. I cannot use 'order by' calls because this order has nothing to do with the fields of the second entity. I thought about having one field of the first entity be an array of entities, but I'm not sure how to accomplish that either..
EDIT
So far I have something like this:
/**
* #ORM\OneToMany(targetEntity="FusionDesign\BlogBundle\Entity\Element", mappedBy="page")
*/
private $elements;
and
/**
* #ORM\ManyToOne(targetEntity="FusionDesign\BlogBundle\Entity\Page", inversedBy="elements")
* #ORM\JoinColumn(name="page_id", referencedColumnName="id")
*/
private $page;
I'm aware that I can put "ORDER BY whatever ASC" somewhere in there but that orders according to a column in Element, and that's not what I need, because Element entities and Page entities would never be persisted at the same time, nor by the same process. What I want to do is constructing a basic CMS where the user could generate new pages. First choose the kind of elements a page could potentially have (like header image, banner, title, and so on) and persist Element entities with fields describing the html, routing and controller content according to those choices. Then, when a new page is created, give the user the choice to order those potential elements at will, and bind Element entities following an order that reflects the layout desired.
I thought about having something like this
/**
* #var array
*
* #ORM\Column(name="structure", type="array")
*/
private $structure;
Where the array stores Element entities but I have no idea how to do that.
You just need to define the orderBy attribute in doctrine's mapping configuration for the relation.
YAML Mapping Example:
'Example\Entity\Article':
# [..]
oneToMany:
images:
targetEntity: 'Example\Entity\Article\Image\ArticleImage'
mappedBy: 'article'
orderBy: # <--- here
position: 'ASC'
Product and Image are two entities linked by a oneToMany association (one product has many images). I try to enumerate with TWIG each product with the first image (filename fied) like this :
class ProductRepository extends EntityRepository
{
public function getProductsWithImages() {
$query = $this->createQueryBuilder('e')
->leftJoin('e.images', 'i', 'with', 'i.order = :order')
->setParameter('order' , 0)
->select('e')
->addSelect('i');
return $query->getQuery()->getResult();
}
}
But I got this error :
Method "filename" for object "\entity\product" does not exist.
I understand why (product entity has no image field). What is the best pratice to get only one child element without add a reference on the parent (like a mainImage field) ?
As the doctrine documentation explains :
A one-to-many association has to be bidirectional, unless you are
using an additional join-table. This is necessary, because of the
foreign key in a one-to-many association being defined on the “many”
side. Doctrine needs a many-to-one association that defines the
mapping of this foreign key.
I have this kind of relation in my current project and I simply defined bidirectional one-to-many association. So in your twig view you should be able to do for example :
{# first Image linked to the Product #}
{{ product.images.first }}
The attribute images is an ArrayCollection.
Hope it helps
My issue is, I'm having trouble grasping DiscriminatorColumn and DiscriminatorMap in Doctrine's Class Inheritance.
I have a products entity that is considered the parent class / table.
There are several child entities that inherit the product entity. (models, parts, and options)
I feel like I should be able to use the primary key to link both tables... But how do I do that with DiscriminatorColumn?
Here is the general idea of what I want to happen...
Fetch all model objects from database while inheriting product parent entity
SELECT object
FROM parts_object parts
LEFT JOIN products_object po
ON parts.product_fk = po.product_id
Or... Fetch all part objects from database while inheriting product parent entity
SELECT object
FROM parts_object parts
LEFT JOIN products_object po
ON parts.product_fk = po.product_id
Ideally I want this done using Doctrine instead of some custom SQL.
Do I need to setup a "type" column for the parent table so each row defines whether it's a part, model, or option?
Doctrine inheritance docs
Okay, I'll try to explain this as simple as possible.
Let's start with DiscriminatorColumn
Discriminator column is basically, as it says, a column in your database. Its used to store, a key, if you like which helps to identify what kind of object you're currently querying, based on your DiscriminatorMap configuration.
DiscriminatorMap is the way you map each of those keys to an entity. You said you have the following
Product [parent]
Model [child of parent]
Part [child of parent]
Option [child of parent]
Then, your discriminator map should look something like this, for example:
#DiscriminatorMap({
"model" = "AppBundle\Entity\Model",
"Part" = "AppBundle\Entity\Part",
"Option" = "AppBundle\Entity\Option"
})
Always pay attention to your last definition in your mapping. The last line must end without a comma!
As of InheritanceType I would suggest you to use #InheritanceType("JOINED") because this will let you have single table for each of your child classes.
Every child class must extend your Product entity class, which is obviously the parent. Each child class must not define $id property, because of the inheritance mapping.
Then querying for records by specific type comes with the following query:
"SELECT product FROM AppBundle\Entity\Product product WHERE product INSTANCE OF AppBundle\Entity\Part"
The query will search only for records mapped to this entity only.
If you have any questions, don't hesitate to ask.
Edit as of new comment
-----------------------
A little bit more explanation. You do not need to create any extra property/column in your entity mappings. The moment you add this annotation #DiscriminatorColumn(name="discr", type="string") doctrine will create that column automatically for you. The column from this example would be named discr with type of VARCHAR.
I still don't understand what is used to join the tables. How does doctrine know to link the ids between the product and model
About this part. If you use #InheritanceType("JOINED") this would mean that your GeneratedValue ID would be set in your main entity - Product. Then each of the child entities that extend Product would automatically get the same ID, which is why you don't need to specify $id property in your child entities.
Lastly, how can you check which entity type you're currently viewing for example. Consider the following scenario, each of your child entities extends Product and we will perform a dummy search for a record:
$product = $entityManager->find('AppBundle:Product', 1); // example
Now, if you actually go and do a var_dump($product) you will notice something interesting. The object would be an instance of either Model,Part or Option because each of these entities are defined in your discriminator map and Doctrine automatically maps your records based on that.
Later, this can come handy in situations like this:
if( $product instanceof \AppBundle\Entity\Part ) {
// do something only if that record belongs to part.
}
If you want to use DiscriminatorMap for Doctrine, so you should use Doctrine, but not SQL.
Basic setup is:
/**
* #ORM\Table(name="product")
* #ORM\Entity(repositoryClass="MyApp\ProductBundle\Repository\ProductRepository")
* #ORM\InheritanceType("SINGLE_TABLE")
* #ORM\DiscriminatorColumn(name="productType", type="string")
* #ORM\DiscriminatorMap({
* "Product" = "Product",
* "Model" = "Model",
* "Part" = "Part",
* "Option" = "Option",
* })
*/
class Product
{
...
}
MyApp\ProductBundle\Entity\Model
/**
* #ORM\Entity(repositoryClass="MyApp\ProductBundle\Repository\ModelRepository")
*/
class Model extends Product
{
}
MyApp\ProductBundle\Entity\Part
/**
* #ORM\Entity(repositoryClass="MyApp\ProductBundle\Repository\PartRepository")
*/
class Part extends Product
{
}
MyApp\ProductBundle\Entity\Option
/**
* #ORM\Entity(repositoryClass="MyApp\ProductBundle\Repository\OptionRepository")
*/
class Option extends Product
{
}
Then if you need to get all products at controller
$em = $this->getDoctrine()->getManager();
$repo = $em->getRepository("MyAppProductBundle:Product");
$products = $repo->findAll();
Then if you need select all models, just setup proper repository
$repo = $em->getRepository("MyAppProductBundle:Model");
$models = $repo->findAll();
I want to display a table with some entity relations from a Doctrine DQL query.
My "main" entity Lead has a relation with Tour like this:
class Lead {
/**
* #var integer $tourId
* #ORM\Column(name="`tour_id`", type="integer")
*/
private $tourId;
/**
* #var Tour $tour
* #ORM\ManyToOne(targetEntity="Tour")
* #ORM\JoinColumn(name="tour_id", referencedColumnName="id")
*/
private $tour;
...
}
And I get the data from DB with a Doctrine2 DQL:
SELECT l, c
FROM BuvMarketplaceBundle:Lead l '
JOIN l.client c
Note that I don't JOIN with Tour becouse not all Leads have a Tour associated, this field can be null.
Then I am printing like this:
{% for lead in leads %}
{{ lead.id }}
{% if lead.tour %}
{{ lead.tour.name }}
{% endif %}
{% endfor %}
The problem comes where lead.tour has a numeric value, but this value does not exists in the tours table (because it has been deleted). I get the "Entity was not found." exception that refers to the lead.tour that does not exist in the DB.
I tried to check lead.tour with is defined, is not null but nothing works.
Twig is not supporting the cheking of types, so there is no basic function available to check lead.tour is object or similar.
Is there any way that can check an object from Twig or left join from the DQL?
A left join will solve the problem of not all leads having a tour.
SELECT lead,tour
FROM BuvMarketplaceBundle:Lead lead
LEFT JOIN lead.tour tour
And as my comment indicates, in a properly setup Doctrine 2 model, it will not be possible for lead to point to a tour record that does not exist.