How to convert Doctrine2 entity graph to array - orm

I have the following entities
Product Entity
Category Entity
Tag Entity
Brand Entity
What I am trying to do is when I get the Product by id, I want to convert it to array including all associated entities including their associated entries.
The result array needs to be serializable.

Use doctrine query builder and the HYDRATE_ARRAY hydration mode?
Edit: Sorry for not including examples, I was on my mobile at the time. Check out the blog post I wrote on some good practices with doctrine that semi-relates to this.
For a code example the repository method I would write to cover this would be as follows (I would avoid using abbreviations like p c etc. as it makes your code a lot less readable (at least while you are getting started with doctrine...)
<?php
namespace Vendor\Prefix\Repository;
use Doctrine\ORM\EntityRepository;
class ProductRepository extends EntityRepository
{
public function find($id)
{
// $em is the entity manager
$qb = $em->createQueryBuilder();
$qb
->select('product', 'category', 'tag', 'brand', 'category_child')
// Or SomeBundle:Product if you're on symfony
->from('Vendor\Prefix\Entity\Product', 'product')
// You need to explicitly fetch join all your
// associations...and select them
->leftJoin('product.Brand', 'brand')
->leftJoin('product.Tags', 'tag')
->leftJoin('product.Categories', 'category')
->leftJoin('category.Children', 'category_child')
// Use prepared statments...its a good habit
->where($qb->expr()->eq('product.ID', ':id'))
->setParameter('id', $id)
;
$query = $qb->getQuery();
// Potential Hydration Modes
// --------------------------------
// Doctrine\ORM\Query::HYDRATE_OBJECT
// Doctrine\ORM\Query::HYDRATE_ARRAY
// Doctrine\ORM\Query::HYDRATE_SCALAR
// Doctrine\ORM\Query::HYDRATE_SINGLE_SCALAR
// Doctrine\ORM\Query::HYDRATE_SIMPLEOBJECT
// Hydrate the result as an array to get the requested format
// When you use array hyrdation doctrine does it according
// to your entity graph
return $query->getResult(Doctrine\ORM\Query::HYDRATE_ARRAY);
}
}

I think you need to use JOIN in DQL syntax:
$results = $entityManager->executeQuery('SELECT p FROM Product p JOIN p.Brand b JOIN p.Tags t JOIN p.Categories c')->getArrayResult();
var_dump($results);
Also, I think you should avoid class members starting with an uppercase letter. It could introduce a bit of confusion with the entity name.

Related

Compare two database fields in extbase repository

I am using TYPO3 8. In my extension I have a database table "company" in which I store for each company the total number of places (number_places) and the number of occupied places (occupied_places).
Now I want to limit the search to companies which have available places left.
In MySQL it would be like this:
SELECT * FROM company WHERE number_places > occupied_places;
How can I create this query in the extbase repository?
I tried to introduce the virtual property placesLeft in my model but it did not work.
I don't want to use a raw SQL statement as mentioned below, because I already have implemented a filter which uses plenty of different constraints.
Extbase query to compare two fields in same table
You can do it like this in your repository class, please note the comments inside the code:
class CompanyRepository extends \TYPO3\CMS\Extbase\Persistence\Repository
{
public function findWithAvailablePlaces(bool $returnRawQueryResult = false)
{
// Create a QueryBuilder instance
$queryBuilder = $this->objectManager->get(\TYPO3\CMS\Core\Database\ConnectionPool::class)
->getConnectionForTable('company')->createQueryBuilder();
// Create the query
$queryBuilder
->select('*')
->from('company')
->where(
// Note: this string concatenation is needed, because TYPO3's
// QueryBuilder always escapes the value in the ExpressionBuilder's
// methods (eq(), lt(), gt(), ...) and thus render it impossible to
// compare against an identifier.
$queryBuilder->quoteIdentifier('number_places')
. \TYPO3\CMS\Core\Database\Query\Expression\ExpressionBuilder::GT
. $queryBuilder->quoteIdentifier('occupied_places')
);
// Execute the query
$result = $queryBuilder->execute()->fetchAll();
// Note: this switch is not needed in fact. I just put it here, if you
// like to get the Company model objects instead of an array.
if ($returnRawQueryResult) {
$dataMapper = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper::class);
return $dataMapper->map($this->objectType, $result);
}
return $result;
}
}
Notes:
If you have lots of records to deal with, I would - for performance reasons - not use the data mapping feature and work with arrays.
If you want to use the fluid pagination widget, be sure you don't and build your own pagination. Because of the way this works (extbase-internally), you'd get a huge system load overhead when the table grows. Better add the support for limited db queries to the repository method, for example:
class CompanyRepository extends \TYPO3\CMS\Extbase\Persistence\Repository
{
public function findWithAvailablePlaces(
int $limit = 10,
int $offset = 0,
bool $returnRawQueryResult = false
) {
// ...
$queryBuilder
->setMaxResults($limit)
->setFirstResult($offset);
$result = $queryBuilder->execute()->fetchAll();
// ...
}
}
I think you cant do this using the default Extbase Query methods like equals() and so on. You may use the function $query->statement() for your specific queries like this.
You also can use the QueryBuilder since TYPO3 8 which has functions to compare fields to each other:
https://docs.typo3.org/typo3cms/CoreApiReference/latest/ApiOverview/Database/QueryBuilder/Index.html#quoteidentifier-and-quoteidentifiers
It's fine to use this QueryBuilder inside Extbase repositories. After this you can use the DataMapper to map the query results to Extbase models.
In case of using "statement()" be aware of escaping every value which may cause any kind of SQL injections.
Based on the current architecture of TYPO3, the data structure is such that comparing of two tables or, mixing results from two tables ought to be done from within the controller, by injecting the two repositories. Optionally, you can construct a Domain Service that can work on the data from the two repositories from within the action itself, in the case of a routine. The service will also have to be injected.
Note:
If you have a foreign relation defined in your table configuration, the results of that foreign relation will show in your defined table repository. So, there's that too.

