I intend to build multilingual website. So I have DB table with columns:
id
text_en
text_pl
When getting data from DB I'd like to have only id and text fields. Normally, I'd issue:
SELECT id, text_$locale AS text FROM page ...
How to do it in Doctrine so that I have such SELECT done automatically when I use findBy() or findOneBy()? Is Query Builder my only option?
Creating a query is the semantic option; you should put it in as a custom method in a repository.
I'm posting an example here so that I can use it for reference later on.
Quick example:
<?php
namespace Acme\AppBundle\Entity;
use Doctrine\ORM\EntityRepository;
/**
* PageRepository
*
* This class was generated by the Doctrine ORM. Add your own custom
* repository methods below.
*/
class PageRepository extends EntityRepository
{
/**
* Gets and id and page name based by ID
* #return array
*/
public function findNameById($id)
{
$query = $this->getEntityManager()
->createQuery("SELECT p.id, p.name
FROM Acme\AppBundle\Entity\Page p
WHERE p.id = :pageId");
$query->setParameter('pageId', $id);
$result = $query->getOneOrNullResult();
return $result;
}
}
Correct, you'll have to use a querybuilder or native query if you want to select specific columns.
Specifying individual columns using findBy is not possible. You could lazy-load associations to entities in other tables but at a minimum findBy will return all columns from the entity's table.
Related
I have simple Entity:
id
username
guard
"guard" is id of another user from the same Entity. I have to render view with simple table:
username | name of guard
-------------------------
John | Bob
I tried to do that by query:
$ur = $this->getDoctrine()->getRepository(User::class)->createQueryBuilder('u')
->leftJoin(User::class, 'u2', \Doctrine\ORM\Query\Expr\Join::WITH, 'u.guard = u2.id')
->getQuery()
->getResult();
but it gives me just id and username, no joined data.
I know that entire query should be like:
SELECT
*
FROM
user u0_
LEFT JOIN user u1_ ON (u0_.guard = u1_.id)
but I can't find the way to implement that by QueryBuilder and then to access that in twig template.
Regards
OK, I found out the mistakes in my code:
I tried to set that OneToOne realtion and that was small mistake, but I needed here ManyToOne.
/**
* Many Users have One Guard (User)
* #ORM\ManyToOne(targetEntity="User")
*/
private $guard = 0;
When I did that Symfony automatically force me to change my code and in column "guard" I have to insert User object.
After that I don't need join anymore - just select data from table and guard column includes User object which I can use in Twig, etc.
namespace AppBundle\Entity;
use Doctrine\ORM\EntityRepository;
class UserRepository extends EntityRepository
{
public function findAllDB()
{
$qb = $this->createQueryBuilder('u');
$query = $qb->getQuery();
return $query->execute();
}
}
Is it possible to filter an entity and display only few columns in symfony2?
I think I can do a custom query for this, but it seems a bit dirty and I am sure there is a better solution.
For example I have my variable $createdBy below, and it contains few data that shouldnt be displayed in this parent entity such as password etc...
/**
* #var Customer
*
* #ORM\ManyToOne(targetEntity="MyCompany\Bundle\CustomerBundle\Entity\Customer")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="created_by", referencedColumnName="id", nullable=false)
* })
*/
protected $createdBy;
So I need to display my Customer entity, but only containing fields like id and name for example.
EDIT :
I already have an instance of Project, the entity with my createdBy field, and I want to grab my customer data 'formatted' for this entity and not returning too much fields like password ...
Thanks
It sounds like expected behavior to me. The doctrine documentation seems to imply that eager fetching is only one level deep.
According to the docs:
Whenever you query for an entity that has persistent associations and
these associations are mapped as EAGER, they will automatically be
loaded together with the entity being queried and is thus immediately
available to your application.
http://doctrine-orm.readthedocs.org/en/latest/reference/working-with-objects.html#by-eager-loading
The entity being queried has eager on createdBy so it will be populated.
to bypass you can create a method in your entity repository as following :
// join entities and load wanted fields
public function findCustom()
{
return $this->getEntityManager()
->createQuery(
'SELECT p FROM AppBundle:Product p ORDER BY p.name ASC'
)
->getResult();
}
hope this helps you
try this and let me know if it works, you should fill the right repository name
'XXXXBundle:CustomerYYYY', 'c'
public function findUser($user_id){
$qb = $this->_em->createQueryBuilder('c')
->select(array('c', 'cb.id', 'cb.name'))
->from('XXXXBundle:Customer', 'c')
->where('c.id <> :id')
->leftJoin('c.createdBy', 'cb')
->setParameter('id', $user_id)->getQuery();
if ($qb != null)
return $qb->getOneOrNullResult();
return null;
}
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.
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 try to write this kind of query :
$qb = $this
->createQueryBuilder()
->select('gh')
->orderBy("IF(gh.language = 'en', 1, 0)", 'desc')
->getQuery();
How can it work ?
Short answer: You can't.
Long answer:
Doctrine only supports a limited subset of SQL functions because it was built to be flexible and not SQL engine specific. Therefore, things like IF statements are not implemented into doctrine, and won't be implemented because the way MySQL handles IF, for example, is different from what PostgreSQL does it (it does not even exist).
However, Doctrine 2 is so powerful that allows you to write your own functions, and extend its functionality, however I wouldn't recommend this unless it's really really necessary to do so.
You can find more details about user defined functions and Doctrine in their documentation:
http://doctrine-orm.readthedocs.org/en/latest/cookbook/dql-user-defined-functions.html
What I would do, is to write raw SQL to query your database. You will have to create a custom repository to keep your code clean and organized.
The custom repositories are an "extension" of your entities.
For example, if you have an entity GH, then your entity class looks like this:
<?php
namespace YourNamespace\ProjectName\WhateverBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* GH
*
* #ORM\Table(name="GH")
* #ORM\Entity(repositoryClass="YourNamespace\ProjectName\WhateverBundle\GHRepository")
*/
class Category {}
Note the repositoryClass statement added to your annotations, that indicates where your customer repository is.
Then create a repository class in the same folder where your entity class is. The convention is to name it "Repository", so in your case it would be: GHRepository and extend the parent class EntityRepository, your custom repository class would look like this:
<?php
namespace YourNamespace\ProjectName\WhateverBundle\Entity;
use Doctrine\ORM\EntityRepository;
use PDO;
class GH Repository extends EntityRepository {
}
Then you can just create public functions inside your class that use raw SQL to query your database. The convention is to name your functions starting with findBy or findAllBy but it's not necessary.
For example, one of my functions look like this:
public function findAllProductsByCategory($categoryId) {
$pdo = $this->getEntityManager()->getConnection();
$findProductsQuery =
"SELECT
P.*
FROM
Products P
LEFT JOIN Types_Products TP ON TP.product_id = P.id
LEFT JOIN Types T ON TP.type_id = T.id
LEFT JOIN Categories_Types CT ON CT.type_id = T.id
LEFT JOIN Categories C ON CT.category_id = C.id
WHERE
C.id = :categoryId
GROUP BY
P.id";
$stmt = $pdo->prepare($findProductsQuery);
$stmt->bindParam(':categoryId', $categoryId, PDO::PARAM_INT);
$stmt->execute();
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
and I can use this function, in any controller this way:
$doctrine = $this->getDoctrine();
$em = $doctrine->getManager();
$categoryRepository = $em->getRepository('MyBundle:Category');
$products = $categoryRepository->findAllProductsByCategory($category->getId());
Asfar as I know you cannot do this in doctrine. In MySQL you would use CASE and is a vendor specific keyword.