Database::createEntities()   B
last analyzed

Complexity

Conditions 5
Paths 5

Size

Total Lines 36
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 15
c 0
b 0
f 0
nc 5
nop 3
dl 0
loc 36
rs 8.439
1
<?php declare(strict_types=1);
2
/**
3
 * Created by PhpStorm.
4
 * User: egorov
5
 * Date: 09.05.2015
6
 * Time: 13:05
7
 */
8
namespace samsonframework\orm;
9
10
use samsonframework\container\definition\analyzer\annotation\annotation\InjectClass;
11
use samsonframework\container\definition\analyzer\annotation\annotation\Service;
12
13
/**
14
 * Database management class.
15
 *
16
 * @package samsonframework\orm
17
 * @Service("database")
18
 */
19
class Database implements DatabaseInterface
20
{
21
    /** Table name prefix */
22
    public static $prefix = '';
23
24
    /** @var \PDO Database driver */
25
    protected $driver;
26
27
    /** @var  SQLBuilder */
28
    protected $sqlBuilder;
29
30
    /**
31
     * Database constructor.
32
     *
33
     * @param \PDO $driver
34
     *
35
     * @InjectClass(driver="\PDO")
36
     * @InjectClass(sqlBuilder="\samsonframework\orm\SQLBuilder")
37
     */
38
    public function __construct(\PDO $driver, SQLBuilder $sqlBuilder)
39
    {
40
        $this->driver = $driver;
41
        $this->sqlBuilder = $sqlBuilder;
42
43
        // Set correct encodings
44
        $this->execute("set character_set_client='utf8'");
45
        $this->execute("set character_set_results='utf8'");
46
        $this->execute("set collation_connection='utf8_general_ci'");
47
    }
48
49
    /**
50
     * {@inheritdoc}
51
     */
52
    public function execute(string $sql)
53
    {
54
        // Perform database query
55
        return $this->driver->prepare($sql)->execute();
56
    }
57
58
    /**
59
     * {@inheritdoc}
60
     */
61
    public function insert(TableMetadata $tableMetadata, array $columnValues)
62
    {
63
        $this->execute($this->sqlBuilder->buildInsertStatement($tableMetadata, $columnValues));
64
65
        return $this->driver->lastInsertId();
66
    }
67
68
    /**
69
     * {@inheritdoc}
70
     */
71
    public function update(TableMetadata $tableMetadata, array $columnValues, Condition $condition = null)
72
    {
73
        return $this->execute($this->sqlBuilder->buildUpdateStatement($tableMetadata, $columnValues, $condition));
74
    }
75
76
    /**
77
     * Execute SQL query.
78
     *
79
     * @param string $sql SQL statement
80
     *
81
     * @deprecated Use self::execute()
82
     * @return mixed Driver result
83
     */
84
    public function fetch(string $sql)
85
    {
86
        return $this->fetchArray($sql);
87
    }
88
89
    /**
90
     * {@inheritdoc}
91
     */
92
    public function fetchArray(string $sql) : array
93
    {
94
        return $this->driver->query($sql)->fetchAll(\PDO::FETCH_ASSOC);
95
    }
96
97
    /**
98
     * {@inheritdoc}
99
     */
100
    public function database() : string
101
    {
102
        return $this->driver->query('select database()')->fetchColumn();
103
    }
104
105
    /**
106
     * {@inheritdoc}
107
     */
108
    public function count(string $sql) : int
109
    {
110
        // Modify query SQL and add counting
111
        $result = $this->fetchArray('SELECT Count(*) as __Count FROM (' . $sql . ') as __table');
112
113
        return array_key_exists(0, $result) ? (int)$result[0]['__Count'] : 0;
114
    }
115
116
    /**
117
     * {@inheritdoc}
118
     * @throws \InvalidArgumentException
119
     */
120
    public function fetchObjects(string $sql, TableMetadata $metadata) : array
121
    {
122
        $grouped = [];
123
        foreach ($this->driver->query($sql)->fetchAll(\PDO::FETCH_ASSOC) as $row) {
124
            $instance = new $metadata->className();
125
            $this->fillEntityFieldValues($instance, $metadata, $row);
126
            $grouped[$row[$metadata->getTablePrimaryField()]] = $instance;
127
        }
128
129
        return $grouped;
130
    }
131
132
    /**
133
     * {@inheritdoc}
134
     */
135
    public function fetchColumn(string $sql, int $columnIndex) : array
136
    {
137
        return $this->driver->query($sql)->fetchAll(\PDO::FETCH_COLUMN, $columnIndex);
138
    }
139
140
    /**
141
     * {@inheritdoc}
142
     */
143
    public function fetchObjectsWithJoin(string $sql, TableMetadata $metadata, array $joinedMetadata) : array
144
    {
145
        return $this->createEntities(
146
            $this->fetchArray($sql),
147
            $metadata,
148
            $joinedMetadata
149
        );
150
    }
151
152
    /**
153
     * Create entity instances and its joined entities.
154
     *
155
     * @param array           $rows
156
     * @param TableMetadata   $metadata
157
     * @param TableMetadata[] $joinedMetadata
158
     *
159
     * @return array
160
     * @throws \InvalidArgumentException
161
     */
162
    protected function createEntities(array $rows, TableMetadata $metadata, array $joinedMetadata = [])
163
    {
164
        $objects = [];
165
166
        /** @var array $entityRows Iterate entity rows */
167
        foreach ($this->groupResults($rows, $metadata->primaryField) as $primaryValue => $entityRows) {
168
            // Create entity instance
169
            $instance = $objects[$primaryValue] = new $metadata->className($this, $metadata);
170
171
            // TODO: $attributes argument should be filled with selected fields?
172
            $this->fillEntityFieldValues($instance, $metadata, $entityRows[0]);
173
174
            // Iterate inner rows for nested entities creation
175
            foreach ($entityRows as $row) {
176
                // Iterate all joined entities
177
                foreach ($joinedMetadata as $joinMetadata) {
178
                    if (array_key_exists($joinMetadata->primaryField, $row)) {
179
                        // Create joined instance and add to parent instance
180
                        $joinedInstance = new $joinMetadata->className($this, $joinMetadata);
181
182
                        // TODO: We need to change value retrieval
183
                        $this->fillEntityFieldValues($joinedInstance, $joinMetadata, $row);
184
185
                        // Store joined instance by primary field value
186
                        $instance->joined[$joinMetadata->className][$row[$joinMetadata->primaryField]] = $joinedInstance;
187
                    } else {
188
                        throw new \InvalidArgumentException(
189
                            'Cannot join ' . $joinMetadata->className . ' - primary field ' . $joinMetadata->primaryField . ' not found'
190
                        );
191
                    }
192
                }
193
            }
194
        }
195
196
        return $objects;
197
    }
198
199
    /**
200
     * Regroup database rows by primary field value.
201
     *
202
     * @param array  $rows Collection of records received from database
203
     * @param string $primaryField Primary field name for grouping
204
     *
205
     * @return array Grouped rows by primary field value
206
     */
207
    protected function groupResults(array $rows, string $primaryField) : array
208
    {
209
        /** @var array $grouped Collection of database rows grouped by primary field value */
210
        $grouped = [];
211
212
        // Iterate result set
213
        for ($i = 0, $rowsCount = count($rows); $i < $rowsCount; $i++) {
214
            $row = $rows[$i];
215
216
            // Group by primary field value
217
            $grouped[$row[$primaryField]][] = $row;
218
        }
219
220
        return $grouped;
221
    }
222
223
    /**
224
     * Fill entity instance fields from row column values according to entity value attributes.
225
     *
226
     * @param mixed $instance Entity instance
227
     * @param TableMetadata $metadata
228
     * @param array $row Database results row
229
     */
230
    protected function fillEntityFieldValues($instance, TableMetadata $metadata, array $row)
231
    {
232
        foreach ($row as $columnName => $columnValue) {
233
            // If database row has aliased field column
234
            if ($metadata->isColumnExists($columnName)) {
235
                $columnName = $metadata->getTableColumnAlias($columnName);
236
                // Store attribute value
237
                $instance->$columnName = $columnValue;
238
            }
239
        }
240
241
        // FIXME: Old activerecord pattern behavior
242
        $instance->id = (int)$row[$metadata->primaryField];
243
        // FIXME: Old activerecord pattern behavior
244
        $instance->attached = true;
245
246
        // Call handler for object filling
247
        $instance->filled();
248
    }
249
250
    /**
251
     * Quote variable for security reasons.
252
     *
253
     * @param string $value
254
     *
255
     * @return string Quoted value
256
     */
257
    protected function quote($value)
258
    {
259
        return $this->driver->quote($value);
260
    }
261
}
262