Doctrine ORM Association creates PHP proxy class - php

Why are Proxy classes created instead of entity objects?
DoctrineORMModule\Proxy\__CG__\App\Entity\FormType vs \App\Entity\FormType
Setup: Laminas, Doctrine ORM
Class Project{}
Class ProjectForm
{
/**
* #ORM\Id
* #ORM\Column(name="id")
* #ORM\GeneratedValue
*/
protected $id;
/**
* #ORM\OneToOne(targetEntity="FormType")
* #ORM\JoinColumn(name="form_type_id", referencedColumnName="id")
*/
protected $formType;
/**
* #ORM\OneToOne(targetEntity="Project")
* #ORM\JoinColumn(name="project_id", referencedColumnName="id")
*/
protected $project;
/**
* #ORM\Column(name="label")
*/
protected $label;
/**
* #ORM\Column(name="status")
*/
protected $status;
/**
* #ORM\Column(name="order_nr")
*/
protected $order;
}
Class FormType
{/**
* #ORM\Id
* #ORM\Column(name="id")
* #ORM\GeneratedValue
*/
protected $id;
/**
* #ORM\Column(name="code")
*/
protected $code;
/**
* #ORM\Column(name="label_txt")
*/
protected $label;
:
:
}
Then, using a custom extended Repository use the query builder to get data from storage
$qb->select('pc','ft')
->from(\App\Entity\ProjectForm::class,'pc')
->join(\App\Entity\FormType::class,'ft')
->where($qb->expr()->eq('pc.project',$projectId))
->andWhere('pc.formType=ft.id');
SQL Generated is/seems correct (when passing project ID 1) and returns the correct number of rows
SELECT p0_.id AS id_0, p0_.label AS label_1, p0_.status AS status_2, p0_.order_nr AS order_nr_3, f1_.id AS id_4, f1_.code AS code_5, f1_.label_txt AS label_txt_6, p0_.form_type_id AS form_type_id_7, p0_.project_id AS project_id_8 FROM project_forms p0_ INNER JOIN form_types f1_ WHERE p0_.project_id = 1 AND p0_.form_type_id = f1_.id
The same resultset is visible via
$this->getEntityManager()->getRepository(\Eho\Core\Entity\ProjectForm::class)->findBy(['project'=>$projectId],[]);
With $result[0]->getProject()->getTitle() returning the correct string but $result[0]->getFormType()->getName() throwing and error due to class \DoctrineORMModule\Proxy\__CG__\Entity\FormType being returned from getFormType()...???
Project::getTite() -> string
ProjectForm->getName() -> string
EDIT: Also tried different join for giggles
$joinOn = \Doctrine\ORM\Query\Expr\Join::ON;
$joinWith = \Doctrine\ORM\Query\Expr\Join::WITH;
$qb->select('pf','ft')
->from(\App\Entity\ProjectForm::class,'pf')
->innerJoin(\App\Entity\FormType::class,'ft',$joinWith, 'pf.formType=ft.id')
->where($qb->expr()->eq('pf.project',$projectId));
With SQL generated:
SELECT ...fields... FROM project_forms p0_ INNER JOIN form_types f1_ ON (p0_.form_type_id = f1_.id) WHERE p0_.project_id = 1
But the same resultsets (with Proxy classes are returned)...
Why is ONE entity (project) populated correctly and another (formType) as a proxy...?

Related

QueryBuilder Invalid PathExpression. Must be a StateFieldPathExpression

