Doctrine says a field doesn't exist when it does - Symfony2 - symfony

I have an entity:
...
class UserRole extends Role
{
/**
* #ORM\Column(name="user_role_id", type="integer")
* #ORM\Id()
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
...
But when I try to use user_role_id in a doctrine query I get this error:
[Semantical Error] line 0, col 42 near 'user_role_id': Error: Class
MyApp\Model\UserRole has no field or association named user_role_id
The query I am using is this:
$query = $this->getEntityManager()
->createQuery('SELECT r FROM Model:UserRole AS r WHERE r.user_role_id IN (:roles)')
->setParameter('roles', array_values($roles));
I've definitely got a user_role_id field as I can see it in phpMyAdmin.
Does anyone have any idea why doctrine is not recognising it?

This is doctrine and you want to write a DQL not a SQL, and you need to use the field names as defined in entities not the column names.
So in your query just use r.id instead of r.user_role_id
Take a look at DQL in Doctrine

Related

Symfony Doctrine order oneToMany by specific order

I use Symfony 4, and I have a OneToMany relation. I want to order this relation by a specific order. For now, I order only by ASC :
/**
* #ORM\OneToMany(targetEntity="App\Entity\Ingredients", mappedBy="product", fetch="EAGER")
* #ORM\OrderBy({"family" = "ASC"})
*/
private $ingredients;
It works perfectly.
Now I would like to order by a specific order : "vegetables", "fruits" then "meat".
I tried :
/**
* #ORM\OneToMany(targetEntity="App\Entity\Ingredients", mappedBy="product", fetch="EAGER")
* #ORM\OrderBy({"family" = "vegetables, fruits, meat"})
*/
private $ingredients;
Which is of course doesn't work. Still trying, but is there an easy way to achieve it ?
As noted in https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/reference/annotations-reference.html#annref_orderby
The DQL Snippet in OrderBy is only allowed to consist of unqualified, unquoted field names and of an optional ASC/DESC positional statement. Multiple Fields are separated by a comma (,). The referenced field names have to exist on the targetEntity class of the #ManyToMany or #OneToMany annotation.
However if you use DQL you should be able to accomplish this with something like:
$qb = $em->getRepository(Ingredients::class)->createQueryBuilder('i');
$results = $qb->orderBy('FIELD(family,vegetables,fruits,meat)')
->getQuery()
->getResults();
** I havent tested this code **

Join from inversed side

given the two following intities:
<?php
/**
* User
* #ORM\Entity()
*/
class User implements AdvancedUserInterface, \Serializable, EncoderAwareInterface
{
/**
* #var Vip
* #ORM\OneToOne(targetEntity="Vip", mappedBy="user", fetch="EAGER")
*/
protected $vip;
// …
<?php
/**
* Vip
* #ORM\Entity()
*/
class Vip
{
/**
* #ORM\id #ORM\OneToOne(targetEntity="User", inversedBy="vip", fetch="EAGER")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id", nullable=false)
*/
protected $user;
// …
SHORT :
How can I do this SQL in DQL given above entities:
SELECT u.firstName, v.foo FROM User join Vip v ON v.user_id = u.id
In other words how can I retrieve 10 first users ( with their VIP infos if it exists), using DQL join in such a way that only one SQL query will be generated by Doctrine. Is that possible ?
Long story:
The owning side is the Vip entity because it holds the reference/foreign key to a User underneath in the database.
I am trying to retrieve all User with their Vip datas.
Using the knplabs/knp-paginator-bundle, I first set up a simple query:
$dql = "SELECT u, p FROM AppBundle:User u;
In spite of enforcing the fetch attribute as « EAGER », Vip infos where not part of the initial query. As a consequence, calling getter getVip() on each iteration from inside the twig for in loop like
{% for user in pagination %}
{% if user.getVip() %}
<span class="label label-warning">V.I.P</span>
{% endif %}
{{% endfor %}}
.. caused a query to be issued on each iteration !
The Symfony dev bar shows 6 queries:
DQL documentation and says that one can use JOIN keyword. So my query became:
$dql = "SELECT u, v FROM AppBundle:User u JOIN u.vip v;
But now I get this error:
Warning: spl_object_hash() expects parameter 1 to be object, null
given
Here I'm stuck, wondering how I could fetch Vip datas (or null) along with User datas, in a single query.
In other words how can I retrieve 10 first users ( with their VIP
infos if it exists), using DQL join in such a way that only one SQL
query will be generated by Doctrine. Is that possible ?
You should initialize all related entities using select clause to avoid additional queries when accessing to the related objects.
$repository = $em->getRepository(User::class);
$users = $repository->createQueryBuilder('u')
->addSelect('v') // Initialize Vip's
->join('u.vip', 'v')
->getQuery()
->setMaxResults(10)
->getResult();
Yes, one may add associated entities to the SELECT statement.
But more precisely, one should only add relations that are really involved in the expected result , in other words, entities fetched as "EAGER".
I realized that the vip entity had another relation (oneToMany with a vehicule entity). I just want to retrieve users with their vip metas. Adding another JOIN to the query would just bring more datas since I would not use vehicules anyway (and issue extra work behind the scenes).
-> So I simply changed the fetch attribute from "EAGER" to "LAZY" in the vip OneToMany declaration.
To conclude:
Ask yourself «what are involved intities ?», should it be part
of the result (do you simply need those infos).
if NO, you might turn fetch attribute to "[EXTRA_]LAZY" in the relation declaration like
/**
* #ORM\OneToMany(targetEntity="Vehicule", mappedBy="vip", fetch="LAZY", …)
*/
protected $vehicules;
if YES you will have to select those entities in your query.
Using DQL:
SELECT u, v, w FROM AppBundle:User u LEFT JOIN u.vip v LEFT JOIN v.vehicules w
Using queryBuilder:
$repository = $em->getRepository(User::class);
$users = $repository->createQueryBuilder('u')
->addSelect('v')
->join('u.vip', 'v')
->addSelect('w')
->join('v.vehicules', 'w')
// …

Symfony entities without relational

I work with Symfony2 and Doctrine and I have a question regarding entities.
In a performance worries, I'm wondering if it is possible to use an entity without going all the associations?
Currently, I have not found another way to create a model inheriting the class with associations and associations specify NULL in the class that inherits.
thank you in advance
OK, a little detail, it's for a API REST (JSON).
This is my class :
/**
* Offerequipment
*
* #ORM\Table(name="offer_equipment")
* #ORM\Entity(repositoryClass="Charlotte\OfferBundle\Repository\Offerequipment")
*/
class Offerequipment
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Charlotte\OfferBundle\Entity\Offer")
* #ORM\JoinColumn(name="offer_id", referencedColumnName="id")
*/
private $offer;
/**
* #ORM\ManyToOne(targetEntity="Charlotte\ProductBundle\Entity\Equipment")
* #ORM\JoinColumn(name="equipment_id", referencedColumnName="id")
*/
private $equipment;
/**
* #VirtualProperty
*
* #return String
*/
public function getExample()
{
return $something;
}
and with QueryBuilder method, i can't get my virtual properties or getters.
Thanks for your help :)
Look at Serialization.
By serialising your entities, you can choose to exclude or expose a property of an entity when you render it.
Look at the Symfony built-in Serializer and/or JMSSerializer.
Otherwise, you can use QueryBuilder and DQL to choose what fields you want to fetch in your queries.
Like this, you can make your own find method in the Repository of your entities.
// src/AcmeBundle/Repository/FooRepository
class FooRepository extends \Doctrine\ORM\EntityRepository
// ...
public function find($id) {
$queryBuilder = $this->createQueryBuilder('e')
->select('e.fieldA', 'e.fieldB') // selected fields
->where('e.id = :id') // where statement on 'id'
->setParameter('id', $id);
$query = $queryBuilder->getQuery();
$result = $query->getResult();
}
// ...
}
Don't forget define the Repository in the corresponding Entity.
/**
* Foo.
*
* #ORM\Entity(repositoryClass="AcmeBundle\Repository\FooRepository")
*/
class Foo
{
// ...
}
By default Doctrine will not automatically fetch all of the associations in your entities unless you specifically each association as EAGER or unless you are using a OneToOne association. So if you are looking to eliminate JOINs, you can just use Doctrine in its default state and it won't JOIN anything automatically.
However, you this will not alleviate all of your performance concerns. Say, for example, you are displaying a list of 50 products in your application on a single page and you want to show their possible discounts, where discounts are an association on your product entity. Doctrine will create 50 additional queries just to retrieve the discount data unless you explicitly join the discount entity in your query.
Essentially, the Symfony profiler will be your friend and show you when you should be joining entities on your query - don't just think that because you aren't joining associations automatically that your performance will always be better.
Finally, after many days, I've found the solution to select only one entity.
VirtualProperties are found :)
public function findAllByOffer($parameters)
{
$queryBuilder = $this->createQueryBuilder('oe');
$queryBuilder->select('oe, equipment');
$queryBuilder->join('oe.equipment', 'equipment');
$result = $queryBuilder->getQuery()->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true)->getResult();
return $result;
}

