omines / datatables-bundle

DataTables bundle for Symfony
https://omines.github.io/datatables-bundle/
MIT License
255 stars 114 forks source link

Add an ORM adapter which allows fetch joins #121

Closed jbtronics closed 4 years ago

jbtronics commented 4 years ago

The default ORM adapter does not allow queries where doctrine performs fetch joins (selecting additional entities via joins), because the normal result limit statements do not work there (it causes strange bugs like pages with only one row).

However doctrine offers the Pagiator class which can correctly determine page counts and elements for each page.

I wrote an adapter using doctrine paginator (extending the basic ORMAdapter) and because using fetch joins in more complex queries (to avoid the N+1 query problem) , I propose this adapter for the main repository.

I think a separate adapter is reasonable for this case, because the FetchJoinAdapter is slower than the normal adapter (because it have to perform 3 queries to determine the correct results) and it only supports object hydration mode, so a developer should explicitly choose to use this adapter.

This PR should also solve issue #93 .

rwkt commented 4 years ago

Are you able to post a basic example of it's usage? I've been trying to get this to work with the code below but I'm not having any luck; this is for a OneToMany relation (one customer has many phones - but only display one phone as the main one).

$table = $dataTableFactory->create()
    ->add('mainPhone', TextColumn::class, ['field' => 'p.value'])
    ->createAdapter(FetchJoinORMAdapter::class, [
        'entity' => Customer::class,
        'query' => function (QueryBuilder $builder) {
            $builder
                ->distinct()
                ->select('c, p')
                ->from(Customer::class, 'c')
                ->leftJoin('c.phones', 'p', 'WITH', 'p.isMain = 1 AND p.category = 0')
                ;
        },
    ])
    ->handleRequest($request);
**App\Entity\Customer**
     /**
     * @ORM\OneToMany(targetEntity="App\Entity\Phone", mappedBy="customer", cascade={"persist"})
     */
    private $phones;
jbtronics commented 4 years ago

Are you able to post a basic example of it's usage? I've been trying to get this to work with the code below but I'm not having any luck; this is for a OneToMany relation (one customer has many phones - but only display one phone as the main one).

The adapter just allow to fetch multiple objects at once. If you want to show it somehow, you have to implement your own logic, that transform the objects somehow to a field. (Maybe it would be possible to add a new column for that).

Taking your example code above, you would change the TextColoumnLine to something like this: ->add('mainPhone', TextColumn::class, [ 'render' => 'render' => function ($value, $context) { $str = ''; foreach ($context->getPhones() as $phone) { $str .= $phone->getName() . '\n'; } return $str; } ]);

As the phones objects are already populated with data, this will not cause any more database queries (unlike when using the default ORMAdapter).

curry684 commented 4 years ago

OK :)