Trying to fetch all supplierUsers where the id is DISTINCT and hydrate the results.
The SQL query to get all the results would be the following
SELECT DISTINCT supplier_user_id FROM job_item_quote
The Query builder to fetch the above.
$qb = $this->createQueryBuilder('a')
->select('a.supplierUser')
->distinct(true);
$result = $qb->getQuery()->getResult();
Outputted getQuery(). Which is exactly what I'm looking for.
SELECT DISTINCT a.supplierUser FROM Project\Entities\JobItemQuote a
The error thrown when trying to fetch distinct users
[Semantical Error] line 0, col 18 near 'supplierUser,': Error: Invalid PathExpression. Must be a StateFieldPathExpression.
I've tried adding joins in for supplierUser in hopes it would fix. Same error thrown.
JobItemQuote Entity
/**
* #ORM\Entity(repositoryClass="Project\Repositories\JobItemQuote\JobItemQuoteRepository")
* #ORM\Table(name="job_item_quote")
*/
class JobItemQuote extends BaseEntity
{
public static $joins = [
'supplierUser' => SupplierUser::class,
'jobItem' => JobItem::class
];
/**
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
* #var int
*/
protected $id; // thekey
/**
* #ORM\ManyToOne(targetEntity="JobItem", inversedBy="quotes")
* #var JobItem
*/
protected $jobItem;
/**
* #ORM\ManyToOne(targetEntity="SupplierUser")
* #var SupplierUser
*/
protected $supplierUser;
....
}
SupplierUser Entity
/**
* #ORM\Entity(repositoryClass="Project\Repositories\SupplierUser\SupplierUserRepository")
* #ORM\Table(name="supplier_user")
*/
class SupplierUser extends User {
public static $joins = [
'supplier' => Supplier::class,
'supplierGroup' => SupplierGroup::class
];
/**
* #ORM\OneToOne(targetEntity="Supplier", inversedBy="supplierUser", cascade={"persist"})
* #var Supplier
*/
protected $supplier;
/**
* #ORM\ManyToOne(targetEntity="SupplierGroup")
* #var Group
*/
protected $supplierGroup;
....
}
Your need is to retrieve the list of supplierUsers associated with JobItemQuote, so you should make the query in JobItemQuoteRepository making a join with supplierUsers, you find bellow the example :
$qb = $this->createQueryBuilder('jiq')
->select('su')
->join(SupplierUser::class, 'su', Join::With, 'su.id = jiq.supplierUser')
->distinct(true)
;
$result = $qb->getQuery()->getResult();
By this query you will have the list of SupplierUser (distinctly) assoiated to JobsItemQuote.

create method to generate default value with prefix and auto-increlmentation directly in entity

In my Symfony project I have this entity named Order.php:
<?php
namespace myApp\EntityBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Datetime;
/**
*
* #ORM\Table(name="`order`")
* #ORM\Entity(repositoryClass="myApp\EntityBundle\Repository\OrderRepository")
*
*/
class Order
{
/**
* #var integer
*
* #ORM\Column(name="order_id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/**
* Command name ref for right accounting
*
* #var string
*
* #Assert\Type(
* type="string",
* message="The value {{ value }} is not a valid {{ type }}."
* )
* #Assert\NotNull()
* #Assert\NotBlank()
* #ORM\Column(name="order_name", type="string", length=255, nullable=true, unique=true)
*/
protected $orderName;
// etc other associations, object and getters setters...
/**
* Constructor
*/
public function __construct()
{
$this->generateOrderName();
}
/**
* generate order_name with base ref
*
* #return Order
*/
public function generateOrderName()
{
// trying to make the good practice for generated orderName value
//$now = new Datetime();
//$year = $now->format("Y");
//$month = $now->format("m");
//$orderNameCommon = $year."_".$month."_";
$this->setOrderName();
return $this;
}
/**
* Set orderName
*
* #param string $orderName
*
* #return Order
*/
public function setOrderName($orderName)
{
$this->orderName = $orderName;
return $this;
}
/**
* Get orderName
*
* #return string
*/
public function getOrderName()
{
return $this->orderName;
}
}
The purpose is to generate an orderName like this: '2016_09_00001' which means that I registered order by date Year_Month_orderNumber.
I have to take care that the orderName have to be unique, and for example if I have already an order named '2016_09_00001', I have to register the orderName like this '2016_09_00002'. Like an auto-incrementation for example.
As you can see, I have made this code directly in entity class Order:
/**
* Constructor
*/
public function __construct()
{
// call the order name generation function
$this->generateOrderName();
}
/**
* generate order_name with base ref
*
* #return Order
*/
public function generateOrderName()
{
$now = new Datetime();
$year = $now->format("Y");
$month = $now->format("m");
$prefix = $year."_".$month."_";
// That I had planned:
// to create a prefix with Datetime argument, year and month
// Then check all order in database with this prefix
// Finally count the results and add +1 on the final value
$this->setOrderName();
return $this;
}
As you can see, I have called the generateOrderName() function in the constructor, and then I would like to make this process:
That I had planned:
to create a prefix with Datetime argument, year and month
Then check all order in database with this prefix
Finally count the results and add +1 on the final value
What is the best way to make this properly in the entity directly? I think it's not a good thing to have a repository dql query call in the entity class itself.
I have create this one for example:
public function searchOrderName($prefix)
{
return
$this->getEntityManager()
->createQuery(
"SELECT o.orderName FROM EntityBundle:Order o
WHERE o.orderName
LIKE :prefix
ORDER BY o.id ASC"
)
->setParameter("prefix", $prefix.'%')
->getResult();
}
How can I proceed to create the generateOrderName() function in the entity to make the generated process I would like directly in entity class ?
You will need a service for it. You cannot access DB from your Entity. So make OrderFactory, inject there OrderRepository and you will easily reach your goal

