Doctrine ORM, many to many, lazy loading. Retrieve an Entity and fetch join all the feautures - orm

I have Zend Framework 3 + Doctrine ORM application.
Class Goods have link "characters":
/**
* Goods
*
* #ORM\Entity
* #ORM\Table(name="goods")
* #property int $id
*/
class Goods implements InputFilterAwareInterface
{
/**
* #ORM\ManyToMany(targetEntity="\Application\Entity\CharacterValue", inversedBy="goods")
* #ORM\JoinTable(name="character_value_item",
* joinColumns={#ORM\JoinColumn(name="good_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="value_id", referencedColumnName="id")})
**/
protected $characters;
public function getCharacters()
{
return $this->characters;
}
}
I trying to use this method to get characters by method for lazy loading, but it returns just one character. Not all characters for the product.
$dql = 'SELECT u, ch FROM Goods u LEFT JOIN u.characters ch';
This method from here:
$query = $em->createQuery('SELECT u, p FROM CmsUser u JOIN u.phonenumbers p');
$users = $query->getResult(); // array of CmsUser objects with the phonenumbers association loaded
$phonenumbers = $users[0]->getPhonenumbers();
https://www.doctrine-project.org/projects/doctrine-orm/en/2.7/reference/dql-doctrine-query-language.html#dql-select-examples
I do not understand why documentation's method working wrong. What is the right way to deside my issue?

Related

Moodle get SQL data but don't get all

I'm working on a plugin for showing all users completed courses.
But I only get 10 records, when I place the SQL inside my database I get 40+. I think there is a limit or it does only return 1 course from every user.
Any tips?
externallib.php file:
/**
* External Web Service Template
*
* #package specialist_in_websites
* #copyright 2011 Moodle Pty Ltd (http://moodle.com)
* #license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
require_once($CFG->libdir . "/externallib.php");
class specialist_in_websites_users_courses_external extends external_api
{
/**
* Returns description of method parameters
* #return external_function_parameters
*/
public static function hello_world_parameters()
{
return new external_function_parameters(
array()
);
}
/**
* Returns welcome message
* #return string welcome message
*/
public static function hello_world()
{
global $DB;
$sql = 'SELECT u.id as user_id, c.id as course_id, DATE_FORMAT(FROM_UNIXTIME(p.timecompleted),"%Y-%m-%d") AS completed_time
FROM mdl_course_completions AS p
JOIN mdl_course AS c ON p.course = c.id
JOIN mdl_user AS u ON p.userid = u.id
WHERE c.enablecompletion = 1 ORDER BY u.id';
$datas = $DB->get_records_sql($sql);
return (array)$datas;
}
/**
* Returns description of method result value
* #return external_description
*/
public static function hello_world_returns()
{
return new external_multiple_structure(
new external_single_structure(
array(
'user_id' => new external_value(PARAM_INT, 'user id'),
'course_id' => new external_value(PARAM_INT, 'course id'),
'completed_time' => new external_value(PARAM_TEXT, 'Time of Completed'),
)
)
);
}
}
and response code:
string(510) "[{"user_id":2,"course_id":12,"completed_time":null},{"user_id":3,"course_id":10,"completed_time":null},{"user_id":4,"course_id":9,"completed_time":null},{"user_id":5,"course_id":41,"completed_time":null},{"user_id":6,"course_id":14,"completed_time":null},{"user_id":7,"course_id":10,"completed_time":null},{"user_id":8,"course_id":9,"completed_time":null},{"user_id":9,"course_id":9,"completed_time":null},{"user_id":10,"course_id":10,"completed_time":null},{"user_id":11,"course_id":10,"completed_time":null}]"
As stated in the docs the $DB->get_records_*() functions get a hashed array of records, indexed by the first field returned.
So, if you return more than one record with the same user_id field, then they will overwrite the previous record in the array.
Make sure you turn on debugging when developing for Moodle and you will see warning messages about this.
Solutions - either find a different field to be the first field returned (e.g. course_completions.id) or use $DB->get_recordset_sql() and then loop through the results.
Also, do not use the 'mdl_' prefix in your code, as that will break on any site with a different prefix, or if you try to run any behat or unit tests on your site. As stated in the docs, write:
FROM {course_completions} p
JOIN {course} c ON p.course = c.id
etc.
(and don't use AS with tables, as that's not compatible with all DB types)