Using inline sql with entity frame work and c#

We are having som performance issue with EF and I want to try rewrite a query to inline sql. However I am having some difficulties, it is probably just a noob issue.
Lets say I have 3 classes: License, GroupLicense and Product
public class License
{
//stuff
Product MyProduct{get;set}
}
public class GroupLicense:License
{
// more stuff
}
public class Product
{
//product info stuff
}
Now I need to fetch some Grouplicenses depending on some requirements. However doing it with the datacontext and linq takes 2 minutes.
Something similar to this
var institutionLicenses = db.GroupLicenses
.Include(lic => lic.Product).Where(x => productIds.Contains(x.Product.Id) && x.LicenseStatus == StatusEnum.Active).ToList();
I want to do the same query using inline sql similar to this: I join the tables so all the primitive fields are okay.
var gl = db.Database.SqlQuery<GroupLicense>("select * from GroupLicense as g left join Licenses on g.Id =Licenses.Id").ToList();
(It is just example code - I know it is not working :))
However when executing, The base product property on License is null, all the primitive fields are there.
What do I need to change in my sql statement to make it work?

NHibernate QueryOver projection on many-to-one

I am trying to get a QueryOver working using a Projection on a many-to-one.
The class "Post" has a property many-to-one "Creator".
Using
session.QueryOver(Of Post).
Select(Projections.
Property(of Post)(Function(x) x.Creator).
WithAlias(Function() postAlias.Creator)).
TransformUsing(Transformers.AliasToBean(Of Post)()).
List()
works BUT each creator is retrieved by a single query rather than using a join like it is done when not using a select/projection. So if there are 5 posts with 5 different creators, 6 queries will be run 1 for the list of posts and 5 for the creators.
I tried to get it working using a JoinAlias but nothing really did the job.
I already searched for a solution, but all solutions I found did use the Linq-Provider which does not really fit since the actual "field list" is passed via a parameter.
Does anyone know if there is a solution to this other than the linq provider?
There is a solution, we can use projections for many-to-one and then custom result transformer.
DISCLAIMER: I can read VB syntax but do not have enough courage to write... I expect that you can read C# and convert it into VB....
So we can have projection like this:
// aliases
Post root = null;
Creator creator = null;
// projection list
var columns = Projections.ProjectionList();
// root properties
columns.Add(Projections.Property(() => root.ID).As("ID"));
columns.Add(Projections.Property(() => root.Text).As("Text"));
// reference properties
columns.Add(Projections.Property(() => creator.ID).As("Creator.ID"));
columns.Add(Projections.Property(() => creator.FirstName).As("Creator.FirstName"));
// so our projections now do have proper ALIAS
// alias which is related to domain model
// (because "Creator.FirstName" will be use in reflection)
var query = session.QueryOver<Post>(() => root)
.JoinAlias(() => root.Creator, () => creator)
.Select(columns)
Now we would need smart Transformer, our own custome one (plugability is power of NHibernate). Here you can find one:
public class DeepTransformer
And we can continue like this
var list = query
.TransformUsing(new DeepTransformer<Post>())
.List<Post>()
Check also this:
Fluent NHibernate - ProjectionList - ICriteria is returning null values
NHibernate AliasToBean transformer associations

How to setup yii model relations right, is using 't' to refer to default table a good idea

