Doctrine Orm - Searching embedded associations - doctrine-orm

I have an Asset Entity that uses an embedded association:
/**
* #ORM\Entity
*/
class Asset
{
....
/**
* #ORM\Embedded(class="Money\Money")
*/
private $code;
I want to search this class and my first instinct was to do something like this:
public function findOneByCurrencyCode(string $currencyCode)
{
$qb = $this->assetRepository->createQueryBuilder('asset');
$qb->innerJoin('asset.code', 'code')
->where('code.currency = :currency')
->setParameter('currency', $currencyCode);
$result = $qb->getQuery()->getOneOrNullResult();
return $result;
}
This, however, returns the following:
[Semantical Error] line 0, col 65 near 'code WHERE code.currency': Error:
Class Domain\Asset\Asset has no association named code
How do you search embedded classes?
EDIT:
I can get a result by doing something like this, however, this I feel is a hack:
$query = "SELECT * from asset where code_currency='BTC';";
$statement = $this->objectManager->getConnection()->prepare($query);
$statement->execute();
$result = $statement->fetchAll();
return $result;

I tried a bunch of different things and managed to get the answer:
$qb = $this->assetRepository->createQueryBuilder('asset');
$qb->where('asset.code.currency = :currency')
->setParameter('currency', $currencyCode);
$result = $qb->getQuery()->getOneOrNullResult();
return $result;
Turns out no inner join is required. Not sure why and perhaps someone can answer this in time, however the above appears to work with embedded objects
Hope this helps someone else.

"Embedded associations" does NOT exist. So, you doesn't need JOINS.
An embedded is part of your entity along with the others properties.
You have to do something like that:
SELECT u
FROM User u
WHERE u.address.city
(In this query, your entity is User, your embedded is Address and *city is the property of your
embedded).
It's pretty good explained in Doctrine documentation:
Doctrine embeddables

Related

Pagerfanta does not like my doctrine query

In an application built on the symfony 4 framwork, I use a query in my user repository to find users with a specific role, that looks like this:
public function findByRole($role)
{
$qb = $this->_em->createQueryBuilder();
$qb->select('u')
->from($this->_entityName, 'u')
->where('u.roles LIKE :roles')
->setParameter('roles', '%"'.$role.'"%');
return $qb->getQuery()->getResult();
}
Now this query works well in general, but whenever I use it in combination with pagerfanta i encounter a problem. For example whenever I use the following in a controller:
$em = $this->getDoctrine()->getManager();
$query = $em->getRepository(User::class)->findByRole('ROLE_ADMIN');
$pagerfanta = $this->paginate($request, $query);
I get the error: "Call to a member function setFirstResult() on array".
To get around this problem I use:
$em = $this->getDoctrine()->getManager();
$query = $em->getRepository(User::class)->createQueryBuilder('u')
->andWhere( 'u.roles LIKE :role')
->setParameter('role', '%"ROLE_ADMIN"%');
$pagerfanta = $this->paginate($request, $query);
This works with pagerfanta without any problems. I just do not understand what is wrong with the first query (which does work whenever I do not use pagerfanta). Any ideas?
In order to make it work, just remove "->getQuery()->getResult();".

Doctrine CreateQueryBuilder returns repeated result

In Symfony3 I'm running
php app/console generate:doctrine:entity --entity=AcmeBlogBundle:Post
It creates 2 files: Post (in entity folder) and PostRepository (in repository folder extending \Doctrine\ORM\EntityRepository)
Everything is fine until I try to run the following repository-custom-function in my controller
$rir = $this->getDoctrine()->getRepository("AcmeBlogBundle:Post");
$replacementInstruction = $rir->getOneBy(
array("id" => 6)
);
My custom repository function is as follow
public function getOneBy($option)
{
$alias = "p";
$fields = $this->prepareRequestSelectFields("p");
$qb = $this->createQueryBuilder('Post');
$qb->select($fields)
->from('AcmeBlogBundle:Post', $alias)
->where($alias . '.id = :id')
->setParameter('id', 6)
;
$result = $qb->getQuery()->getResult();
}
private function prepareRequestSelectFields($alias)
{
return $alias. ".id";
}
In my database there are 10 posts with id from 1 to 10, so I expect it to return 1 result, however it return correct Post (id 6) 10 times
Why is that?
p.s. if I move the query builder to a custom service wrapper e.g. PostManager it works just fine (returning 1)
This doesn't really answer my question but apparently createQueryBuilder() in EntityRepository and in EntityManager are different, thanks to https://maltronic.io/2014/12/22/doctrine-createquerybuilder-entitymanager-vs-entityrepository/
So in my EntityRepository it should be
...
$qb = $this->createQueryBuilder('p'); // this should be the alias
$qb->select($fields)
// ->from('AcmeBlogBundle:Post', $alias) // remove this
->where($alias . '.id = :id')
->setParameter('id', 6)
;
...
Personally I dislike how 2 different functions has the same name, but I guess it's fine because one is from Doctrine and one is from Symfony. They are decoupled
edit Actually I might be wrong, they are both from Doctrine ... sigh
Still tho, it doesn't answer why in my original code, it returns multiple time
What about getSingleResult() rather than getResult() ? And you should maybe use the doctrine method findOneBy()
You can use the helper methods provided by the repositories, allowing you to fetch one or multiples entities from repositories with dynamic method names, in your case you want to fetch one post by id:
//fetch one
$replacementInstruction = $this->getDoctrine()->getRepository("AcmeBlogBundle:Post")->findOneById(6);
For information, fetching all entities is done with ->findByProperty

Doctrine : Translatable won't deliver translations for entities linked by #ManyToOne (or others associations)

Say you have a Category entity :
class Category
{
/**
* #Gedmo\Translatable
*/
private $categoryName;
}
And a Module entity.
Each Module belongs to one Category :
class Module
{
/**
* #Gedmo\Translatable
*/
private $moduleName;
/**
* #ManyToOne(targetEntity="Module")
*/
private $category;
}
Using the query hint I can get a Module translated to a specific locale :
function getModule($id, $locale)
{
$query = $em->createQuery('select m from AppBundle\Entity\Module m where m.id=' . $id);
$query->setHint(TranslatableListener::HINT_TRANSLATABLE_LOCALE, $locale);
$query->setHint(
\Doctrine\ORM\Query::HINT_CUSTOM_OUTPUT_WALKER,
'Gedmo\\Translatable\\Query\\TreeWalker\\TranslationWalker'
);
$module = $query->getSingleResult();
return $module;
}
Testing :
$module = getModule(1234, 'en');
// SUCCEED
$this->assertEquals('module name in english', $module->getModuleName();
$category = $module->getCategory();
// FAIL !
$this->assertEquals('category name in english', $category->getModuleName();
// SUCCEED (but one more request)
$category->setTranslatableLocale('en');
$em->refresh($category);
$this->assertEquals('category name in english', $category->getModuleName();
In few words : the query hint is able to insert a JOIN to the SELECT statement for the Module entity.
Call to $module->getCategory() will trigger lazy loading. But this time, there will be no translation JOINed.
Meaning I have to explicitly ask for the english version of the category.
Question : is it possible to have associated entities translated when querying the owner entity ? (having the FAILed test to succeed)
I already tried eager fetching on the ManyToOne association. Won't work because I am querying through DQL.
UPDATE :
To make this specific example to work (and have a better code) I can JOIN the Category in my DQL :
select m,c from Module join m.category where m.id=id
This will load the module and its category with the correct translations.
But in a perfect world I would love to have a generic code like this one :
protected function getEntityById($className, $id, $locale = null)
{
$dql = 'select e from AppBundle\Entity\\' . $className . ' e where e.id=' . $id;
$query = $this->em->createQuery($dql);
$query->setHint(
\Gedmo\Translatable\TranslatableListener::HINT_TRANSLATABLE_LOCALE,
$locale
);
$query->setHint(
\Doctrine\ORM\Query::HINT_CUSTOM_OUTPUT_WALKER,
'Gedmo\\Translatable\\Query\\TreeWalker\\TranslationWalker'
);
$entity = $query->getSingleResult();
return $entity;
}
How to update this piece of code to have the associated entities to be loaded with the correct locale ?
I'm afraid there is no way to affect the behaviour of $module->getCategory(). Association's loading can only be affected by using doctrine filters. Unfortunately these filters can only add to WHERE part of request, so it is not possible to add join.
I'd create a function in category repository to get category with the proper translation like you did in the getModule(). I don't think there is another way. Ex:
$category = $categoryRepository->findByModule18n($module, $locale);

Flow: Convert object to array for CSV export

I want to export my object "mitglied" to a .csv-file. My controller looks like that:
public function exportAction() {
// find all mitglieds
$records = $this->mitgliedRepository->findTennis();
// Set path for export-file
$csvPath = '/var/www/apps/flow/Packages/Application/ITOOP.Atc/Resources/Private/Export/test.csv';
$fp = fopen($csvPath, 'w');
foreach ($records as $lines) {
fputcsv($fp, $lines);
}
fclose($fp);
}
When I call the exportAction, I get a an error:
#1: Warning: fputcsv() expects parameter 2 to be array, object given in /var/www/apps/flow/Data/Temporary/Development/Cache/Code/Flow_Object_Classes/itoop_atc_Controller_MitgliedController.php line 494
line 494 is...
fputcsv($fp, $lines);
...so I think I have to convert the object "mitglied" to an array.
My the public function findTennis in my mitgliedRepository looks like that:
public function findTennis() {
$query = $this->createQuery();
$result = $query->matching($query->equals('abteilung', 'Tennis'))
->setOrderings(array('name' => \TYPO3\Flow\Persistence\QueryInterface::ORDER_ASCENDING))
->execute();
return $result;
}
I tried to set toArray(); in the repository like the that:
public function findTennis() {
$query = $this->createQuery();
$result = $query->matching($query->equals('abteilung', 'Tennis'))
->setOrderings(array('name' => \TYPO3\Flow\Persistence\QueryInterface::ORDER_ASCENDING))
->execute()
->toArray;
return $result;
}
But then I get the following error:
#1: Notice: Undefined property: TYPO3\Flow\Persistence\Doctrine\QueryResult::$toArray in /var/www/apps/flow/Data/Temporary/Development/Cache/Code/Flow_Object_Classes/itoop_atc_Domain_Repository_MitgliedRepository.php line 105
line 105 of course is
->toArray;
Does anybody know, how to convert an object to an array in flow?
With the following example the export works, so I think the (formatting of the) repository query is the problem.
public function exportAction() {
// Set path for export-file
$csvPath = '/var/www/apps/flow/Packages/Application/ITOOP.Atc/Resources/Private/Export/test.csv';
$test = array (
array('xxx', 'bbb', 'ccc', 'dddd'),
array('123', '456', '789'),
array('aaa', 'bbb')
);
$fp = fopen($csvPath, 'w');
foreach ($test as $lines) {
fputcsv($fp, $lines);
}
fclose($fp);
}
Please point me to the right direction. Thank you!
The error messages explained
#1: Warning: fputcsv() expects parameter 2 to be array, object given in /var/www/apps/flow/Data/Temporary/Development/Cache/Code/Flow_Object_Classes/itoop_atc_Controller_MitgliedController.php line 494
fputcsv expects it's 2nd parameter to be an array. That array will be written as a CSV line into single file, with each array element as column. When iterating over your $records variable, you get instances of your domain object class (so probably sth. like ITOOP\Atc\Domain\Model\Mitglied). That's undefined behaviour, thus the warning.
#1: Notice: Undefined property: TYPO3\Flow\Persistence\Doctrine\QueryResult::$toArray in /var/www/apps/flow/Data/Temporary/Development/Cache/Code/Flow_Object_Classes/itoop_atc_Domain_Repository_MitgliedRepository.php line 105
toArray is a function that is offered by Doctrine QueryResult class. Typically, Doctrine queries do not fetch all objects returned by the query, but return an iterator that fetches and maps entities on-demand. The toArray method fetches all records at once and returns an array instead of the iterator. Your error occurs, because you try to access toArray as a property, and not calling it as a method. The following code would be correct:
$result = $query->matching($query->equals('abteilung', 'Tennis'))
->setOrderings(array('name' => \TYPO3\Flow\Persistence\QueryInterface::ORDER_ASCENDING))
->execute()
->toArray(); // <- Mind the brackets!
However, this will not help you anything, because in your controller, you will still be iterating over a list of domain entities (foreach does not care if its iterating over an iterator or an array; that's actually the point of iterators in PHP).
Quick&Dirty solution
Convert your domain entities by hand in your controller. Only you can know how your CSV export should look like, so this cannot be automated. I'm thinking something like this:
foreach ($records as $record) {
$csvLine = [
$record->getFirstProperty(),
$record->getSecondProperty(),
// and so on...
];
fputcsv($fp, $csvLine);
}
Better solution
Rendering CSV data is not a concern that should be addressed in the controller. Basically, it should go into a view. You can implement a custom view class for handling the CSV output.
For that, you need to implement the \TYPO3\Flow\Mvc\View\ViewInterface. The easiest way to do this is to subclass \TYPO3\Flow\Mvc\View\AbstractView. Name your view class <PackageNamespace>\View\<Controller>\Action<Format> (so sth. like ITOOP\Atc\View\Mitglied\ExportCsv. Implement your CSV export logic in the view's render() method. Flow will pick up and use the view class automatically as soon as it's present.
Implementing custom views is explained in depth in this article -- it's in German though, although based on your class naming I suspect that won't be a problem ;).
I solved the problem with arbitrary DQL. As I mentioned I think the problem was that I didn't got an array as result by the query. But with the following query in my repository I do:
/**
* #Flow\Inject
* #var \Doctrine\Common\Persistence\ObjectManager
* inject Doctrine's EntityManager to execute arbitrary DQL
*/
protected $entityManager;
/**
* find mitglieder with Abteilung Tennis und return an array
*/
public function exportTennis() {
$query = $this->entityManager->createQuery("SELECT mitglied FROM \itoop\atc\Domain\Model\Mitglied mitglied WHERE mitglied.abteilung = 'Tennis'");
return $query->getResult(\Doctrine\ORM\Query::HYDRATE_ARRAY);
}
The important part I think is getResult(\Doctrine\ORM\Query::HYDRATE_ARRAY);

Doctrine paginator

I'm running into a problem with the Doctrine Paginator.
In my repository I have a function to retrieve a specific dataset.
I use the querybuilder for this:
{myEntityRepository}->createQueryBuilder($alias)
In order to select only specific fields I use the following:
if (count($selectFields) > 0) {
$qb->resetDQLPart('select');
foreach ($selectFields as $selectField) {
$qb->addSelect($alias . '.' . $selectField);
}
}
This works fine when I retrieve the whole set like this:
$query = $qb->getQuery();
$data = $query->getResult(AbstractQuery::HYDRATE_ARRAY);
But it fails when I use the paginator:
$paginator = new Paginator($qb, $fetchJoinCollection = false);
$total = $paginator->count(),
$data = $paginator->getQuery()->getResult(AbstractQuery::HYDRATE_ARRAY)
I get the error:
Not all identifier properties can be found in the ResultSetMapping:
relationID\vendor\doctrine\orm\lib\Doctrine\ORM\Query\Exec\SingleSelectExecutor.php(38)
Question: Why does the paginator fail when I select only specific fields?
Am I overlooking something? Or am I doing it wrong all together?
i am using this solution.
add use statements
use Zend\Paginator\Paginator;
use DoctrineORMModule\Paginator\Adapter\DoctrinePaginator as DoctrineAdapter;
use Doctrine\ORM\Tools\Pagination\Paginator as ORMPaginator;
in your action
$viewModel = new ViewModel();
$entityManager = $this->getServiceLocator()
->get('Doctrine\ORM\EntityManager');
$queryBuilder = $entityManager
->createQueryBuilder();
$queryBuilder->add('select', new Expr\Select(array('t.id', 't.name')));
$queryBuilder->add('from', 'Application\Entity\Table t');
$adapter = new DoctrineAdapter(
new ORMPaginator(
$queryBuilder
)
);
$paginator = new Paginator($adapter);
$paginator->setDefaultItemCountPerPage(20);
$page = (int)$this->params()->fromQuery('page');
if($page) $paginator->setCurrentPageNumber($page);
$viewModel->results = $paginator;
return $viewModel;
Doctrine is trying to hydrate a relationship outlined by your YAML file, using a field that doesn't exist because you've excluded it from your SELECT statement. Take a look at your mapping file to figure out what you need to add back in.
I would think that it's only complaining with the Paginator because the field is not being accessed (and therefore not being lazy-loaded) when you don't use the Paginator.
As an aside (and with zero understanding of your stack, so YMMV) I would avoid making a habit of SELECTing reduced result sets, as you'll find yourself running into odd issues like this all the time. If you do need extra performance, you'd be better off putting a good old caching layer in place...