Filtering on many-to-many association with Doctrine2

I have an Account entity which has a collection of Section entities. Each Section entity has a collection of Element entities (OneToMany association). My problem is that instead of fetching all elements belonging to a section, I want to fetch all elements that belong to a section and are associated with a specific account. Below is my database model.
Thus, when I fetch an account, I want to be able to loop through its associated sections (this part is no problem), and for each section, I want to loop through its elements that are associated with the fetched account. Right now I have the following code.
$repository = $this->objectManager->getRepository('MyModule\Entity\Account');
$account = $repository->find(1);
foreach ($account->getSections() as $section) {
foreach ($section->getElements() as $element) {
echo $element->getName() . PHP_EOL;
}
}
The problem is that it fetches all elements belonging to a given section, regardless of which account they are associated with. The generated SQL for fetching a section's elements is as follows.
SELECT t0.id AS id1, t0.name AS name2, t0.section_id AS section_id3
FROM mydb.element t0
WHERE t0.section_id = ?
What I need it to do is something like the below (could be any other approach). It is important that the filtering is done with SQL.
SELECT e.id, e.name, e.section_id
FROM element AS e
INNER JOIN account_element AS ae ON (ae.element_id = e.id)
WHERE ae.account_id = ?
AND e.section_id = ?
I do know that I can write a method getElementsBySection($accountId) or similar in a custom repository and use DQL. If I can do that and somehow override the getElements() method on the Section entity, then that would be perfect. I would just very much prefer if there would be a way to do this through association mappings or at least by using existing getter methods. Ideally, when using an account object, I would like to be able to loop like in the code snippet above so that the "account constraint" is abstracted when using the object. That is, the user of the object does not need to call getElementsByAccount() or similar on a Section object, because it seems less intuitive.
I looked into the Criteria object, but as far as I remember, it cannot be used for filtering on associations.
So, what is the best way to accomplish this? Is it possible without "manually" assembling the Section entity with elements through the use of DQL queries? My current (and shortened) source code can be seen below. Thanks a lot in advance!
/**
* #ORM\Entity
*/
class Account
{
/**
* #var int
* #ORM\Column(type="integer")
* #ORM\Id
* #ORM\GeneratedValue
*/
protected $id;
/**
* #var string
* #ORM\Column(type="string", length=50, nullable=false)
*/
protected $name;
/**
* #var ArrayCollection
* #ORM\ManyToMany(targetEntity="MyModule\Entity\Section")
* #ORM\JoinTable(name="account_section",
* joinColumns={#ORM\JoinColumn(name="account_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="section_id", referencedColumnName="id")}
* )
*/
protected $sections;
public function __construct()
{
$this->sections = new ArrayCollection();
}
// Getters and setters
}
/**
* #ORM\Entity
*/
class Section
{
/**
* #var int
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
protected $id;
/**
* #var string
* #ORM\Column(type="string", length=50, nullable=false)
*/
protected $name;
/**
* #var ArrayCollection
* #ORM\OneToMany(targetEntity="MyModule\Entity\Element", mappedBy="section")
*/
protected $elements;
public function __construct()
{
$this->elements = new ArrayCollection();
}
// Getters and setters
}
/**
* #ORM\Entity
*/
class Element
{
/**
* #var int
* #ORM\Id
* #ORM\GeneratedValue
* #ORM\Column(type="integer")
*/
protected $id;
/**
* #var string
* #ORM\Column(type="string", length=50, nullable=false)
*/
protected $name;
/**
* #var Section
* #ORM\ManyToOne(targetEntity="MyModule\Entity\Section", inversedBy="elements")
* #ORM\JoinColumn(name="section_id", referencedColumnName="id")
*/
protected $section;
/**
* #var \MyModule\Entity\Account
* #ORM\ManyToMany(targetEntity="MyModule\Entity\Account")
* #ORM\JoinTable(name="account_element",
* joinColumns={#ORM\JoinColumn(name="element_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="account_id", referencedColumnName="id")}
* )
*/
protected $account;
// Getters and setters
}
If I understand correctly, you want to be able to retrieve all Elements of all Sections of an Account, but only if those Elements are associated with that Account, and this from a getter in Account.
First off: An entity should never know of repositories. This breaks a design principle that helps you swap out the persistence layer. That's why you cannot simple access a repository from within an entity.
Getters only
If you only want to use getters in the entities, you can solve this by adding to following 2 methods:
class Section
{
/**
* #param Account $accout
* #return Element[]
*/
public function getElementsByAccount(Account $accout)
{
$elements = array();
foreach ($this->getElements() as $element) {
if ($element->getAccount() === $account) {
$elements[] = $element->getAccount();
}
}
return $elements;
}
}
class Account
{
/**
* #return Element[]
*/
public function getMyElements()
{
$elements = array()
foreach ($this->getSections() as $section) {
foreach ($section->getElementsByAccount($this) as $element) {
$elements[] = $element;
}
}
return $elements;
}
}
Repository
The solution above is likely to perform several queries, the exact amount depending on how many Sections and Elements are associated to the Account.
You're likely to get a performance boost when you do use a Repository method, so you can optimize the query/queries used to retrieve what you want.
An example:
class ElementRepository extends EntityRepository
{
/**
* #param Account $account [description]
* #return Element[]
*/
public function findElementsByAccount(Account $account)
{
$dql = <<< 'EOQ'
SELECT e FROM Element e
JOIN e.section s
JOIN s.accounts a
WHERE e.account = ?1 AND a.id = ?2
EOQ;
$q = $this->getEntityManager()->createQuery($dql);
$q->setParameters(array(
1 => $account->getId(),
2 => $account->getId()
));
return $q->getResult();
}
}
PS: For this query to work, you'll need to define the ManyToMany association between Section and Account as a bidirectional one.
Proxy method
A hybrid solution would be to add a proxy method to Account, that forwards the call to the repository you pass to it.
class Account
{
/**
* #param ElementRepository $repository
* #return Element[]
*/
public function getMyElements(ElementRepository $repository)
{
return $repository->findElementsByAccount($this);
}
}
This way the entity still doesn't know of repositories, but you allow one to be passed to it.
When implementing this, don't have ElementRepository extend EntityRepository, but inject the EntityRepository upon creation. This way you can still swap out the persistence layer without altering your entities.