Symfony 3 Doctrine Repository Join and array

I'm working on symfony 3 and got some problem with my builder on my repository.
How it's work:
I have an announce who have a lot of information like name, game etc...
And the user link by a relation OneToOne
I send you a part from my announce entity
class Announce
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\OneToOne(targetEntity="Wolk\UsersBundle\Entity\User")
* #ORM\JoinColumn(nullable=false)
*/
private $user;
/**
* #var string
*
* #ORM\Column(name="game", type="string", length=255)
*/
private $game;
Etc...
Now you can see my repository
public function byResearch($role, $game, $region, $language, $rank, $gamemode, $matchtype, $platform)
{
$qb = $this->createQueryBuilder('u')
->select('u');
if ($language != null) {
$qb->join('u.user' , 's');
$qb->addSelect('s');
$qb->andWhere('s.language like \''.$language.'\'');
}
if ($gamemode!= null) {
$qb->andWhere('u.gamemode = \''.$gamemode.'\'');
}
if ($matchtype!= null) {
$qb->andWhere('u.matchtype = \''.$matchtype.'\'');
}
if ($region!= null) {
$qb->andWhere('u.region = \''.$region.'\'');
}
if ($rank!= null) {
$qb->andWhere('u.rank like \'%'.$rank.'%\'');
}
if ($platform!= null) {
$qb->andWhere('u.platform like \'%'.$platform.'%\'');
}
if ($game!= null) {
$qb->andWhere('u.game = \''.$game.'\'');
}
if ($role!= null) {
foreach($role as $itm1)
{
$qb->andWhere('u.role like \'%'.$itm1.'%\'' );
}
}
$qb->andwhere('u.active = :active');
$qb->setParameter('active', '1');
$qb->orderBy('u.date', 'DESC');
return $qb->getQuery()->getResult();
}
My problem is about the language from my user
People will search for french, german, english only announce and all user have all their language in the entity user
User.Language = Array('fr', 'en') // something like that
And actually i have this result on my website (from the profiler)
SELECT
a0_.id AS id_0,
a0_.game AS game_1,
a0_.platform AS platform_2,
a0_.Availability AS Availability_3,
a0_.language AS language_4,
a0_.Description AS Description_5,
a0_.category AS category_6,
a0_.goal AS goal_7,
a0_.Rank AS Rank_8,
a0_.active AS active_9,
a0_.premium AS premium_10,
a0_.level AS level_11,
a0_.visit AS visit_12,
a0_.region AS region_13,
a0_.role AS role_14,
a0_.exp AS exp_15,
a0_.lan AS lan_16,
a0_.gamemode AS gamemode_17,
a0_.matchtype AS matchtype_18,
a0_.date AS date_19,
f1_.username AS username_20,
f1_.username_canonical AS username_canonical_21,
f1_.email AS email_22,
f1_.email_canonical AS email_canonical_23,
f1_.enabled AS enabled_24,
f1_.salt AS salt_25,
f1_.password AS password_26,
f1_.last_login AS last_login_27,
f1_.confirmation_token AS confirmation_token_28,
f1_.password_requested_at AS password_requested_at_29,
f1_.roles AS roles_30,
f1_.id AS id_31,
f1_.gender AS gender_32,
f1_.birthday AS birthday_33,
f1_.subscribedate AS subscribedate_34,
f1_.Country AS Country_35,
f1_.language AS language_36,
f1_.timezone AS timezone_37,
a0_.user_id AS user_id_38,
f1_.image_id AS image_id_39,
f1_.premium_id AS premium_id_40
FROM
announce a0_
INNER JOIN fos_user f1_ ON a0_.user_id = f1_.id
WHERE
f1_.language LIKE 'fr'
AND a0_.platform LIKE '%PC%'
AND a0_.game = 'lol'
AND a0_.active = ?
ORDER BY
a0_.date DESC
And i really don't know what is wrong about the language search
Join is maybe wrong for a relation OneToOne?
Maybe "Where like" for an array is not the good solution?
I'm searching since a day without knowing which things is not working so i hope you can help me in this :)
If you need more information, it's will be with pleasure
You forgot to add percent signs around language value. It should be:
$qb->andWhere('s.language like \'%'.$language.'%\'');

