Builder::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 16
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 16
c 0
b 0
f 0
rs 9.4285
cc 1
eloc 12
nc 1
nop 2
1
<?php
2
namespace Netdudes\DataSourceryBundle\DataSource\Driver\Doctrine\QueryBuilder;
3
4
use Doctrine\ORM\EntityManager;
5
use Doctrine\ORM\QueryBuilder;
6
use Netdudes\DataSourceryBundle\DataSource\DataSourceInterface;
7
use Netdudes\DataSourceryBundle\DataSource\Driver\Doctrine\DoctrineDriver;
8
use Netdudes\DataSourceryBundle\DataSource\Driver\Doctrine\Events\GenerateJoinsEvent;
9
use Netdudes\DataSourceryBundle\DataSource\Driver\Doctrine\Events\GenerateSelectsEvent;
10
use Netdudes\DataSourceryBundle\DataSource\Driver\Doctrine\Events\PostGenerateQueryBuilderEvent;
11
use Netdudes\DataSourceryBundle\Query\Query;
12
use Netdudes\DataSourceryBundle\Query\SearchTextFieldHandler;
13
use Netdudes\DataSourceryBundle\Query\SearchTextFilterConditionTransformer;
14
15
class Builder
16
{
17
    /**
18
     * @var Filterer
19
     */
20
    protected $filterer;
21
22
    /**
23
     * @var Sorter
24
     */
25
    protected $sorter;
26
27
    /**
28
     * @var Paginator
29
     */
30
    protected $paginator;
31
32
    /**
33
     * @var string
34
     */
35
    protected $fromAlias;
36
37
    /**
38
     * @var array
39
     */
40
    protected $joins;
41
42
    /**
43
     * @var array
44
     */
45
    protected $selectFieldsMap = [];
46
47
    /**
48
     * @var RequiredFieldsExtractor
49
     */
50
    protected $requiredFieldsExtractor;
51
52
    /**
53
     * @var JoinGenerator
54
     */
55
    protected $joinGenerator;
56
57
    /**
58
     * @var SelectGenerator
59
     */
60
    protected $selectGenerator;
61
62
    /**
63
     * @var SearchTextFieldHandler
64
     */
65
    protected $searchTextFieldHandler;
66
67
    /**
68
     * @var EntityManager
69
     */
70
    private $entityManager;
71
72
    /**
73
     * @var DataSourceInterface
74
     */
75
    private $dataSource;
76
77
    /**
78
     * @param DataSourceInterface $dataSource
79
     * @param EntityManager       $entityManager
80
     */
81
    public function __construct(DataSourceInterface $dataSource, EntityManager $entityManager)
0 ignored issues
show
Bug introduced by
You have injected the EntityManager via parameter $entityManager. This is generally not recommended as it might get closed and become unusable. Instead, it is recommended to inject the ManagerRegistry and retrieve the EntityManager via getManager() each time you need it.

The EntityManager might become unusable for example if a transaction is rolled back and it gets closed. Let’s assume that somewhere in your application, or in a third-party library, there is code such as the following:

function someFunction(ManagerRegistry $registry) {
    $em = $registry->getManager();
    $em->getConnection()->beginTransaction();
    try {
        // Do something.
        $em->getConnection()->commit();
    } catch (\Exception $ex) {
        $em->getConnection()->rollback();
        $em->close();

        throw $ex;
    }
}

If that code throws an exception and the EntityManager is closed. Any other code which depends on the same instance of the EntityManager during this request will fail.

On the other hand, if you instead inject the ManagerRegistry, the getManager() method guarantees that you will always get a usable manager instance.

Loading history...
82
    {
83
        $this->dataSource = $dataSource;
84
        $this->entityManager = $entityManager;
85
86
        $fields = $dataSource->getFields();
87
        $transformers = $dataSource->getTransformers();
88
89
        $this->requiredFieldsExtractor = new RequiredFieldsExtractor($fields, $transformers);
90
        $this->joinGenerator = new JoinGenerator($fields, $this->getFromAlias(), $this->requiredFieldsExtractor);
91
        $this->selectGenerator = new SelectGenerator($fields, $this->getFromAlias(), $this->joinGenerator, $this->requiredFieldsExtractor);
92
        $this->filterer = new Filterer();
93
        $this->searchTextFieldHandler = new SearchTextFieldHandler(new SearchTextFilterConditionTransformer());
94
        $this->sorter = new Sorter();
95
        $this->paginator = new Paginator();
96
    }
97
98
    /**
99
     * Gets the fully generated query builder. Will autogenerate select and
100
     * join statements as needed.
101
     *
102
     * This function is cached, and will only be generated once per execution.
103
     *
104
     * @param Query $query
105
     *
106
     * @return QueryBuilder
107
     */
108
    public function buildQueryBuilder(Query $query, $entityClass)
109
    {
110
        $this->searchTextFieldHandler->handle($query->getFilter(), $this->dataSource->getFields());
111
112
        $queryBuilder = $this->entityManager->createQueryBuilder();
113
        $queryBuilder->from($entityClass, $this->getFromAlias());
114
115
        $select = $this->selectGenerator->generate($query);
116
        $event = new GenerateSelectsEvent($select, $this->getFromAlias());
0 ignored issues
show
Bug introduced by
It seems like $select defined by $this->selectGenerator->generate($query) on line 115 can be null; however, Netdudes\DataSourceryBun...ctsEvent::__construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
117
        $this->dataSource->getEventDispatcher()->dispatch(DoctrineDriver::EVENT_GENERATE_SELECTS, $event);
118
        $select = $event->select;
119
        $queryBuilder->add('select', $select);
120
121
        $joins = $this->joinGenerator->generate($query);
122
        $event = new GenerateJoinsEvent($this->getFromAlias(), $joins);
123
        $this->dataSource->getEventDispatcher()->dispatch(DoctrineDriver::EVENT_GENERATE_JOINS, $event);
124
        $joins = $event->joins;
125
        foreach ($joins as $join) {
126
            $queryBuilder
127
                ->leftJoin($join->getJoin(), $join->getAlias(), $join->getConditionType(), $join->getCondition(), $join->getIndexBy());
128
        }
129
130
        $this->filterer->filter($queryBuilder, $query->getFilter(), $this->selectGenerator->getUniqueNameToSelectFieldMap($query));
131
132
        $this->sorter->sort($queryBuilder, $query->getSort(), $this->selectGenerator->getUniqueNameToSelectFieldMap($query));
133
134
        $this->paginator->paginate($queryBuilder, $query->getPagination(), $this->dataSource->getFields());
0 ignored issues
show
Unused Code introduced by
The call to Paginator::paginate() has too many arguments starting with $this->dataSource->getFields().

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.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
135
136
        $this->dataSource->getEventDispatcher()->dispatch(DoctrineDriver::EVENT_POST_GENERATE_QUERY_BUILDER, new PostGenerateQueryBuilderEvent($queryBuilder, $this->getFromAlias()));
137
138
        return $queryBuilder;
139
    }
140
141
    /**
142
     * Gets the FROM alias, an internal name given to the class in the FROM part of the DQL.
143
     *
144
     * This name is generated once, and it's unique per execution of the data source.
145
     *
146
     * @return string
147
     */
148
    protected function getFromAlias()
149
    {
150
        if (is_null($this->fromAlias)) {
151
            $this->fromAlias = uniqid('ENTITY_');
152
        }
153
154
        return $this->fromAlias;
155
    }
156
}
157