Doctrine: Fetch entities by property in array - symfony

I have an entity Image which has the property "categories". Categories is an instance of Doctrine's ArrayCollection and holds different Category objects.
I now want to fetch all Image objects containing the Category object "main".
With normal properties this looks like this:
$repository->findBy(array('category' => 'main'));
Is this also possible with Array properties or do I have to fetch the Images over the Category side?
Regards!

The ->findBy() method only works on the owning side of relations.
You can also create a custom method in your repostiory:
public function findByCategoryName($categoryName)
{
return $this
->createQueryBuilder('image')
->innerJoin('image.categories', 'category')
->where('category.name = :categoryName')
->setParameter('categoryName', $categoryName)
->getQuery()
->getResult()
;
}

Related

Sulu media display in list

I created an Entity, PlayerInfo. Inside of that, I have photo field and defined MediaInterface, and use it with that. In adminUI everything is shown OK, in Frontend too, but when I got an idea to show thumbnail in list, I get empty field, and error in console that says:
Invalid type given: "number". "object" is needed. (TRANSFORMER ERROR)
Here is my list XML property code:
<property name="avatar" visibility="always" translation="sulu_admin.avatar" sortable="false">
<field-name>id</field-name>
<entity-name>SuluMediaBundle:Media</entity-name>
<joins>
<join>
<entity-name>SuluMediaBundle:Media</entity-name>
<field-name>App\Entity\PlayerInfo.photo</field-name>
</join>
</joins>
<transformer type="thumbnails"/>
</property>
What can be the problem?
The table adapter of the Sulu list view uses a transformer to determine what should be rendered inside of the cell based on the data returned by your API.
You have used the thumbnails transformer in your code. If you look at the ThumbnailFieldTransformer implementation, you see that the transformer expects data in a format like this:
{
"sulu-40x40":"/uploads/media/sulu-40x40/02/2-isle.jpg?v=1-0",
}
I suspect that you have used the ListBuilder component of Sulu to gather that list data in the controller that implements your API (like the sulu-demo project). The ListBuilder component uses optimized queries to load the requested data directly from the database.
Unfortunately, the ListBuilder component returns only the ID of the associated media entity and does not transform the data into the format that is expected by the ThumbnailFieldTransformer automatically. You need to construct and set the object that contains the thumbnail urls in your controller using the MediaManagerInterface::getFormatUrls method.
As an example, you can have a look at how the organization logo is handled in the AccountController that implements the API for the built-in organization list of Sulu:
$ids = \array_filter(\array_column($accounts, 'logo'));
$logos = $this->mediaManager->getFormatUrls($ids, $locale);
foreach ($accounts as $key => $account) {
if (\array_key_exists('logo', $account) && $account['logo'] && \array_key_exists($account['logo'], $logos)) {
$accounts[$key]['logo'] = $logos[$account['logo']];
}
}
You have to set the Image in your Entity.
Let's take a "User" Entity for example.
I think you have some kind of property like "avatar".
/**
* #var MediaInterface|null
*/
protected $avatar;
The error occurs because you have only saved the ID.
The solution is to set the avatar in the Entity before.
e.g. in the controller you call for the list.
$media = $this->container->get('sulu.repository.media')->findMediaById($user->getAvatar()->getId());
$user->setAvatar($media);

Symfony 5 : setting a property based on a parent property value

