symfony2 doctrine2: addselect elements to existing entity - performance

I am using Symfony2 and Doctrine2.
There are many instances when I have an entity and I need to loop through its associated entities. Of course it often triggers new queries and is not very performant.
Would there be a best practice to addselect other entities to an existing one?
Think about when you use a paramconverter in symfony. It just gets you the entity. What if I retrieve an order and want to loop through its orderLines? Do I need to build a new query and retrieve->leftjoin('order.orderlines', 'l')->addselect('l')->where('order = $order') ?

The best practice in this case is to use a custom repository method that explicitly joins the associated entity. Then Doctrine will not have to query individually through every iteration of the loop. You can also use this custom repository method in the ParamConverter.
Custom Repository Method:
Here's an example Controller:
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
/**
* #Route("/blog/{id}")
* #ParamConverter("post", class="MyBundle:Order",
options={"repository_method" = "findOrderWithLineItems"})
*/
public function showAction(Order $order)
{
}
Then specifying the custom repository on the entity:
namespace MyBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* #ORM\Entity(repositoryClass="MyBundle\Entity\OrderRepository")
*/
class Order
{
}
Then your custom repository:
namespace MyBundle\Entity;
use Doctrine\ORM\EntityRepository;
class OrderRepository extends EntityRepository
{
public function findOrderWithLineItems($id)
{
return $this->createQueryBuilder('o')
->join('o.orderLines', 'ol')
->where('o.id = :id')
->setParameter('id', $id)
->getQuery()
->getResult()
;
}
}
Always Join via Eager Fetching:
If instead you want to always fetch the associated entity (always join the table), even when doing a simple select on the base entity, you can specify an eager join on the associated entity:
class Order
{
/**
* #ManyToOne(targetEntity="OrderLines", fetch="EAGER")
*/
private $orderLines;
}

Related

API Platform - Which approach should I use for creating custom operation without entity

I'm new to API Platform. I think it's great but I cannot find any example how to create custom endpoint that isn't based on any entity. There are a lot of examples based on an entity and usually they are all about CRUD. But what about custom operations?
I need to create custom search through database with some custom parameters which aren't related to any entity.
E.g. I want to receive POST request something like this:
{
"from": "Paris",
"to": "Berlin"
}
This data isn't saved to db and I haven't entity for it.
After I receive this data, there should be a lot of business logic including db queries through a lot of db tables and also getting data from external sources.
Then, after the business logic is finished, I want to return back result which is also custom and isn't related to any entity.
E.g.
{
"flights": [/* a lot of json data*/],
"airports": [/* a lot of json data*/],
"cities": [/* a lot of json data*/],
.......
}
So, I think I'm not the only on who does something similar. But I really cannot find a solution or best practices how to do this.
In the documentation I've found at least three approaches and I cannot implement none of them.
The best one, I guess the most suitable for me it is using Custom Operations and Controllers. But documentation says this one is not recommended. Also I think I should use DTOs for request and response, but for this approach I'm not sure I can use them.
The second one I found it's using Data Transfer Objects, but this approach requires an entity. According to the documentation, I should use DTOs and DataTransformers to convert DTO to an Entity. But I don't need entity, I don't need save it to db. I want just handle received DTO on my own.
The third one I guess it is using Data Providers, but I'm not sure it is suitable for my requirements.
So, the main question is which approach or best practice should I use to implement custom operation which isn't related to any entity. And it will be great use DTOs for request and response.
You are not forced to use entities. Classes that are marked with #ApiResource annotation may not be entities. Actually, if your application is smarter than basic CRUD you should avoid marking entities as ApiResource.
Since you want to use POST HTTP method (which is for creating resource items) you can do something like this.
1) Define class describing search fields and which will be your #ApiResource
<?php
// src/ApiResource/Search.php
namespace App\ApiResource;
use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Action\NotFoundAction;
use ApiPlatform\Core\Annotation\ApiProperty;
use App\Dto\SearchResult;
/**
* #ApiResource(
* itemOperations={
* "get"={
* "controller"=NotFoundAction::class,
* "read"=true,
* "output"=false,
* },
* },
* output=SearchResult::class
* )
*/
class Search
{
/**
* #var string
* #ApiProperty(identifier=true)
*/
public $from;
/** #var string */
public $to;
}
2) Define DTO that will represent the output
<?php
// src/Dto/SearchResult.php
namespace App\Dto;
class SearchResult
{
public $flights;
public $airports;
public $cities;
}
3) Create class that will inplement DataPersisterInterface for handling business logic.
It will be called by framework because you make POST request.
<?php
// src/DataPersister/SearchService.php
declare(strict_types=1);
namespace App\DataPersister;
use ApiPlatform\Core\DataPersister\DataPersisterInterface;
use App\Dto\SearchResult;
use App\ApiResource\Search;
final class SearchService implements DataPersisterInterface
{
public function supports($data): bool
{
return $data instanceof Search;
}
public function persist($data)
{
// here you have access to your request via $data
$output = new SearchResult();
$output->flights = ['a lot of json data'];
$output->airports = ['a lot of json data'];
$output->cities = ['inputData' => $data];
return $output;
}
public function remove($data)
{
// this method just need to be presented
}
}
That way you will recieve results based on request.