How to Join with Native SQL Query and doctrine

I'm developping an application with symfony 3.4. I want to execute a specific query. So i have two entities: the first is PrPurchaseRequest. the second is PrSpecificFieldValue. PrPurchaseRequest has oneToMany prSpecificFieldValues.
I want to get id of purchaseRequest and prSpecificFieldValues
i did that
$queryBuilder = $this->getEntityManager()->createQuery('select p.id as purchaseId, pr.keyField AS keyField,pr.ID AS prkeyvalueid from '.PrPurchaseRequest::class. ' p LEFT JOIN '. PrSpecificFieldValue::class .' spec ON p.id = spec.purchaseId ');
and that didn't work for me
[Syntax Error] Error: Expected end of string, got
'ON'
how can i do it
Using doctrine you need to play around your entities and their mappings with other entities in order to relate them like
use Doctrine\Common\Collections\ArrayCollection;
/** #Entity */
class PrPurchaseRequest
{
/**
*
* #OneToMany(targetEntity="PrSpecificFieldValue", mappedBy="prPurchaseRequest")
*/
private $prSpecificFieldValues;
// ...
public function __construct() {
$this->prSpecificFieldValues = new ArrayCollection();
}
}
/** #Entity */
class PrSpecificFieldValue
{
/**
*
* #ManyToOne(targetEntity="PrPurchaseRequest", inversedBy="prSpecificFieldValues")
* #JoinColumn(name="pr_purchase_request_id", referencedColumnName="id")
*/
private $prPurchaseRequest;
}
Now you have defined relationship between your entities you can join them based on their mapping (prSpecificFieldValues defined on PrPurchaseRequest class ) like
Its DQL (DQL != SQL)
SELECT p,v
FROM PrPurchaseRequest p
JOIN p.prSpecificFieldValues v
No need to specify ON clause doctrine will handle this for you.
One-To-Many, Bidirectional

Doctrine Many to Many relations

i edit the thread for put more info.
I have the "User" entity and the "Rol" entity, and i am fighting to do work the collection of Roles of a User.
In the User entity i defined:
/**
* #ManyToMany(targetEntity="AppsManantiales\CommonBundle\Entity\Perfil")
* #JoinTable(name="usuarios_perfiles",
* joinColumns={#JoinColumn(name="idUsuario", referencedColumnName="idusuario")},
* inverseJoinColumns={#JoinColumn(name="idPerfil", referencedColumnName="idperfil")}
* )
*/
protected $perfiles;
And in the constructor:
public function __construct(){
$this->perfiles = new \Doctrine\Common\Collections\ArrayCollection();
$this->contacto = new \Doctrine\Common\Collections\ArrayCollection();
}
Before the class namespace put:
use AppsManantiales\CommonBundle\Entity\Perfil;
When execute:
php app/console generate:doctrine:entities CommonBundle
An error appear:
[Doctrine\Common\Annotations\AnnotationException]
[Semantical Error] The annotation "#ManyToMany" in property AppsManantiales\CommonBundle\Entity\Usuario::$perfiles was never impo
rted. Did you maybe forget to add a "use" statement for this annotation?
Any ideas ?.
First part: So in this case, u got a relation many-to-many between Role entity and User entity. First of all, check, r entities correct after generting. Here u can find examples of establishing different realtions: http://docs.doctrine-project.org/en/latest/reference/association-mapping.html && http://docs.doctrine-project.org/en/2.0.x/reference/association-mapping.html (the second has more information with examples of Doctrine queries)
Second part of your question: after establishing right relations, select query of your User gonna be smth like:
$user = $em->createQueryBuilder()
->select('u, r')
->from('YourBundle:User', 'u')
->innerJoin('u.roles', 'r')
->where('u.id IN (:ids)')
->setParameter('ids', $ids)
->getQuery()
->getResult();
And as u guess, u can get roles with the help of your accessor: $user->getRoles()
p.s. yes, ofcource if all entities r correct, u can add methods manually.
EDITED
Oh sry, I forgot, u use Symfony2. So by default in your entities, u got such line:
use Doctrine\ORM\Mapping as ORM;
As u can notice, all annotations u used were with prefix #ORM\. exmpls:
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
So just add prefix #ORM and the result:
/**
* #ORM\ManyToMany(targetEntity="AppsManantiales\CommonBundle\Entity\Perfil")
* #ORM\JoinTable(name="usuarios_perfiles",
* joinColumns={#ORM\JoinColumn(name="idUsuario", referencedColumnName="idusuario")},
* inverseJoinColumns={#ORM\JoinColumn(name="idPerfil", referencedColumnName="idperfil")}
* )
*/

