Completed
Push — master ( b7ef6a...58a002 )
by Kirill
03:12
created

DatabaseProcessor::applicator()   A

Complexity

Conditions 3
Paths 1

Size

Total Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 6.7968

Importance

Changes 0
Metric Value
dl 0
loc 21
ccs 2
cts 8
cp 0.25
rs 9.584
c 0
b 0
f 0
cc 3
nc 1
nop 1
crap 6.7968
1
<?php
2
/**
3
 * This file is part of Hydrogen package.
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 */
8
declare(strict_types=1);
9
10
namespace RDS\Hydrogen\Processor;
11
12
use Doctrine\ORM\QueryBuilder;
13
use RDS\Hydrogen\Criteria;
14
use RDS\Hydrogen\Criteria\Common\Field;
15
use RDS\Hydrogen\Criteria\CriterionInterface;
16
use RDS\Hydrogen\Query;
17
18
/**
19
 * Class DatabaseProcessor
20
 */
21
class DatabaseProcessor extends Processor
22
{
23
    /**
24
     * @var string[]|BuilderInterface[]
25
     */
26
    protected const CRITERIA_MAPPINGS = [
27
        Criteria\GroupBy::class     => DatabaseProcessor\GroupByBuilder::class,
28
        Criteria\Having::class      => DatabaseProcessor\HavingBuilder::class,
29
        Criteria\HavingGroup::class => DatabaseProcessor\HavingGroupBuilder::class,
30
        Criteria\Join::class        => DatabaseProcessor\JoinBuilder::class,
31
        Criteria\Limit::class       => DatabaseProcessor\LimitBuilder::class,
32
        Criteria\Offset::class      => DatabaseProcessor\OffsetBuilder::class,
33
        Criteria\OrderBy::class     => DatabaseProcessor\OrderByBuilder::class,
34
        Criteria\Relation::class    => DatabaseProcessor\RelationBuilder::class,
35
        Criteria\Selection::class   => DatabaseProcessor\SelectBuilder::class,
36
        Criteria\Where::class       => DatabaseProcessor\WhereBuilder::class,
37
        Criteria\WhereGroup::class  => DatabaseProcessor\GroupBuilder::class,
38
    ];
39
40
    /**
41
     * @param Query $query
42
     * @param string $field
43
     * @return mixed
44
     */
45 10
    public function getScalarResult(Query $query, string $field)
46
    {
47 10
        $query->from($this->repository);
48
49
        /** @var QueryBuilder $builder */
50 10
        [$deferred, $builder] = $this->await($this->createQueryBuilder($query));
0 ignored issues
show
Bug introduced by
The variable $deferred does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $builder does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
51
52 10
        return $builder->getQuery()->getSingleScalarResult();
53
    }
54
55
    /**
56
     * @param Query $query
57
     * @return string
58
     */
59
    public function dump(Query $query): string
60
    {
61
        $query->from($this->repository);
62
63
        /** @var QueryBuilder $builder */
64
        [$deferred, $builder] = $this->await($this->createQueryBuilder($query));
0 ignored issues
show
Bug introduced by
The variable $deferred does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $builder does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
65
66
        return $builder->getQuery()->getDQL();
67
    }
68
69
    /**
70
     * @param Query $query
71
     * @return \Generator
72
     */
73 30
    protected function createQueryBuilder(Query $query): \Generator
74
    {
75 30
        $builder = $this->em->createQueryBuilder();
76 30
        $builder->from($query->getRepository()->getClassName(), $query->getAlias());
0 ignored issues
show
Bug introduced by
The method getClassName does only exist in Doctrine\Common\Persistence\ObjectRepository, but not in RDS\Hydrogen\Hydrogen.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
77 30
        $builder->setCacheable(false);
78
79 30
        return $this->fillQueryBuilder($builder, $query);
80
    }
81
82
    /**
83
     * @param QueryBuilder $builder
84
     * @param Query $query
85
     * @return \Generator
86
     */
87 30
    protected function fillQueryBuilder(QueryBuilder $builder, Query $query): \Generator
88
    {
89
        /**
90
         * @var \Generator $context
91
         * @var CriterionInterface $criterion
92
         */
93 30
        foreach ($this->bypass($builder, $query) as $criterion => $context) {
94 30
            while ($context->valid()) {
95 30
                [$key, $value] = [$context->key(), $context->current()];
0 ignored issues
show
Bug introduced by
The variable $key does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $value does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
96
97
                switch (true) {
98 30
                    case $key instanceof Field:
99 8
                        $context->send($placeholder = $query->createPlaceholder($key->toString()));
100 8
                        $builder->setParameter($placeholder, $value);
101 8
                        continue 2;
102
103 30
                    case $value instanceof Field:
104 30
                        $context->send($value->toString($criterion->getQueryAlias()));
0 ignored issues
show
Bug introduced by
The method getQueryAlias cannot be called on $criterion (of type integer|string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
105 30
                        continue 2;
106
107
                    case $value instanceof Query:
108
                        $context->send($query->attach($value));
109
                        continue 2;
110
111
                    default:
112
                        $result = (yield $key => $value);
113
114
                        if ($result === null) {
115
                            $stmt = \is_object($value) ? \get_class($value) : \gettype($value);
116
                            $error = 'Unrecognized coroutine\'s return statement: ' . $stmt;
117
                            $context->throw(new \InvalidArgumentException($error));
118
                        }
119
120
                        $context->send($result);
121
                }
122
            }
123
        }
124
125 30
        return $builder;
126
    }
127
128
    /**
129
     * @param Query $query
130
     * @param string ...$fields
131
     * @return iterable
132
     */
133 20
    public function getResult(Query $query, string ...$fields): iterable
134
    {
135 20
        $query->from($this->repository);
136
137 20
        if (! $query->has(Criteria\Selection::class)) {
138 20
            $query->select(':' . $query->getAlias());
139
        }
140
141
        /**
142
         * @var QueryBuilder $builder
143
         * @var Queue $deferred
144
         */
145 20
        [$deferred, $builder] = $this->await($this->createQueryBuilder($query));
0 ignored issues
show
Bug introduced by
The variable $deferred does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
Bug introduced by
The variable $builder does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
146
147 20
        return \count($fields) > 0
0 ignored issues
show
Bug Best Practice introduced by
The return type of return \count($fields) >...a($builder, $deferred); (array) is incompatible with the return type declared by the interface RDS\Hydrogen\Processor\P...sorInterface::getResult of type RDS\Hydrogen\Processor\iterable.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
148 2
            ? $this->executeFetchFields($builder, $fields)
149 20
            : $this->executeFetchData($builder, $deferred);
150
    }
151
152
    /**
153
     * @param QueryBuilder $builder
154
     * @param array $fields
155
     * @return array
156
     */
157 2
    private function executeFetchFields(QueryBuilder $builder, array $fields): array
158
    {
159 2
        $result = [];
160
161 2
        foreach ($builder->getQuery()->getArrayResult() as $record) {
162 2
            $result[] = \array_merge(\array_only($record, $fields), \array_only($record[0] ?? [], $fields));
163
        }
164
165 2
        return $result;
166
    }
167
168
    /**
169
     * @param QueryBuilder $builder
170
     * @param Queue $deferred
171
     * @return array
172
     */
173 20
    private function executeFetchData(QueryBuilder $builder, Queue $deferred): array
174
    {
175 20
        $query = $builder->getQuery();
176
177 20
        $deferred->invoke($result = $query->getResult());
178
179 20
        return $result;
180
    }
181
}
182