Zend Framework 2 - Doctrine - Iterate all entity classes [duplicate]

This is probably pretty simple, but I can't find a way to do this.
Is there any way to get a list of class names of the entities that Doctrine manages? Something like:
$entities = $doctrine->em->getEntities();
where $entities is an array with something like array('User', 'Address', 'PhoneNumber') etc...
I know this question is old, but in case someone still needs to do it (tested in Doctrine 2.4.0):
$classes = array();
$metas = $entityManager->getMetadataFactory()->getAllMetadata();
foreach ($metas as $meta) {
$classes[] = $meta->getName();
}
var_dump($classes);
Source
Another way to get the class names of all entities (with namespace) is:
$entitiesClassNames = $entityManager->getConfiguration()->getMetadataDriverImpl()->getAllClassNames();
Unfortunately not, your classes should be organized in the file structure though. Example: a project i'm working on now has all its doctrine classes in an init/classes folder.
There is no built function. But you can use marker/tagger interface to tag entity classes that belong to your application. You can then use the functions "get_declared_classes" and "is_subclass_of" find the list of entity classes.
For ex:
/**
* Provides a marker interface to identify entity classes related to the application
*/
interface MyApplicationEntity {}
/**
* #Entity
*/
class User implements MyApplicationEntity {
// Your entity class definition goes here.
}
/**
* Finds the list of entity classes. Please note that only entity classes
* that are currently loaded will be detected by this method.
* For ex: require_once('User.php'); or use User; must have been called somewhere
* within the current execution.
* #return array of entity classes.
*/
function getApplicationEntities() {
$classes = array();
foreach(get_declared_classes() as $class) {
if (is_subclass_of($class, "MyApplicationEntity")) {
$classes[] = $class;
}
}
return $classes;
}
Please note that my code sample above does not use namespaces for the sake simplicity. You will have to adjust it accordingly in your application.
That said you did't explain why you need to find the list of entity classes. Perhaps, there is a better solution for what your are trying to solve.

hasManyThrough with an intermediate pivot table