zend framework 2 - ServiceManager error while saving data in database

I am trying to create a saveAction in zend2 framework using doctrine.
in my PromotionsController i have this action:
public function saveLinkAction() {
$view = new ViewModel();
$salonId = (int) $this->params()->fromPost('salon_id', null);
$addLink = $this->getServiceLocator()->get('Promotions\Model\Link');
$linkData['salon_id'] = $salonId;
$linkData['link'] = '/link/example';
$addLink->setData($linkData);
return $view;
}
This is just for learning how to write data in database.
$addLink = $this->getServiceLocator()->get('Promotions\Model\Link');
This line of code is showing an error and i don't know what is the cause?
Zend\ServiceManager\ServiceManager::get was unable to fetch or create an instance for Promotions\Model\Link
I have created a Link.php in Model directory.
<?php
namespace Link\Model;
use Application\Model\Entity;
use Zend\Form\Annotation;
/**
* #Entity
* #Table(name="promo_link")
*/
class Link extends Entity {
/********** PROPERTIES **********/
/**
* #Id #GeneratedValue(strategy="AUTO") #Column(name="id", type="integer")
* #var int
*
* #Annotation\Exclude()
*/
protected $id;
/**
* #Column(name="salon", type="integer")
* #var int
*
* #Annotation\Options({"label":"Salon"})
* #Annotation\Validator({"name": "Digits"})
*/
protected $salon;
/**
* #Column(name="link", type="string")
* #var string
*/
protected $link;
/**
* #Column(name="start_date", type="string")
* #var string
*/
protected $start_date;
/**
* #Column(name="end_date", type="string")
* #var string
*/
protected $end_date;
}
?>
The error tells you where the problem is:
Zend\ServiceManager\ServiceManager::get was unable to fetch or create an instance for Promotions\Model\Link
Meaning: The ServiceManager doesn't know what Promotions\Model\Link is supposed to be. This key either doesn't exist in your SMConfig or while creating the instance to be returned some error occurs.
TL/DR - Check your ServiceManager Configuration regarding the key Promotions\Model\Link
In order to save your data in your database, you will need the entitymanager.
$link = new Link();
$link->setSalonId($salonId);
$link->setLink('/link/example');
$em = $this->getServiceLocator()->get('Doctrine\ORM\EntityManager');
$em->persist($link);
$em->flush();
You can start with the above code.
However, preferably you would create a repository and a servicelayer. The service should have access to the entityManager and hold your logic. Your controller should have access to this service.

