Passed
Push — master ( ece3a4...72e2c0 )
by Jan
04:26
created

FetchJoinORMAdapter   A

Complexity

Total Complexity 14

Size/Duplication

Total Lines 105
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 45
dl 0
loc 105
rs 10
c 1
b 0
f 0
wmc 14

6 Methods

Rating   Name   Duplication   Size   Complexity  
A configureOptions() 0 14 1
A getCount() 0 4 1
A configure() 0 4 1
A prepareQuery() 0 32 5
A getResults() 0 28 5
A getSimpleTotalCount() 0 8 1
1
<?php
2
/**
3
 * This file is part of Part-DB (https://github.com/Part-DB/Part-DB-symfony).
4
 *
5
 * Copyright (C) 2019 - 2020 Jan Böhmer (https://github.com/jbtronics)
6
 *
7
 * This program is free software; you can redistribute it and/or
8
 * modify it under the terms of the GNU General Public License
9
 * as published by the Free Software Foundation; either version 2
10
 * of the License, or (at your option) any later version.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU General Public License
18
 * along with this program; if not, write to the Free Software
19
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
20
 */
21
22
namespace App\DataTables\Adapter;
23
24
use Doctrine\ORM\Query;
25
use Doctrine\ORM\QueryBuilder;
26
use Doctrine\ORM\Tools\Pagination\Paginator;
27
use Omines\DataTablesBundle\Adapter\AdapterQuery;
28
use Omines\DataTablesBundle\Adapter\Doctrine\Event\ORMAdapterQueryEvent;
29
use Omines\DataTablesBundle\Adapter\Doctrine\ORMAdapterEvents;
30
use Omines\DataTablesBundle\Column\AbstractColumn;
31
use Symfony\Component\OptionsResolver\OptionsResolver;
32
33
/**
34
 * Similar to ORMAdapter this class allows to access objects from the doctrine ORM.
35
 * Unlike the default ORMAdapter supports Fetch Joins (additional entites are fetched from DB via joins) using
36
 * the Doctrine Paginator.
37
 * @author Jan Böhmer
38
 */
39
class FetchJoinORMAdapter extends ORMAdapter
40
{
41
    protected $use_simple_total;
42
43
    public function configure(array $options)
44
    {
45
        parent::configure($options);
46
        $this->use_simple_total = $options['simple_total_query'];
47
    }
48
49
    protected function configureOptions(OptionsResolver $resolver)
50
    {
51
        parent::configureOptions($resolver);
52
53
        //Enforce object hydration mode (fetch join only works for objects)
54
        $resolver->addAllowedValues('hydrate', Query::HYDRATE_OBJECT);
55
56
        /**
57
         * Add the possibility to replace the query for total entity count through a very simple one, to improve performance.
58
         * You can only use this option, if you did not apply any criteria to your total count.
59
         */
60
        $resolver->setDefault('simple_total_query', false);
61
62
        return $resolver;
63
    }
64
65
    protected function prepareQuery(AdapterQuery $query)
66
    {
67
        $state = $query->getState();
68
        $query->set('qb', $builder = $this->createQueryBuilder($state));
69
        $query->set('rootAlias', $rootAlias = $builder->getDQLPart('from')[0]->getAlias());
70
71
        // Provide default field mappings if needed
72
        foreach ($state->getDataTable()->getColumns() as $column) {
73
            if (null === $column->getField() && isset($this->metadata->fieldMappings[$name = $column->getName()])) {
74
                $column->setOption('field', "{$rootAlias}.{$name}");
75
            }
76
        }
77
78
        /** @var Query\Expr\From $fromClause */
79
        $fromClause = $builder->getDQLPart('from')[0];
80
        $identifier = "{$fromClause->getAlias()}.{$this->metadata->getSingleIdentifierFieldName()}";
81
82
        //Use simpler (faster) total count query if the user wanted so...
83
        if ($this->use_simple_total) {
84
            $query->setTotalRows($this->getSimpleTotalCount($builder));
85
        } else {
86
            $query->setTotalRows($this->getCount($builder, $identifier));
87
        }
88
89
        // Get record count after filtering
90
        $this->buildCriteria($builder, $state);
91
        $query->setFilteredRows($this->getCount($builder, $identifier));
92
93
        // Perform mapping of all referred fields and implied fields
94
        $aliases = $this->getAliases($query);
95
        $query->set('aliases', $aliases);
96
        $query->setIdentifierPropertyPath($this->mapFieldToPropertyPath($identifier, $aliases));
97
    }
98
99
    public function getResults(AdapterQuery $query): \Traversable
100
    {
101
        $builder = $query->get('qb');
102
        $state = $query->getState();
103
104
        // Apply definitive view state for current 'page' of the table
105
        foreach ($state->getOrderBy() as list($column, $direction)) {
106
            /** @var AbstractColumn $column */
107
            if ($column->isOrderable()) {
108
                $builder->addOrderBy($column->getOrderField(), $direction);
109
            }
110
        }
111
        if ($state->getLength() > 0) {
112
            $builder
113
                ->setFirstResult($state->getStart())
114
                ->setMaxResults($state->getLength());
115
        }
116
117
        $query = $builder->getQuery();
118
        $event = new ORMAdapterQueryEvent($query);
119
        $state->getDataTable()->getEventDispatcher()->dispatch($event, ORMAdapterEvents::PRE_QUERY);
0 ignored issues
show
Unused Code introduced by
The call to Symfony\Contracts\EventD...erInterface::dispatch() has too many arguments starting with Omines\DataTablesBundle\...dapterEvents::PRE_QUERY. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

119
        $state->getDataTable()->getEventDispatcher()->/** @scrutinizer ignore-call */ dispatch($event, ORMAdapterEvents::PRE_QUERY);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
120
121
        //Use Doctrine paginator for result iteration
122
        $paginator = new Paginator($query);
123
124
        foreach ($paginator->getIterator() as $result) {
125
            yield $result;
126
            $this->manager->detach($result);
0 ignored issues
show
Deprecated Code introduced by
The function Doctrine\ORM\EntityManager::detach() has been deprecated: 2.7 This method is being removed from the ORM and won't have any replacement ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

126
            /** @scrutinizer ignore-deprecated */ $this->manager->detach($result);

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
127
        }
128
    }
129
130
    public function getCount(QueryBuilder $queryBuilder, $identifier)
131
    {
132
        $paginator = new Paginator($queryBuilder);
133
        return $paginator->count();
134
    }
135
136
    protected function getSimpleTotalCount(QueryBuilder $queryBuilder)
137
    {
138
        /** The paginator count queries can be rather slow, so when query for total count (100ms or longer),
139
         * just return the entity count.
140
         */
141
        /** @var Query\Expr\From $from_expr */
142
        $from_expr = $queryBuilder->getDQLPart('from')[0];
143
        return $this->manager->getRepository($from_expr->getFrom())->count([]);
144
    }
145
}