Symfony2 and Doctrine - Error: Invalid PathExpression. Must be a StateFieldPathExpression

I have an entity that looks like this:
/**
* #Gedmo\Tree(type="nested")
* #ORM\Table(name="categories")
* #ORM\Entity()
*/
class Category extends BaseCategory
{
/**
* #ORM\OneToMany(targetEntity="Category", mappedBy="parent")
*/
protected $children;
/**
* #Gedmo\TreeParent
* #ORM\ManyToOne(targetEntity="Category", inversedBy="children")
* #ORM\JoinColumn(name="parent_id", referencedColumnName="id", onDelete="SET NULL")
*/
protected $parent;
}
and I am trying to run a query like this:
$qb = $this->em->createQueryBuilder()
->select('c.parent')
->from('Category', 'c');
$result = $qb->getQuery()->getArrayResult();
However, I am getting the following error:
[Semantical Error] ... Error: Invalid PathExpression. Must be a StateFieldPathExpression.
How can I select the parent_id field from my table. I have tried a bunch of variations and even if I do something like this:
$qb = $this->em->createQueryBuilder()
->select('c')
->from('Category', 'c');
I get all fields in the table except for the parent_id. This seems like Doctrine is getting in the way. How can I query for this parent_id field? or better yet how can I get all fields in the table including the parent_id
You can use the currently undocumented IDENTITY function to select the FK IDs in a query:
SELECT IDENTITY(c.parent) ...
Solution using createQueryBuilder:
$query->SELECT('pa.id')
->from('Category', 'ca');
$query->join('ca.parent', 'pa');
$result = $query->getQuery()->getArrayResult();
You are selecting an object that is not joined.
Like said in another answer, you have to do something like :
qb->innerJoin("c.parent", "p")
You can change it like this:
->select(array('i.id','identity(i.parent) parent','i.nom'))

