Completed
Push — master ( 0eeb04...4002ae )
by Oscar
02:31
created

Select::createRow()   B

Complexity

Conditions 5
Paths 6

Size

Total Lines 22
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 22
rs 8.6737
cc 5
eloc 12
nc 6
nop 1
1
<?php
2
3
namespace SimpleCrud\Queries\Mysql;
4
5
use SimpleCrud\SimpleCrudException;
6
use SimpleCrud\Queries\Query;
7
use SimpleCrud\Scheme\Scheme;
8
use SimpleCrud\RowCollection;
9
use SimpleCrud\Table;
10
use PDO;
11
12
/**
13
 * Manages a database select query.
14
 */
15
class Select extends Query
16
{
17
    const MODE_ONE = 1;
18
    const MODE_ALL = 2;
19
    const MODE_ARRAY = 3;
20
21
    use ExtendedSelectionTrait;
22
23
    protected $leftJoin = [];
24
    protected $orderBy = [];
25
    protected $statement;
26
    protected $mode = 2;
27
28
    /**
29
     * Change the mode to returns just the first row.
30
     * 
31
     * @return self
32
     */
33
    public function one()
34
    {
35
        $this->mode = self::MODE_ONE;
36
37
        return $this->limit(1);
38
    }
39
40
    /**
41
     * Change the mode to returns all rows.
42
     * 
43
     * @param bool $asArray
44
     * 
45
     * @return self
46
     */
47
    public function all($asArray = false)
48
    {
49
        $this->mode = $asArray ? self::MODE_ARRAY : self::MODE_ALL;
50
51
        return $this;
52
    }
53
54
    /**
55
     * {@inheritdoc}
56
     *
57
     * @return Row|RowCollection|null
58
     */
59
    public function run()
60
    {
61
        $statement = $this->__invoke();
62
63
        if ($this->mode === self::MODE_ONE) {
64
            return ($row = $statement->fetch()) === false ? null : $this->createRow($row);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return ($row = $statemen...$this->createRow($row); (array) is incompatible with the return type of the parent method SimpleCrud\Queries\Query::run of type SimpleCrud\Queries\PDOStatement.

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...
65
        }
66
67
        $result = $this->mode === self::MODE_ALL ? $this->table->createCollection() : [];
68
69
        while (($row = $statement->fetch())) {
70
            $result[] = $this->createRow($row);
71
        }
72
73
        return $result;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $result; (SimpleCrud\RowCollection|array) is incompatible with the return type of the parent method SimpleCrud\Queries\Query::run of type SimpleCrud\Queries\PDOStatement.

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...
74
    }
75
76
    /**
77
     * Create a row and insert the joined rows if exist.
78
     *
79
     * @param array $data
80
     * 
81
     * @return Row
82
     */
83
    protected function createRow(array $data)
84
    {
85
        $row = $this->table->createFromDatabase($data);
86
87
        if (empty($this->leftJoin)) {
88
            return $row;
89
        }
90
91
        foreach ($this->leftJoin as $join) {
92
            $table = $this->table->getDatabase()->{$join['table']};
93
            $values = [];
94
95
            foreach (array_keys($table->fields) as $name) {
96
                $field = sprintf('%s.%s', $join['table'], $name);
97
                $values[$name] = $data[$field];
98
            }
99
100
            $row->{$join['table']} = empty($values['id']) ? null : $table->createFromDatabase($values);
101
        }
102
103
        return $row;
104
    }
105
106
    /**
107
     * Adds an ORDER BY clause.
108
     *
109
     * @param string      $orderBy
110
     * @param string|null $direction
111
     *
112
     * @return self
113
     */
114
    public function orderBy($orderBy, $direction = null)
115
    {
116
        if (!empty($direction)) {
117
            $orderBy .= ' '.$direction;
118
        }
119
120
        $this->orderBy[] = $orderBy;
121
122
        return $this;
123
    }
124
125
    /**
126
     * Adds a LEFT JOIN clause.
127
     *
128
     * @param string     $table
129
     * @param string     $on
130
     * @param array|null $marks
131
     *
132
     * @return self
133
     */
134
    public function leftJoin($table, $on = null, $marks = null)
135
    {
136
        $scheme = $this->table->getScheme();
137
138 View Code Duplication
        if (!isset($scheme['relations'][$table])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
139
            throw new SimpleCrudException(sprintf("The tables '%s' and '%s' are not related", $this->table->name, $table));
140
        }
141
142 View Code Duplication
        if ($scheme['relations'][$table][0] !== Scheme::HAS_ONE) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
143
            throw new SimpleCrudException(sprintf("Invalid LEFT JOIN between the tables '%s' and '%s'", $this->table->name, $table));
144
        }
145
146
        $this->leftJoin[] = [
147
            'table' => $table,
148
            'on' => $on,
149
        ];
150
151
        if ($marks) {
152
            $this->marks += $marks;
153
        }
154
155
        return $this;
156
    }
157
158
    /**
159
     * {@inheritdoc}
160
     */
161 View Code Duplication
    public function __invoke()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
162
    {
163
        $statement = $this->table->getDatabase()->execute((string) $this, $this->marks);
164
        $statement->setFetchMode(PDO::FETCH_ASSOC);
165
166
        return $statement;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $statement; (PDOStatement) is incompatible with the return type declared by the abstract method SimpleCrud\Queries\Query::__invoke of type SimpleCrud\Queries\PDOStatement.

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...
167
    }
168
169
    /**
170
     * {@inheritdoc}
171
     */
172
    public function __toString()
173
    {
174
        $query = 'SELECT';
175
        $query .= ' '.static::buildFields($this->table->name, array_keys($this->table->fields));
176
177
        foreach ($this->leftJoin as $join) {
178
            $query .= ', '.static::buildFields($join['table'], array_keys($this->table->getDatabase()->{$join['table']}->fields), true);
179
        }
180
181
        $query .= $this->fieldsToString();
182
        $query .= sprintf(' FROM `%s`', $this->table->name);
183
        $query .= $this->fromToString();
184
185
        foreach ($this->leftJoin as $join) {
186
            $relation = $this->table->getScheme()['relations'][$join['table']];
187
188
            $query .= sprintf(
189
                ' LEFT JOIN `%s` ON (`%s`.`id` = `%s`.`%s`%s)',
190
                $join['table'],
191
                $join['table'],
192
                $this->table->name,
193
                $relation[1],
194
                empty($join['on']) ? '' : sprintf(' AND (%s)', $join['on'])
195
            );
196
        }
197
198
        $query .= $this->whereToString();
199
200
        if (!empty($this->orderBy)) {
201
            $query .= ' ORDER BY '.implode(', ', $this->orderBy);
202
        }
203
204
        $query .= $this->limitToString();
205
206
        return $query;
207
    }
208
209
    /**
210
     * Generates the fields/tables part of a SELECT query.
211
     *
212
     * @param string $table
213
     * @param array  $fields
214
     * @param bool   $rename
215
     *
216
     * @return string
217
     */
218
    protected static function buildFields($table, array $fields, $rename = false)
219
    {
220
        $query = [];
221
222
        foreach ($fields as $field) {
223
            if ($rename) {
224
                $query[] = "`{$table}`.`{$field}` as `{$table}.{$field}`";
225
            } else {
226
                $query[] = "`{$table}`.`{$field}`";
227
            }
228
        }
229
230
        return implode(', ', $query);
231
    }
232
}
233