Doctrine 2.1 DQL - Many-to-many query multiple values - Item in multiple categories?

This may seem like a rudimentary request, but I can't seem to get it to work, so I'm either missing something stupid, or am not understanding how it should be done. Thanks in advance.
I have two doctrine entities with a many-to-many relationship: Items and Categories. Which are joined by items_has_categories.
/**
* Item
*
* #Table(name="items")
* #Entity(repositoryClass="Entity\Repository\Item")
*/
class Item
{
....
/**
* #var Categories
*
* #ManyToMany(targetEntity="Categorie", inversedBy="items", cascade={"persist"})
* #JoinTable(name="items_has_categories",
* joinColumns={
* #JoinColumn(name="items_id", referencedColumnName="id")
* },
* inverseJoinColumns={
* #JoinColumn(name="categories_id", referencedColumnName="id")
* }
* )
*/
private $categories;
....
}
/**
* Categorie
*
* #Table(name="categories")
* #Entity(repositoryClass="Entity\Repository\Categorie")
*/
class Categorie
{
.....
/**
* #var Items
*
* #ManyToMany(targetEntity="Item", mappedBy="categories")
*/
private $items;
....
}
And what I'm trying to do is run a query to return all items that are in all of "x" number of categories - which I think/thought should be a SELECT with and AND clause:
class Item extends EntityRepository
{
public function findItemsByCategories($categories)
{
$qString = 'SELECT j, t, c FROM Technique\Entity\Item j LEFT JOIN j.itemImages t JOIN j.categories c WHERE';
$i = 0;
foreach ($categories as $c)
{
$qString .= ' c.name = ?' . $i;
if ($i < (count($categories)-1))
{
$qString .= ' AND';
}
$i++;
}
$query = $this->_em->createQuery($qString);
$query->setParameters($categories);
return $query->getResult();
}
That little bit of code has no errors and spits out the following DQL SELECT query (when 2 categories are sent in the array: $categories):
SELECT j, t, c FROM Technique\Entity\Item j LEFT JOIN j.itemImages t JOIN j.categories c WHERE c.name = ?0 AND c.name = ?1
This is always returning an empty array, i.e., no results. Even though in my DB, there are more than 20 items fitting the criteria: are in both categories.
Anyone see what I'm doing wrong here? Is this supposed to be an AND selection...? Basically I'd just like to know how to query a many-to-many relationship in Doctrine 2+, where there's more than one value that must be met...
For anyone interested, I figured it out (painfully). Doctrine should really explain this better instead of the the one line they have on the DQL page....
Basically it's not an AND query, it's a MEMBER OF AND query. For each category a MEMBER OF must be created and then added to the whole query with an AND:
SELECT j, t FROM Entity\Item j
LEFT JOIN j.itemImages t
WHERE ?0 MEMBER OF j.categories AND ?1 MEMBER OF j.categories
AND ?2 MEMBER OF j.categories, etc.
That will return all items that are in all categories requested.