Symfony2 building ManyToOne relationship in Entities

I have two entities in a OneToMany relationship, Perfil and IPerfil. Perfil can have several IPerfil's associated:
>
class Perfil
{
/**
* #var integer $id
*
* #ORM\Column(name="id_perfiles", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="SEQUENCE")
* #ORM\SequenceGenerator(sequenceName="P360.perfiles_seq")
* #ORM\OneToMany(targetEntity="IPerfil", mappedBy="id")
*/
private $id;
And the other one:
>
class IPerfil
{
/**
* #var integer $id
* #ORM\ManyToOne(targetEntity="Perfil")
* #ORM\JoinColumn(name="id_perfiles", referencedColumnName="id_perfiles", onDelete="CASCADE")
*/
protected $id;
I'm able to lazyload from IPerfil to Perfil, but not in the other direction (from Perfil to IPerfil), but if I can't do any DQL over IPerfil or createQueryBuilder()->setPameter... Symfony will always return an error like this:
[Semantical Error] line 0, col 9 near 'id_perfiles FROM': Error: Invalid PathExpression. Must be a StateFieldPathExpression.
If I define the field Id with an
#ORM\Column(name="id_perfiles", type="integer")
Then I'm able to build DQL's with the entity, but the relationship won't work anymore.
I'm lost around here... It seems I'm missing something in the relationship definition, but have no clue.
Thanks in advance for any tip.
EDIT
If I use:
$entities=$em->getRepository('usuariosBundle:IPerfil')->findAll(array());
It doen'st gives an error, but I need to filter and order the results.
$entities=$em->getRepository('usuariosBundle:IPerfil')->findBy(array(),array('id' => 'asc', 'descripcion' => 'asc'));
Unrecognized field: id
$query=$em->getRepository('usuariosBundle:IPerfil')->createQueryBuilder('p')
->orderBy('id', 'ASC')
->getQuery();
$entities = $query->getResult();
*[Semantical Error] line 0, col 60 near 'id ASC': Error: 'id' is not defined. *
$entities = $this->get('doctrine')->getEntityManager()->createQuery('SELECT p.id FROM usuariosBundle:IPerfil p ')->getResult();
[Semantical Error] line 0, col 9 near 'id FROM usuariosBundle:IPerfil': Error: Invalid PathExpression. Must be a StateFieldPathExpression.
Use attribute name of class, no column name on dql => 'id'
->orderBy('p.id', 'ASC') and ->createQuery('SELECT p FROM usuariosBundle:IPerfil p ')->getResult(), the name of bundle is usuarios os Usuarios ?
If it is useful to anyone, I finally created a non-persistent attribute in the Perfil entity to attach the the relationship and a collection of IPerfil's there:
/**
* #ORM\OneToMany(targetEntity="IPerfil", mappedBy="id")
*/
private $desc;
Need to declare it as a ArrayCollection:
public function __construct()
{
$this->desc = new \Doctrine\Common\Collections\ArrayCollection();
}
And now I'm able to lazy load in the right direction and do any necessary operation. I suppouse Doctrine couldnt load the collection into id, as it wasn't declared as a collection and it's the ID field.

Resources