Selecting entities associated with two other entities, joining three tables in Doctrine2

I have three types of entities,
Users
Groups
Websites
Users can have Websites, and they also can belong to Groups
Each of these entities has a name as well.
Here are my Doctrine2 definitions:
<?php
/** #Entity */
class User
{
// ...
/**
* #Column(type="string",length=255,nullable=false)
* #var string
*/
protected $name;
/**
* #ManyToMany(targetEntity="Group", inversedBy="users")
* #JoinTable(name="users_groups")
*/
private $groups;
/**
* #ManyToMany(targetEntity="Website", inversedBy="users")
* #JoinTable(name="users_websites")
*/
private $websites;
// ...
}
/** #Entity */
class Group
{
// ...
/**
* #Column(type="string",length=255,nullable=false)
* #var string
*/
protected $name;
/**
* #ManyToMany(targetEntity="User", mappedBy="groups")
*/
private $users;
// ...
}
/** #Entity */
class Website
{
// ...
/**
* #Column(type="string",length=255,nullable=false)
* #var string
*/
protected $name;
/**
* #ManyToMany(targetEntity="User", mappedBy="websites")
*/
private $users;
// ...
}
So now if I want to find all Users in a group called "Admins", I can do this:
$group = $em->getRepository("Group")->findOneByName("Admins");
$users = $group->users;
I can also get all users that are associated with website "Google.com" by doing:
$websites = $em->getRepository("Website")->findOneByName("Google.com");
$users = $websites->users;
Now if I want to get all users who are in "Admins", and are also associated with website "Google", what can I do?
If I was using TSQL, I would join the three tables, how do I do that in Doctrine?
Must I use DQL? How would the DQL look like?
You should use DQL for this.
SELECT u
FROM User u
JOIN u.groups g
JOIN u.websites w
WHERE
g.name = :group_name
AND w.name = :website_name
And the php code to do so:
$dql = '...'; // What i've written above
$query = $em->createQuery($dql);
$query->setParameter('group_name', 'Admins');
$query->setParameter('website_name', 'Google');
$users = $query->getResult();
I encourage you to read the doctrine ORM DQL documentation : http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/dql-doctrine-query-language.html
PS: The 2 lines of code you've written, should return groups, and websites, and not users

Categories