In Symfony 5, let's say we have 3 entities linked like this :
Foo is an entity that have Bar as child. Foo as one property called fooProperty.
Bar have Foo as parent, and Baz as child
Baz have Bar as parent, of course. Baz have one property called bazProperty.
Let's say that the value of bazProperty is dependent of the value of the value of fooProperty. My first idea was to refer to foo entity inside the baz entity class :
function setBazProperty($value) {
if ($this->getBar()->getFoo()->getFooProperty > 0) {
$this->bazProperty = $value;
} else {
$this->bazProperty = 0;
}
}
But this occur many sql queries, as Doctrine will ask first to get Bar entity, then Foo entity.
So I imagine to access to Foo entity through an unique query managed in a repository class.
But, because of the separation of concern, I wouldn't inject the repository in the Baz entity, but I would use a service instead.
So I've created a BazService with two arguments in the constructor :
public function __construct(Baz $baz, BazRepository $bazRepository)
{
$this->baz = $baz;
$this->bazRepository= $bazRepository;
}
In this service, I've also added a method fetching Foo entity :
public function getFoo()
{
return $this->bazRepository->getFoo($this->baz);
}
And last, in a controller, now I would like to get Foo entity :
$bazService = new BazService($baz);
$foo = $bazService->getFoo();
Here are my questions :
I'm not able to initialize bazService in the controller. The constructor ask for 2 argmuments (the entity and the repository) and I would like only to provide the entity and to inject automatically the repository class.
I've tried to add it in serices.yaml without success (probably because I didn't instantiate bazService in the constructor of my controller) :
App\Service\BazService:
arguments:
$bazRepository: App\Repository\BazRepository
Is there any other solution ? How I can inject the entity class differently in the service class ?
Using a service when setting a property is too complex is the recommended solution ? Some article (here, here and here) recommend to use a service when the method inside entity class become more complex and require external entity or repositories. But maybe there's a lighter solution...
Separation of concerns is IMHO the right argument to look at. There are some approaches to go for, that depend largely on how you retrieve the entity. However, in my opinion, the concern of an entity is NOT to fetch some other entities data from the database, it is the repository's or maybe the controller's concern. So let's see how to do that ...
One way is to automatically retrieve the parent entity / entities. Depending on your use case, you could do that in general (via fetch="EAGER" - see: #ManyToOne / #OneToOne), otherwise you could implement a special repository function, that fetches the additional entities. If your entities always only have at most one parent each, this can absolutely reduce the number of queries from 3 to 1, since the parent and parent of parent entities can be retrieved simultaneously.
// in BazRepository
public function getWithParents($id) {
$qb = $this->createQueryBuilder('baz');
$qb->leftJoin('baz.bar', 'bar')
->addSelect('bar')
->leftJoin('bar.foo', 'foo')
->addSelect('foo')
->where('baz.id = :id')
->setParameter('id', $id);
return $qb->getQuery()->getOneOrNullResult();
}
if the child entity accesses the parent entity, it should just use the entity from cache and avoid a second query (source: https://symfonycasts.com/screencast/doctrine-relations/join-n-plus-one)
If having the entities is already "too much", you can slightly cheat by (again) creating a custom repository method that not only fetches the Baz entity, but also the Foo.fooProperty value and sets it for a virtual/temporary property on the Baz entity.
// in BazRepository
public function getWithFooProperty(int $id) {
$qb = $this->createQueryBuilder('baz');
$qb->leftJoin('baz.bar', 'bar')
->lefTJoin('bar.foo', 'foo')
->select('foo.fooProperty as fooProperty')
->where('baz.id = :id')
->setParameter('id', $id);
$result = $qb->getQuery()->getResult(); // should be an array with an array with two keys, but I might be wrong
if(count($result) == 0) {
return null;
}
$baz = $row[0][0];
$baz->fooProperty = $row[0][1];
return $baz;
}
(Disclaimer: please check the $result here, to see if the accesses are correct)
you now could access it in Baz:
function getFooProperty() {
if(isset($this->fooProperty)) {
return $this->fooProperty;
} else {
// fallback, in case entity was fetched by another repository method
return $this->getBar()->getFoo()->getFooProperty();
}
}

Can't set entity value and insert it into table because of lazyloading

I have UserItem() entity.
I have Shard() entity.
I have Item() entity.
Shard() entity has method getItem() which gets Item() entity.
When I try to add data to UserItem() entity.
$userItem->setItem($item);
$userItem->setUser($this->websiteUser);
$em->merge($userItem);
By using setItem() and setUser() methods.
I can't do it because in setItem($item)
parameter $item has class of
Proxies\__CG__\AppBundle\Entity\Item
When it only accepts
AppBundle\Entity\Item
I get $item like that:
$item = $em->getRepository(Item::class)->find($shards[$i]->getItem()->getId());
$shards is an array with Shard() entities.
All relationships in Entities are defined correctly.
How can I overcome this situation?
I know that I get proxies because of lazyloading.

Symfony, OneTwoMany, first child element in twig

Product and Image are two entities linked by a oneToMany association (one product has many images). I try to enumerate with TWIG each product with the first image (filename fied) like this :
class ProductRepository extends EntityRepository
{
public function getProductsWithImages() {
$query = $this->createQueryBuilder('e')
->leftJoin('e.images', 'i', 'with', 'i.order = :order')
->setParameter('order' , 0)
->select('e')
->addSelect('i');
return $query->getQuery()->getResult();
}
}
But I got this error :
Method "filename" for object "\entity\product" does not exist.
I understand why (product entity has no image field). What is the best pratice to get only one child element without add a reference on the parent (like a mainImage field) ?
As the doctrine documentation explains :
A one-to-many association has to be bidirectional, unless you are
using an additional join-table. This is necessary, because of the
foreign key in a one-to-many association being defined on the “many”
side. Doctrine needs a many-to-one association that defines the
mapping of this foreign key.
I have this kind of relation in my current project and I simply defined bidirectional one-to-many association. So in your twig view you should be able to do for example :
{# first Image linked to the Product #}
{{ product.images.first }}
The attribute images is an ArrayCollection.
Hope it helps

How to transform a collection of entities to form elements indexed by their IDs?

I'm having trouble rendering a collection of entity IDs as form elements using a simple transformer.
In the main form's buildForm method I have this for the things collection:
$collection = $builder->create( 'things', 'collection' );
$collection->addModelTransformer( new EntityTransformer );
$builder->add( $collection );
In the EntityTransformer I ensure the form receives scalars indexed by the entities' primary keys:
public function transform( $entities ){
$scalars = array();
foreach( $entities as $entity ){
$scalars[ $entity->getId() ] = (string) $entity;
}
return $scalars;
}
I am now expecting to see form elements with names corresponding to the entity IDs, like "form[things][998]", "form[things][999]" etc.. But instead the entity ID keys are ignored and the elements become zero-indexed "form[things][0]", "form[things][1]" etc..
It doesn't matter what type of form elements I use, or what I assign as the scalar values of the array, it is the associative mapping which gets lost in all cases.
I know there are other ways to achieve entity mapping, but I want to understand why this approach doesn't work.
My question is simply, why is the collection field ignoring the keys defined by the transformer and reindexing the collection?

Resources