Completed
Push — master ( be1dc2...d56e9e )
by Vitaly
06:38
created

Database::insert()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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