Usage of 't' in model relations can give the error
"Column not found: 1054 Unknown column 't.etc' in 'on clause'."
It's the usage of 't' to refer to the current table in CActiveRecord Model relations.
I often stumble accross it when using ,findAll, CActiveDataProvider, etc
Sometimes it works sometimes it doesn't depending on what model you execute and from where.
I tried using tableAlias but it doesn't work. There must be an easy way.
How can I setup my models and it's relations in such a way that the relations are stable
and always works?
Here is an example of two classes to show the problem...
class Order extends CActiveRecord
{
/**
* #return array relational rules.
*/
public function relations()
{
// NOTE: you may need to adjust the relation name and the related
// class name for the relations automatically generated below.
return array(
'author'=>array(self::BELONGS_TO, 'User', 'user_id'),
'shopproduct'=>array(self::BELONGS_TO, 'ShopProduct', 'product_id',
'with'=>array(
'tagsrelations',
),
),
);
}
}
class ShopProduct extends CActiveRecord
{
/**
* #return array relational rules.
*/
public function relations()
{
// NOTE: you may need to adjust the relation name and the related
// class name for the relations automatically generated below.
return array(
'author'=>array(self::BELONGS_TO, 'User', 'user_id'),
'tagsrelations'=>array(self::HAS_MANY, 'TagsRelations','',
'on'=>' tagsrelations.tbl_uuid = t.uuid ',
'with'=>'tag',
'order'=>'tag.name ASC',
),
);
}
}
// works
$model=Order::model()->with('shopproduct')->findAll();
// doesn't work
// "Column not found: 1054 Unknown column 't.uuid' in 'on clause'. The SQL statement executed was"
$model=ShopProduct::model()->with('tagsrelations')->findAll();
Maybe someone can explain why this isn't working in an understanding way.
How to fix this one once and for all. BELONG TO relations usually work.
What is the best to make an HAS MANY relationship that always works
Do you need the t only for the join or for some specific purpose? Because for the join this is not the right way to do it. It should be done sth like this:
'tagsrelations'=>array(self::HAS_MANY, 'TagsRelations', array('tbl_uuid'=>'uuid')),
Of course you can then add the with condition if you need that as well. But the join should be done this way.

Mapping two tables to one entity in Doctrine2

I'm looking at using doctrine for an application I'm working on - but after reading the documentation I'm having trouble conceptualizing how to represent the database structure we have in terms of entities.
I have many tables which have partner tables which hold translation data like the following....
Where I would like to have one Entity (Navigation Element) which had access to the 'label' field depending on what Language I set in my application. The following from the Doctrine documentation seems to suggest that you need to define one (single) table which is used to persist an entity
http://www.doctrine-project.org/docs/orm/2.0/en/reference/basic-mapping.html
By default, the entity will be
persisted to a table with the same
name as the class name. In order to
change that, you can use the #Table
annotation as follows:
Or do I need to define two entities and link them (or allow the translation table to inherit from the element table).
And what strategy would I use to always insert a language_id clause to the Join (to ensure I'm pulling the right label for the currently set language). Is this something I would define in the entity itself, or elsewhere?
This seems to suit a One-To-Many Bidirectional association. This is the scenario from that page translated to your situation:
/** #Entity */
class NavigationElement
{
// ...
/**
* #OneToMany(targetEntity="NavigationElementTranslation", mappedBy="navigationElement")
*/
private $translations;
// ...
public function __construct() {
$this->translations = new \Doctrine\Common\Collections\ArrayCollection();
}
}
/** #Entity */
class NavigationElementTranslation
{
// ...
/**
* #ManyToOne(targetEntity="NavigationElement", inversedBy="translations")
* #JoinColumn(name="navigation_element_id", referencedColumnName="id")
*/
private $navigationElement;
// ...
}
You could add a getLabel($languageId) method to the NavigationElement entity that searches through the translations to get the correct label:
public function getLabel($languageId) {
foreach($this->translations as $trans) {
if($trans->languageId == $languageId)
return $trans->label;
}
throw new InvalidArgumentException();
}
And you could use the following DQL to ensure you only load the translation you want into the $translations property:
$query = $em->createQuery(
"SELECT ne, net
FROM Entity\NavigationElement ne
JOIN ne.translations net WITH net.languageId = :langId"
);
$query->setParameter('langId', $languageId);
$navigationElements = $query->execute();
This situation sounds like one where you would want to cache aggressively. Make sure you look into Doctrine 2's caching mechanisms too.
Also, internationalization can be handled reasonably well in PHP with gettext if you find join tables for translations start to become unmanageable.
I would also direct anyone who has to tackle this same problem to take a look at the following doctrine extension.
http://www.gediminasm.org/article/translatable-behavior-extension-for-doctrine-2