What Doctrine approach to use in web application? - symfony

I'm building web application with symfony2. I'm using Doctrine as db engine. I will need to use some complex database queries to recive data from mysql base. What I discovered ( please correct me if I'm wrong ) is:
DQL - It's useful in case of operationg on object's ( getters, setters, etc). Unfortunately if my application use complex sql queries ( many left joins, inner joins, etc) it is not very helpful.
Native SQL - i'm not sure of this approach. Is't possible to operate on the objects? I'm having hard time with creating join query.
RAW SQL - any sql query is possible but i can't use objects.

Even with an ORM layer in between your application and a database there may be times where you need to write raw SQL queries; for example, if you are producing a report about application usage, or you are calculating intermediate data, etc.
However, in each of those cases you should ask yourself whether or not it would be more appropriate to work with objects rather than raw data. In many cases people assume that they can't achieve their result without going directly to the database level, but they may be surprised to learn that there is really quite a lot you can do with a modern ORM like Doctrine.
In Doctrine, joins are represented by associations, and one of the key benefits of the system is the way in which you can interact with associations through objects rather than raw SQL.
Consider the following "complex" query (okay, it's really not that complex):
SELECT foo.*, bar.*
FROM foo
LEFT JOIN bar ON foo.foo_id = bar.foo_id
WHERE foo.name = "x"
In Doctrine, you can model this with entities. For example, a snippet:
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="foo")
*/
class Foo
{
/**
* #ORM\Id
* #ORM\Column(name="foo_id", type="integer")
* #ORM\GeneratedValue
*/
private $id;
/**
* #ORM\Column(name="foo_name", type="string", length=32)
*/
private $name;
/**
* #ORM\OneToMany(targetEntity="Bar", mappedBy="foos")
*/
private $bars;
// more code ...
}
And another:
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity
* #ORM\Table(name="bar")
*/
class Bar
{
/**
* #ORM\Id
* #ORM\Column(name="bar_id", type="integer")
* #ORM\GeneratedValue
*/
private $id;
/**
* #ORM\Column(name="bar_name", type="string", length=32)
*/
private $name;
/**
* #ORM\ManyToOne(targetEntity="Foo", inversedBy="bars")
* #ORM\JoinColumn(name="foo_id", referencedColumnName="foo_id")
*/
private $foo;
// more code ...
}
Now, in DQL, we can rewrite the original query:
SELECT f, b
FROM \Your\Namespace\Foo f
LEFT JOIN f.bars b
WHERE f.name = "x"
This query will produce a Foo object or objects, and any Bar objects associated with each, all with only that single query to the database. Using this pattern, you should be able to model the vast majority of useful SQL queries with objects in such a way that their relationships are natural and easy to understand.
One other thing worth noting is that the above DQL query is actually called a "fetch join" because it will hydrate both the Foo object(s) being requested and any Bar objects associated with them. A simpler version of the query would be:
SELECT f
FROM \Your\Namespace\Foo f
WHERE f.name = "x"
This will hydrate only Foo objects in the first query, performing no joins. However, you can still access associated Bar objects (e.g. $foo->getBars()), and Doctrine will automatically fetch the associated data on as as-needed basis (this is known as "lazy loading"). In all cases you are free to decide whether you want to hydrate some or all of the object graph for an entity, or just retrieve top-level data and allow Doctrine to load the data as needed.
There is a lot of information about this in the Doctrine Association Mapping documentation.

Related

Doctrine 2.5. OneToOne relationship sharing primary key

In my app, when users sign up I have to send them an email with a validation key, as usually happens on most websites, I'm trying to do this with Doctrine but I can’t get it to work when I try to persist() the user.
First of all, I think the correct way in this case is to use a OneToOne unidirectional relationship, but I don’t know if it would be better to use a bidirectional one. I've tried both and I always get an error.
I have read these two questions carefully:
One to one relationship on two tables sharing primary key
Doctrine one-to-one unidirectional
As well as this part of the documentation. When I validate the schema (php bin/console doctrine:schema:validate) everything is fine.
class Usuario {
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
// ...
}
class ClaveVal {
/**
* #ORM\Id
* #ORM\OneToOne(targetEntity="Usuario")
*/
private $usuario;
/**
* #ORM\Column(name="clave", type="string", length=20, nullable=false)
* #Assert\NotBlank()
*/
private $clave;
// ...
}
Which is quite similar to this.
Now, I'm trying to persist() a new Usuario and a new ClaveVal for this usuario like this:
$usuario = new Usuario();
// Add usuario attributes
$claveVal = new ClaveVal();
$claveVal->setUsuario($usuario);
$claveVal->setClave(‘123456’);
$em->persist($usuario);
$em->persist($claveVal);
But I get this error:
The given entity of type 'AppBundle\Entity\ClaveVal'
(AppBundle\Entity\ClaveVal#000000007020f28f0000000031d5c8c6) has no
identity/no id values set. It cannot be added to the identity map
I know why this happens. This works perfectly:
$em->persist($usuario);
$em->flush();
$em->persist($claveVal);
$em->flush();
But I don't want to do that because I want it to be a unit of work using flush() only once.
Besides, as the author of the post I linked above says, it should be Doctrine's job to flush() at the right moment to get the id.
So, how can I achieve this using flush() only once (and without using transactions or listeners, I'm sure there is an easier way to do this)? Would it be better to use a bidirectional OneToOne relationship? As I said, I tried it too but I got the same error.
Thanks in advance.

Entity associated with non-entity

I have an interface SupplierInterface with 2 implementations: B2BSupplier (a Doctrine entity), RetailSupplier (a static object).
<?php
namespace MyBundle\Model;
interface SupplierInterface {
const B2B = 'B2B';
const RETAIL = 'Retail';
/**
* #return string
*/
public function getSupplierType();
/**
* #return string
*/
public function __toString();
}
Another entity, Supply has a many-to-one relationship with a Supplier. Normally this isn't problematic. But because RetailSupplier is not a Doctrine entity, I'm a bit flummoxed about how to proceed.
Supply looks like this:
<?php
namespace MyBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Validator\Constraints as Assert;
use Gedmo\Blameable\Traits\BlameableEntity;
use Gedmo\Timestampable\Traits\TimestampableEntity;
/**
* Supply
*
* #ORM\Table(name="cir_supply")
* #ORM\Entity()
*/
class Supply
{
use BlameableEntity;
use TimestampableEntity;
/**
* #var int
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity="B2BSupplier")
* #ORM\JoinColumn(name="supplier_id", referencedColumnName="id", nullable=true)
*/
protected $supplier; // <-- PROBLEM, since supplier could be B2BSupplier entity, or it could be vanilla object RetailSupplier
/**
* #ORM\ManyToOne(targetEntity="Chemical", inversedBy="supplies")
* #ORM\JoinColumn(name="chemical_id", referencedColumnName="id", nullable=false)
*/
protected $chemical;
/**
* #ORM\Column(name="external_id", type="string")
*/
protected $externalId;
//getters and setters ...
}
How do I specify a Doctrine relationship when that relationship might not always be valid?
From my experience I'm 99% sure you can't do what you want in your current setup. That being said, there are a few workarounds I can think of. Also before I go into the workarounds. You should think if you really want OneToOne relation on 'supplier' or will ManyToOne work better. OneToOne has some Lazy loading issues and also Workaround 3 work better with ManyToOne.
Workaround 1:
Remove the relation and make the supplier filed contain the id, without having a relation defined.
Extend SupplierRepository 'find' method to handle the cases where id is
2.1 'null' there is no relation in witch case it returns RetailSupplier
2.2 call parent::find for all other cases
2.3 Optional: if null relations are required change 2.1 to use '0' instead of null (adds con 3)
Pros:
fast to achieve from your current setup
keep database foreign key (if step 2.3 is ignored)
Cons:
hidden behavior of the 'find' method
you loose the your doctrine relation
not scalable for other types of Suppliers
source of the information is split between the app and the database
if step 2.3 is required, you loose database foraign key ('0' will not be a foraign key)
Workaround 2:
Modify getSupplier to return RetailSupplier if $this->supplier is null
Modify setSupplier to set null if $supplier is instance of RetailSupplyer
Optinal: Change the first 2 steps to handle '0' as RetailSupplyer and 'null' as no relation
Pros:
fast to achieve from your current setup
keep database foreign key (if step 3 is ignored)
keep doctrine relation
Cons:
hidden behavior of the setter and getter
not scalable for other types of Suppliers
if step 3 is required, you loose database foraign key ('0' will not be a foraign key)
source of the information is split between the app and the database
Workaround 3 (doctrine inheritance mapping):
Create an abstract (called Supplier) this will be inherited by RetailSupplyer and B2BSupplier
Add inheritance metadata to Supplier abstract something like this
Create an entity for RetailSupplyer and a database table with one single line to start (the first RetailSupplier)
Change your database to match your inheritance mapping settings (for more info http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/inheritance-mapping.html)
Change your relation to ManyToOne on $supplier and make it to point to Supplier
Pros:
source of the information is only the database
no hidden behavior in your code
scalable for other types of suppliers and other more retail suppliers
Cons:
harder to achieve from your current setup (database changes, new doctrine setup, possibly some refactor)
pros/cons: Depending on the selected inheritance type you can have full relation path in your database (with foraign key), or you can have no relations. This is up to you ;) after you read the documentation for inheritance mapping.
PS: If I had to choose i will go with Workaround 3. It is hardest to achieve, but solid do it.
Hope this helps and happy coding
Alexandru Cosoi

Symfony - access object of non related tables

How can i access the object of second table when joined (non-related tables)?
I have two table which are not related and I want to get the object of the second class (from below dump output)
My repository with dump
For example:
my controller:
$ProductSet_Repo = $em->getRepository('MyTestBundle:Product\ProductSet')->FindProductSet($productid);
Normally when the tables are related I can simple do
$productSet = $ProductSet_Repo->getproductid()->getProduct(); to get the object of Product class From ProductSet Class.
See My Dump
However since the tables are not in relationship and when i dump the data i get the objects of two classes is there a way I can access the Object My\TestBundle:Products\Entity\Product\ProductSet and \My\TestBundle\Entity\Product\Product?
Note: i don't want to do establish relationship between the two tables as I am working on already existing table for which i don't want to make any changes
Also I know I can select the fields which i want to retrieve. (I dont want to do that)
You write:
i don't want to do establish relationship between the two tables as I am working on already existing table for which i don't want to make any changes.
But with doctrine you are very well able to make a association between two entities without changing the tables. As far as I can see from your query you have a product_id column in your product_set table. That is all you need to make an association between Product and ProductSet.
In your ProductSet class you can do:
<?php
namespace My\TestBundle\Entity\Product;
class ProductSet
{
//... other properties
/**
* #var Product
* #ORM\Id
* #ORM\ManyToOne(targetEntity="My\TestBundle\Entity\Product\Product")
* #ORM\JoinColumn(name="product_id", referencedColumnName="id")
*/
protected $product;
/**
* Set the product.
*
* #param Product $product
* #return ProductSet
*/
public function setProduct(Product $product)
{
$this->product = $product;
return $this;
}
/**
* Get the product.
*
* #return Product
*/
public function getProduct()
{
return $this->product;
}
//... other setters and getters
}
Now you can do:
$repository = $em->getRepository('MyTestBundle:Product\ProductSet')
$productSets = $repository->findBy(array('product' => $productid));
foreach($productSets as $productSet){
$productSet->getProduct()->getId() === $productId; // true
}
You can still join them (despite of strange naming convention you have id of corresponding object in the other entity) using query builder or native sql, but it's a really bad way.
it was developed by previous webdeveloper and i dont want to spend more time as i work as free lancer
That's not an excuse. You should create a relation and migration for these data. Getting money for a poorly designed and developed app is not cool.
Probably additional work when working with that poor design will take your more time than doing it in a proper way.

Select with where clause that uses a field that is not in index returns: Binding an entity with a composite primary key to a query is not supported

I have a table with a composite primary key:
class tablea {
/**
* #ORM\Column(type="datetime")
* #ORM\id
*/
protected $a;
/**
* #ORM\Column(type="integer")
* #ORM\id
*/
protected $b;
/**
* #ORM\Column(type="integer")
* #ORM\Id
*/
protected $c;
/**
* #ORM\Column(type="integer")
*/
protected $d;
/**
* #ORM\Column(type="integer")
*/
protected $e;
}
When I try to execute a query to return all record with d=100 (for instance) I allways get a error message:
Binding an entity with a composite primary key to a query is not
supported. You should split the parameter into the explicit fields and
bind them separately.
here is my code:
$_qry = $_rep->createQueryBuilder('m')
->Where("m.d = :ini")
->setParameter('ini', 100)
->getQuery();
But, if I change to a field that is an index or if I remove the where clause, the query works fine.
I'm not sure how to fix your exact error, but you may want to look into the limitations Doctrine has with composite keys, specifically the restriction on only having primitive types - in your example you're using a datetime as an ID. It could be that this is causing the problem.
While not a solution, this is one of those instances where trying to make Doctrine play nice is going to be more hassle than either:
just adding a synthetic generated key to the entity
using native SQL / a different ORM solution (e.g. Propel)
The impact of either of these choices is obviously dependent on your project - I've found the former the easiest way to go about things, however if you're porting an existing project or you expect the table to be massive (or you're just really picky over normalisation) then this may not be the way to go.
In short: composite keys in Doctrine are a short path to pain.

Stop Doctrine / Symfony from loading associated entities during foreach? (lazy loading not happening)

I'm seeing doctrine generating additional queries to load entities that I'm not directly accessing. I thought that lazy loading meant these associate entities wouldn't get loaded. Can you help me figure out why the queries are happening and how to stop them?
Here's the entities in question:
class Invoice
{
/**
* #ORM\OneToMany(targetEntity="InvoiceCard", mappedBy="invoice")
*/
protected $cards;
...
}
class BaseInvoiceCard
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var integer
*
* #ORM\ManyToOne(targetEntity="Invoice", inversedBy="cards")
* #ORM\JoinColumn(name="invoice_id", referencedColumnName="id")
*/
protected $invoice;
/**
* #var integer
*
* #ORM\ManyToOne(targetEntity="Printing")
* #ORM\JoinColumn(name="printing_id", referencedColumnName="id")
*/
protected $printing;
...
}
class InvoiceCard extends BaseInvoiceCard{ ... }
class Printing{ ... }
This line of code doesn't cause any queries to the InvoiceCards table:
$cards = $invoice->getCards();
Once I do this:
foreach($cards as $card){
//do nothing in this loop
}
I get a "SELECT ... FROM invoicecard", which is expected.
However, I'm also getting a "SELECT ... FROM printing" for every $card in $cards. I never call $card->getPrinting(). This happens even if I do nothing at all inside the loop; just running it causes doctrine to run these queries.
Why this happening and how can I prevent it?
EDIT: This is the code for getCards().
/**
* Get cards
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getCards()
{
return $this->cards;
}
EDIT 2:
I've found a workaround to this problem, though it's not going to solve things in the long run. I fetch the InvoiceCards as an array, rather than having doctrine hydrate them as entities.
$query->getArrayResult();
In my current situation, this technique is better anyways, since I don't require the overhead of full hydration.
However, the application will be working with InvoiceCards in many places, and the original issue will be still be a problem then. I feel like either I've misunderstood Doctrine's lazy loading, or it isn't working as expected.
The entities were getting eagerly loaded because Printing is a parent class whose children use single table inheritance.
If I change $printing to point to leaf entity, lazy loading works as desired.
From the Doctrine docs, 7.2.2. Performance impact
There is a general performance consideration with Single Table Inheritance: If the target-entity of a many-to-one or one-to-one association is an STI entity, it is preferable for performance reasons that it be a leaf entity in the inheritance hierarchy, (ie. have no subclasses). Otherwise Doctrine CANNOT create proxy instances of this entity and will ALWAYS load the entity eagerly.

Resources