Description:
My application is structured as follows:
Property has a Many-Many relationship with Manager, and a Unit has a One-Many relationship with a Property, i.e. A manager can manage multiple properties, one property can have multiple manager accounts and one property can have multiple units.
I would like to have a HasManyThrough relationship on the manager to get all his units, so ideally it would look something like: $manager->units instead of having through loop through each property and call $property->units on it. Is this possible with the current version of laravel?
Tables:
managers:
id
properties:
id
managers_properties:
manager_id
property_id
units:
id
property_id
Eloquent currently does not have methods for chained relations, other than the hasManyThrough, that is only applicable to 2 chained hasMany relations. You should create your own implementation to fetch the related resources. The simplest way is to define an accessor on the Manager model:
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Support\Collection;
/**
* #property-read Collection|\App\Models\Property[] $properties
* #property-read Collection|\App\Models\Unit[] $units
*/
class Manager extends Model
{
public function properties(): BelongsToMany
{
return $this->belongsToMany(Property::class);
}
public function getUnitsAttribute(): Collection
{
return $this->properties
->pluck('units')
->flatten(1)
->unique('id')
->sortBy('id');
}
}
You should now be able to access the related units with $manager->units assuming $manager instanceof App\Models\Manager.
Note
Calling $manager->units does perform at most n + 1 database queries: 1 for fetching n related properties, and another n for fetching related units for each returned property. "At most" because the resources might have been loaded already because of previous calls to the accessor.
Note
Calling $manager->units returns you a Collection of Unit models, a format that's equivalent to what you'd get from the magic accessor of a to-many relationship method. However getUnitsAttribute() is not an actual relationship method (it does not return a relationship object), so it can not be treated as such, whereas Manager::properties() can be.

Querying ORM with WHERE clause - Eloquent/Laravel 4

The relevant code is at: https://gist.github.com/morganhein/9254678
I have nested resource controllers, which query similarly structured tables in a database.
If I go to www.example.com/v1/jobs/1/departments, I want to query all departments that are associated with job 1. However I cannot figure out how to do that using the ORM.
Help?
I didn't test, but I would suggest you to try something like this:
Route::resource('/v1/jobs/{id}/departments', 'DepartmentController');
Route::resource('/v1/jobs', 'JobController');
After that, your DepartmentController methods will receive one argument, which is job id in your case. It is easier to use find() method if you are using id to retrieve any specific model. When you found the model you can get access to related models.
class DepartmentsController extends BaseController {
/**
* Display a listing of the resource.
*
* #return Response
*/
public function index($jobId)
{
Clockwork::startEvent('List_Default_Departments', 'Starting.');
$positions = Auth::user()->jobs()->find($jobId)->departments;
Clockwork::endEvent('List_Default_Departments', 'Done.');
return Response::json($positions->toArray());
}
}
Note: There is a different between $job->departments() (returns Builder object to create more complex queries) and $job->departments (returns Collection object directly).
Also if you would like to get the list of jobs with all related departments you can always call:
$jobs = Auth::user()->jobs()->with('departments')->get();

Communication through events between an entity and its repository in Doctrine2

I'm starting to use Doctrine 2 in a project with a "Group" entity that can inherit from another Group, having the following schema: id | parent_id | name
Because the hierarchy can go deep, I use a linking table, "group_group", using this schema: ancestor_id | descendant_id | depth
The idea is that any group is linked to all of its ancestors and descendants, and the depth field indicates the distance of the relationship, so that I don't have to iterates through parents or children using many SQL requests, a single one can get all the results.
I tried to use Doctrine's ManyToMany relation but I could not get it to be ordered by the depth field, so instead I use the entity's repository to get the related ancestors and descendants.
Because an entity can not access its repository, I would like to know if there is a way for an entity to dispatch events that can be listened by its repository, so that when an entity tries to access its ancestors/descendants, the repository can respond?
Thanks for your help.
An Entity shouldn't have a concrete reference to a Repository but there isn't anything wrong with defining an Interface and have your Repository implement this interface and injecting it into the Entity.
Similar to this solution Doctine 2 Restricting Associations with DQL
interface TreeInterface
{
public function findParent();
public function findChildren();
}
Then your Entities.
class Group
{
$repository;
setRepository(TreeInterface $repository)
{
$this->tree = $repository;
}
public function getParent()
{
return $this->repository->findParent($this);
}
public function getChildren()
{
return $this->repository->findChildren($this);
}
}
class GroupRepository extends EntityRepository implements TreeInterface
{
public function findParent(Group $group)
{
return //stuff
}
public function findChildren(Group $group)
{
return //stuff
}
}
And you use it this way.
$group = $em->find('Group', 1);
$group->setRepository($em->getRepository('Group'));
$children = $group->getChildren();
To avoid setting the repository each time you get a child, I would take a look at the EventManager and postLoad event and see if you can inject a TreeInterface into the Entity on load.

Resources