Passed
Pull Request — 2.0 (#56)
by Vincent
16:03 queued 09:35
created

OrmPreprocessor::table()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 15
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4

Importance

Changes 0
Metric Value
eloc 7
dl 0
loc 15
ccs 8
cts 8
cp 1
rs 10
c 0
b 0
f 0
cc 4
nc 5
nop 1
crap 4
1
<?php
2
3
namespace Bdf\Prime\Query\Compiler\Preprocessor;
4
5
use Bdf\Prime\Mapper\Metadata;
6
use Bdf\Prime\Platform\PlatformInterface;
7
use Bdf\Prime\Query\CompilableClause;
8
use Bdf\Prime\Query\Compiler\AliasResolver\AliasResolver;
9
use Bdf\Prime\Query\Contract\EntityJoinable;
10
use Bdf\Prime\Query\Expression\ExpressionInterface;
11
use Bdf\Prime\Query\Expression\ExpressionTransformerInterface;
12
use Bdf\Prime\Query\Expression\TypedExpressionInterface;
13
use Bdf\Prime\Query\QueryInterface;
14
use Bdf\Prime\Repository\RepositoryInterface;
15
use Bdf\Prime\Types\TypeInterface;
16
use LogicException;
17
18
/**
19
 * Preprocessor for Orm operation (i.e. with EntityRepository and Alias resolver)
20
 */
21
class OrmPreprocessor implements PreprocessorInterface
22
{
23
    /**
24
     * @var AliasResolver
25
     */
26
    protected $aliasResolver;
27
28
    /**
29
     * @var Metadata
30
     */
31
    protected $metadata;
32
33
    /**
34
     * The query repository
35
     *
36
     * @var RepositoryInterface
37
     * @internal
38
     */
39
    private $repository;
40
41
    /**
42
     * @var string
43
     */
44
    protected $type;
45
46
    /**
47
     * @var PlatformInterface
48
     */
49
    protected $platform;
50
51
52
    /**
53
     * OrmPreprocessor constructor.
54
     *
55
     * @param RepositoryInterface $repository
56
     * @throws \Bdf\Prime\Exception\PrimeException
57
     */
58 746
    public function __construct(RepositoryInterface $repository)
59
    {
60 746
        $this->repository = $repository;
61 746
        $this->metadata = $repository->metadata();
62 746
        $this->platform = $repository->connection()->platform();
63
    }
64
65
    /**
66
     * {@inheritdoc}
67
     */
68 347
    public function forInsert(CompilableClause $clause)
69
    {
70 347
        if ($this->repository->isReadOnly()) {
71
            throw new LogicException('Repository "'.$this->metadata->entityName.'" is read only. Cannot execute write query');
72
        }
73
74 347
        $this->type = 'insert';
75
76 347
        return $clause;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $clause returns the type Bdf\Prime\Query\CompilableClause which is incompatible with the return type mandated by Bdf\Prime\Query\Compiler...rInterface::forInsert() of Bdf\Prime\Query\Compiler\Preprocessor\Q.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
77
    }
78
79
    /**
80
     * {@inheritdoc}
81
     */
82 76
    public function forUpdate(CompilableClause $clause)
83
    {
84 76
        if ($this->repository->isReadOnly()) {
85
            throw new LogicException('Repository "'.$this->metadata->entityName.'" is read only. Cannot execute write query');
86
        }
87
88 76
        $this->type = 'update';
89 76
        $toCompile = clone $clause;
90 76
        $toCompile->where($this->repository->constraints());
0 ignored issues
show
Bug introduced by
The method where() does not exist on Bdf\Prime\Query\CompilableClause. It seems like you code against a sub-type of Bdf\Prime\Query\CompilableClause such as Bdf\Prime\Query\AbstractReadCommand. ( Ignorable by Annotation )

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

90
        $toCompile->/** @scrutinizer ignore-call */ 
91
                    where($this->repository->constraints());
Loading history...
91
92 76
        return $toCompile;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $toCompile returns the type Bdf\Prime\Query\CompilableClause which is incompatible with the return type mandated by Bdf\Prime\Query\Compiler...rInterface::forUpdate() of Bdf\Prime\Query\Compiler\Preprocessor\Q.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
93
    }
94
95
    /**
96
     * {@inheritdoc}
97
     */
98 56
    public function forDelete(CompilableClause $clause)
99
    {
100 56
        if ($this->repository->isReadOnly()) {
101
            throw new LogicException('Repository "'.$this->metadata->entityName.'" is read only. Cannot execute write query');
102
        }
103
104 56
        $this->type = 'delete';
105 56
        $toCompile = clone $clause;
106 56
        $toCompile->where($this->repository->constraints());
107
108 56
        return $toCompile;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $toCompile returns the type Bdf\Prime\Query\CompilableClause which is incompatible with the return type mandated by Bdf\Prime\Query\Compiler...rInterface::forDelete() of Bdf\Prime\Query\Compiler\Preprocessor\Q.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
109
    }
110
111
    /**
112
     * {@inheritdoc}
113
     */
114 608
    public function forSelect(CompilableClause $clause)
115
    {
116 608
        $this->type = 'select';
117
118 608
        if ($clause instanceof EntityJoinable && $clause instanceof QueryInterface) {
119 559
            $compilerQuery = clone $clause;
120
121 559
            if (!$this->aliasResolver) {
122 559
                $needReset = false;
123 559
                $this->aliasResolver = new AliasResolver($this->repository, $this->platform->types());
124
            } else {
125 25
                $needReset = true;
126
            }
127
128 559
            $this->aliasResolver->setQuery($compilerQuery);
129
130 559
            if ($clause->state()->needsCompile('from')) {
131 559
                if ($needReset) {
132 2
                    $this->aliasResolver->reset();
133
                }
134
135 559
                foreach ($compilerQuery->statements['tables'] as &$table) {
136 556
                    $table['alias'] = $this->aliasResolver->registerMetadata($table['table'], $table['alias']);
137
                }
138
            }
139
140 559
            if ($clause->state()->needsCompile('joins')) {
141 559
                foreach ($compilerQuery->statements['joins'] as &$join) {
142 24
                    $join['alias'] = $this->aliasResolver->registerMetadata($join['table'], $join['alias']);
143
                }
144
            }
145
146 559
            return $compilerQuery;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $compilerQuery returns the type Bdf\Prime\Query\Compilab...me\Query\QueryInterface which is incompatible with the return type mandated by Bdf\Prime\Query\Compiler...rInterface::forSelect() of Bdf\Prime\Query\Compiler\Preprocessor\Q.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
147
        } else {
148 212
            return $clause;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $clause returns the type Bdf\Prime\Query\CompilableClause which is incompatible with the return type mandated by Bdf\Prime\Query\Compiler...rInterface::forSelect() of Bdf\Prime\Query\Compiler\Preprocessor\Q.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
149
        }
150
    }
151
152
    /**
153
     * {@inheritdoc}
154
     */
155 631
    public function field(string $attribute, &$type = null): string
156
    {
157 631
        if ($this->type === 'select' && $this->aliasResolver !== null) {
158 462
            return $this->aliasResolver->resolve($attribute, $type);
159
        }
160
161 438
        return $this->fieldForWriteQuery($attribute, $type);
162
    }
163
164
    /**
165
     * Get formatted field from attribute alias
166
     *
167
     * @param string $attribute
168
     * @param null|true $type
169
     *
170
     * @return string
171
     */
172 438
    protected function fieldForWriteQuery($attribute, &$type = null)
173
    {
174
        // @fixme Throw exception if wants to write on undefined attribute ?
175 438
        if (!isset($this->metadata->attributes[$attribute])) {
176 2
            return $attribute;
177
        }
178
179 438
        $meta = $this->metadata->attributes[$attribute];
180
181 438
        if ($type === true) {
182 431
            $type = $this->platform->types()->get($meta['type']);
183
        }
184
185 438
        return $meta['field'];
186
    }
187
188
    /**
189
     * {@inheritdoc}
190
     *
191
     * Perform conversion on value, according to the field type and the value
192
     *
193
     * Example :
194
     * $query->where('roles', ':in', [[5, 2], [3]]); => IN(',5,2,', ',3,') OK
195
     * $query->where('roles', [[5, 2], [3]]);        => IN(',5,2,', ',3,') OK
196
     * $query->where('roles', [5, 2]);               => IN('5', '2')       KO
197
     * $query->where('roles', ':in', [5, 2]);        => IN('5', '2')       KO
198
     * $query->where('roles', ',5,2,');              => = ',5,2,'          OK
199
     *
200
     * To ensure that the expression is converted as needed, use Expressions instead of plain value
201
     */
202 457
    public function expression(array $expression): array
203
    {
204 457
        if (isset($expression['column'])) {
205 451
            $type = true;
206
207
            /** @var TypeInterface $type */
208 451
            $expression['column'] = $this->field($expression['column'], $type);
209
210 451
            if ($type !== true) {
211 449
                $value = $expression['value'];
212
213 449
                if ($value instanceof TypedExpressionInterface) {
214 4
                    $value->setType($type);
215 446
                } elseif (is_array($value)) {
216
                    /* The value is an array :
217
                     * - Will result to "into" expression => convert each elements
218
                     * - Will result to "array" expression (i.e. IN, BETWEEN...) => convert each elements
219
                     * - Will result to "equal" expression => converted to IN => convert each elements
220
                     */
221 179
                    foreach ($value as &$v) {
222 179
                        $v = $this->tryConvertValue($v, $type);
223
                    }
224
                } else {
225 378
                    $value = $this->tryConvertValue($value, $type);
226
                }
227
228 449
                $expression['value'] = $value;
229 449
                $expression['converted'] = true;
230
            }
231
        }
232
233 457
        return $expression;
234
    }
235
236
    /**
237
     * {@inheritdoc}
238
     */
239 557
    public function table(array $table): array
240
    {
241 557
        if ($this->aliasResolver === null) {
242 1
            return $table;
243
        }
244
245 556
        if (!$this->aliasResolver->hasAlias($table['alias'])) {
246 4
            $table['alias'] = $this->aliasResolver->registerMetadata($table['table'], $table['alias']);
247
        }
248
249 556
        if ($this->aliasResolver->hasAlias($table['alias'])) {
250 556
            $table['table'] = $this->aliasResolver->getMetadata($table['alias'])->table;
251
        }
252
253 556
        return $table;
254
    }
255
256
    /**
257
     * {@inheritdoc}
258
     */
259 462
    public function root(): ?string
260
    {
261 462
        if ($this->aliasResolver) {
262 461
            return $this->aliasResolver->getPathAlias();
263
        }
264
265 1
        return null;
266
    }
267
268
    /**
269
     * {@inheritdoc}
270
     */
271 690
    public function clear(): void
272
    {
273 690
        if ($this->aliasResolver !== null) {
274 554
            $this->aliasResolver->setQuery(null);
275
        }
276
    }
277
278
    /**
279
     * Try to convert the value to DB value
280
     *
281
     * @param mixed $value
282
     * @param TypeInterface $type
283
     *
284
     * @return mixed
285
     */
286 435
    protected function tryConvertValue($value, TypeInterface $type)
287
    {
288
        if (
289
            $value instanceof QueryInterface
290
            || $value instanceof ExpressionInterface
291
            || $value instanceof ExpressionTransformerInterface
292
        ) {
293 103
            return $value;
294
        }
295
296 435
        return $type->toDatabase($value);
297
    }
298
}
299