Passed
Pull Request — 4.2 (#140)
by David
05:21
created
src/Mouf/Database/TDBM/InnerResultIterator.php 1 patch
Indentation   +263 added lines, -263 removed lines patch added patch discarded remove patch
@@ -29,267 +29,267 @@
 block discarded – undo
29 29
  */
30 30
 class InnerResultIterator implements \Iterator, \Countable, \ArrayAccess
31 31
 {
32
-    /**
33
-     * @var Statement
34
-     */
35
-    protected $statement;
36
-
37
-    protected $fetchStarted = false;
38
-    private $objectStorage;
39
-    private $className;
40
-
41
-    private $tdbmService;
42
-    private $magicSql;
43
-    private $parameters;
44
-    private $limit;
45
-    private $offset;
46
-    private $columnDescriptors;
47
-    private $magicQuery;
48
-
49
-    /**
50
-     * The key of the current retrieved object.
51
-     *
52
-     * @var int
53
-     */
54
-    protected $key = -1;
55
-
56
-    protected $current = null;
57
-
58
-    private $databasePlatform;
59
-
60
-    /**
61
-     * @var LoggerInterface
62
-     */
63
-    private $logger;
64
-
65
-    public function __construct($magicSql, array $parameters, $limit, $offset, array $columnDescriptors, $objectStorage, $className, TDBMService $tdbmService, MagicQuery $magicQuery, LoggerInterface $logger)
66
-    {
67
-        $this->magicSql = $magicSql;
68
-        $this->objectStorage = $objectStorage;
69
-        $this->className = $className;
70
-        $this->tdbmService = $tdbmService;
71
-        $this->parameters = $parameters;
72
-        $this->limit = $limit;
73
-        $this->offset = $offset;
74
-        $this->columnDescriptors = $columnDescriptors;
75
-        $this->magicQuery = $magicQuery;
76
-        $this->databasePlatform = $this->tdbmService->getConnection()->getDatabasePlatform();
77
-        $this->logger = $logger;
78
-    }
79
-
80
-    protected function executeQuery()
81
-    {
82
-        $sql = $this->magicQuery->build($this->magicSql, $this->parameters);
83
-        $sql = $this->tdbmService->getConnection()->getDatabasePlatform()->modifyLimitQuery($sql, $this->limit, $this->offset);
84
-
85
-        $this->logger->debug('Running SQL request: '.$sql);
86
-
87
-        $this->statement = $this->tdbmService->getConnection()->executeQuery($sql, $this->parameters);
88
-
89
-        $this->fetchStarted = true;
90
-    }
91
-
92
-    /**
93
-     * Counts found records (this is the number of records fetched, taking into account the LIMIT and OFFSET settings).
94
-     *
95
-     * @return int
96
-     */
97
-    public function count()
98
-    {
99
-        if (!$this->fetchStarted) {
100
-            $this->executeQuery();
101
-        }
102
-
103
-        return $this->statement->rowCount();
104
-    }
105
-
106
-    /**
107
-     * Fetches record at current cursor.
108
-     *
109
-     * @return AbstractTDBMObject|null
110
-     */
111
-    public function current()
112
-    {
113
-        return $this->current;
114
-    }
115
-
116
-    /**
117
-     * Returns the current result's key.
118
-     *
119
-     * @return int
120
-     */
121
-    public function key()
122
-    {
123
-        return $this->key;
124
-    }
125
-
126
-    /**
127
-     * Advances the cursor to the next result.
128
-     * Casts the database result into one (or several) beans.
129
-     */
130
-    public function next()
131
-    {
132
-        $row = $this->statement->fetch(\PDO::FETCH_NUM);
133
-        if ($row) {
134
-
135
-            // array<tablegroup, array<table, array<column, value>>>
136
-            $beansData = [];
137
-            foreach ($row as $i => $value) {
138
-                $columnDescriptor = $this->columnDescriptors[$i];
139
-
140
-                if ($columnDescriptor['tableGroup'] === null) {
141
-                    // A column can have no tableGroup (if it comes from an ORDER BY expression)
142
-                    continue;
143
-                }
144
-
145
-                // Let's cast the value according to its type
146
-                $value = $columnDescriptor['type']->convertToPHPValue($value, $this->databasePlatform);
147
-
148
-                $beansData[$columnDescriptor['tableGroup']][$columnDescriptor['table']][$columnDescriptor['column']] = $value;
149
-            }
150
-
151
-            $reflectionClassCache = [];
152
-            $firstBean = true;
153
-            foreach ($beansData as $beanData) {
154
-
155
-                // Let's find the bean class name associated to the bean.
156
-
157
-                list($actualClassName, $mainBeanTableName, $tablesUsed) = $this->tdbmService->_getClassNameFromBeanData($beanData);
158
-
159
-                if ($this->className !== null) {
160
-                    $actualClassName = $this->className;
161
-                }
162
-
163
-                // Let's filter out the beanData that is not used (because it belongs to a part of the hierarchy that is not fetched:
164
-                foreach ($beanData as $tableName => $descriptors) {
165
-                    if (!in_array($tableName, $tablesUsed)) {
166
-                        unset($beanData[$tableName]);
167
-                    }
168
-                }
169
-
170
-                // Must we create the bean? Let's see in the cache if we have a mapping DbRow?
171
-                // Let's get the first object mapping a row:
172
-                // We do this loop only for the first table
173
-
174
-                $primaryKeys = $this->tdbmService->_getPrimaryKeysFromObjectData($mainBeanTableName, $beanData[$mainBeanTableName]);
175
-                $hash = $this->tdbmService->getObjectHash($primaryKeys);
176
-
177
-                if ($this->objectStorage->has($mainBeanTableName, $hash)) {
178
-                    $dbRow = $this->objectStorage->get($mainBeanTableName, $hash);
179
-                    $bean = $dbRow->getTDBMObject();
180
-                } else {
181
-                    // Let's construct the bean
182
-                    if (!isset($reflectionClassCache[$actualClassName])) {
183
-                        $reflectionClassCache[$actualClassName] = new \ReflectionClass($actualClassName);
184
-                    }
185
-                    // Let's bypass the constructor when creating the bean!
186
-                    $bean = $reflectionClassCache[$actualClassName]->newInstanceWithoutConstructor();
187
-                    $bean->_constructFromData($beanData, $this->tdbmService);
188
-                }
189
-
190
-                // The first bean is the one containing the main table.
191
-                if ($firstBean) {
192
-                    $firstBean = false;
193
-                    $this->current = $bean;
194
-                }
195
-            }
196
-
197
-            ++$this->key;
198
-        } else {
199
-            $this->current = null;
200
-        }
201
-    }
202
-
203
-    /**
204
-     * Moves the cursor to the beginning of the result set.
205
-     */
206
-    public function rewind()
207
-    {
208
-        $this->executeQuery();
209
-        $this->key = -1;
210
-        $this->next();
211
-    }
212
-    /**
213
-     * Checks if the cursor is reading a valid result.
214
-     *
215
-     * @return bool
216
-     */
217
-    public function valid()
218
-    {
219
-        return $this->current !== null;
220
-    }
221
-
222
-    /**
223
-     * Whether a offset exists.
224
-     *
225
-     * @link http://php.net/manual/en/arrayaccess.offsetexists.php
226
-     *
227
-     * @param mixed $offset <p>
228
-     *                      An offset to check for.
229
-     *                      </p>
230
-     *
231
-     * @return bool true on success or false on failure.
232
-     *              </p>
233
-     *              <p>
234
-     *              The return value will be casted to boolean if non-boolean was returned
235
-     *
236
-     * @since 5.0.0
237
-     */
238
-    public function offsetExists($offset)
239
-    {
240
-        throw new TDBMInvalidOperationException('You cannot access this result set via index because it was fetched in CURSOR mode. Use ARRAY_MODE instead.');
241
-    }
242
-
243
-    /**
244
-     * Offset to retrieve.
245
-     *
246
-     * @link http://php.net/manual/en/arrayaccess.offsetget.php
247
-     *
248
-     * @param mixed $offset <p>
249
-     *                      The offset to retrieve.
250
-     *                      </p>
251
-     *
252
-     * @return mixed Can return all value types
253
-     *
254
-     * @since 5.0.0
255
-     */
256
-    public function offsetGet($offset)
257
-    {
258
-        throw new TDBMInvalidOperationException('You cannot access this result set via index because it was fetched in CURSOR mode. Use ARRAY_MODE instead.');
259
-    }
260
-
261
-    /**
262
-     * Offset to set.
263
-     *
264
-     * @link http://php.net/manual/en/arrayaccess.offsetset.php
265
-     *
266
-     * @param mixed $offset <p>
267
-     *                      The offset to assign the value to.
268
-     *                      </p>
269
-     * @param mixed $value  <p>
270
-     *                      The value to set.
271
-     *                      </p>
272
-     *
273
-     * @since 5.0.0
274
-     */
275
-    public function offsetSet($offset, $value)
276
-    {
277
-        throw new TDBMInvalidOperationException('You can set values in a TDBM result set.');
278
-    }
279
-
280
-    /**
281
-     * Offset to unset.
282
-     *
283
-     * @link http://php.net/manual/en/arrayaccess.offsetunset.php
284
-     *
285
-     * @param mixed $offset <p>
286
-     *                      The offset to unset.
287
-     *                      </p>
288
-     *
289
-     * @since 5.0.0
290
-     */
291
-    public function offsetUnset($offset)
292
-    {
293
-        throw new TDBMInvalidOperationException('You can unset values in a TDBM result set.');
294
-    }
32
+	/**
33
+	 * @var Statement
34
+	 */
35
+	protected $statement;
36
+
37
+	protected $fetchStarted = false;
38
+	private $objectStorage;
39
+	private $className;
40
+
41
+	private $tdbmService;
42
+	private $magicSql;
43
+	private $parameters;
44
+	private $limit;
45
+	private $offset;
46
+	private $columnDescriptors;
47
+	private $magicQuery;
48
+
49
+	/**
50
+	 * The key of the current retrieved object.
51
+	 *
52
+	 * @var int
53
+	 */
54
+	protected $key = -1;
55
+
56
+	protected $current = null;
57
+
58
+	private $databasePlatform;
59
+
60
+	/**
61
+	 * @var LoggerInterface
62
+	 */
63
+	private $logger;
64
+
65
+	public function __construct($magicSql, array $parameters, $limit, $offset, array $columnDescriptors, $objectStorage, $className, TDBMService $tdbmService, MagicQuery $magicQuery, LoggerInterface $logger)
66
+	{
67
+		$this->magicSql = $magicSql;
68
+		$this->objectStorage = $objectStorage;
69
+		$this->className = $className;
70
+		$this->tdbmService = $tdbmService;
71
+		$this->parameters = $parameters;
72
+		$this->limit = $limit;
73
+		$this->offset = $offset;
74
+		$this->columnDescriptors = $columnDescriptors;
75
+		$this->magicQuery = $magicQuery;
76
+		$this->databasePlatform = $this->tdbmService->getConnection()->getDatabasePlatform();
77
+		$this->logger = $logger;
78
+	}
79
+
80
+	protected function executeQuery()
81
+	{
82
+		$sql = $this->magicQuery->build($this->magicSql, $this->parameters);
83
+		$sql = $this->tdbmService->getConnection()->getDatabasePlatform()->modifyLimitQuery($sql, $this->limit, $this->offset);
84
+
85
+		$this->logger->debug('Running SQL request: '.$sql);
86
+
87
+		$this->statement = $this->tdbmService->getConnection()->executeQuery($sql, $this->parameters);
88
+
89
+		$this->fetchStarted = true;
90
+	}
91
+
92
+	/**
93
+	 * Counts found records (this is the number of records fetched, taking into account the LIMIT and OFFSET settings).
94
+	 *
95
+	 * @return int
96
+	 */
97
+	public function count()
98
+	{
99
+		if (!$this->fetchStarted) {
100
+			$this->executeQuery();
101
+		}
102
+
103
+		return $this->statement->rowCount();
104
+	}
105
+
106
+	/**
107
+	 * Fetches record at current cursor.
108
+	 *
109
+	 * @return AbstractTDBMObject|null
110
+	 */
111
+	public function current()
112
+	{
113
+		return $this->current;
114
+	}
115
+
116
+	/**
117
+	 * Returns the current result's key.
118
+	 *
119
+	 * @return int
120
+	 */
121
+	public function key()
122
+	{
123
+		return $this->key;
124
+	}
125
+
126
+	/**
127
+	 * Advances the cursor to the next result.
128
+	 * Casts the database result into one (or several) beans.
129
+	 */
130
+	public function next()
131
+	{
132
+		$row = $this->statement->fetch(\PDO::FETCH_NUM);
133
+		if ($row) {
134
+
135
+			// array<tablegroup, array<table, array<column, value>>>
136
+			$beansData = [];
137
+			foreach ($row as $i => $value) {
138
+				$columnDescriptor = $this->columnDescriptors[$i];
139
+
140
+				if ($columnDescriptor['tableGroup'] === null) {
141
+					// A column can have no tableGroup (if it comes from an ORDER BY expression)
142
+					continue;
143
+				}
144
+
145
+				// Let's cast the value according to its type
146
+				$value = $columnDescriptor['type']->convertToPHPValue($value, $this->databasePlatform);
147
+
148
+				$beansData[$columnDescriptor['tableGroup']][$columnDescriptor['table']][$columnDescriptor['column']] = $value;
149
+			}
150
+
151
+			$reflectionClassCache = [];
152
+			$firstBean = true;
153
+			foreach ($beansData as $beanData) {
154
+
155
+				// Let's find the bean class name associated to the bean.
156
+
157
+				list($actualClassName, $mainBeanTableName, $tablesUsed) = $this->tdbmService->_getClassNameFromBeanData($beanData);
158
+
159
+				if ($this->className !== null) {
160
+					$actualClassName = $this->className;
161
+				}
162
+
163
+				// Let's filter out the beanData that is not used (because it belongs to a part of the hierarchy that is not fetched:
164
+				foreach ($beanData as $tableName => $descriptors) {
165
+					if (!in_array($tableName, $tablesUsed)) {
166
+						unset($beanData[$tableName]);
167
+					}
168
+				}
169
+
170
+				// Must we create the bean? Let's see in the cache if we have a mapping DbRow?
171
+				// Let's get the first object mapping a row:
172
+				// We do this loop only for the first table
173
+
174
+				$primaryKeys = $this->tdbmService->_getPrimaryKeysFromObjectData($mainBeanTableName, $beanData[$mainBeanTableName]);
175
+				$hash = $this->tdbmService->getObjectHash($primaryKeys);
176
+
177
+				if ($this->objectStorage->has($mainBeanTableName, $hash)) {
178
+					$dbRow = $this->objectStorage->get($mainBeanTableName, $hash);
179
+					$bean = $dbRow->getTDBMObject();
180
+				} else {
181
+					// Let's construct the bean
182
+					if (!isset($reflectionClassCache[$actualClassName])) {
183
+						$reflectionClassCache[$actualClassName] = new \ReflectionClass($actualClassName);
184
+					}
185
+					// Let's bypass the constructor when creating the bean!
186
+					$bean = $reflectionClassCache[$actualClassName]->newInstanceWithoutConstructor();
187
+					$bean->_constructFromData($beanData, $this->tdbmService);
188
+				}
189
+
190
+				// The first bean is the one containing the main table.
191
+				if ($firstBean) {
192
+					$firstBean = false;
193
+					$this->current = $bean;
194
+				}
195
+			}
196
+
197
+			++$this->key;
198
+		} else {
199
+			$this->current = null;
200
+		}
201
+	}
202
+
203
+	/**
204
+	 * Moves the cursor to the beginning of the result set.
205
+	 */
206
+	public function rewind()
207
+	{
208
+		$this->executeQuery();
209
+		$this->key = -1;
210
+		$this->next();
211
+	}
212
+	/**
213
+	 * Checks if the cursor is reading a valid result.
214
+	 *
215
+	 * @return bool
216
+	 */
217
+	public function valid()
218
+	{
219
+		return $this->current !== null;
220
+	}
221
+
222
+	/**
223
+	 * Whether a offset exists.
224
+	 *
225
+	 * @link http://php.net/manual/en/arrayaccess.offsetexists.php
226
+	 *
227
+	 * @param mixed $offset <p>
228
+	 *                      An offset to check for.
229
+	 *                      </p>
230
+	 *
231
+	 * @return bool true on success or false on failure.
232
+	 *              </p>
233
+	 *              <p>
234
+	 *              The return value will be casted to boolean if non-boolean was returned
235
+	 *
236
+	 * @since 5.0.0
237
+	 */
238
+	public function offsetExists($offset)
239
+	{
240
+		throw new TDBMInvalidOperationException('You cannot access this result set via index because it was fetched in CURSOR mode. Use ARRAY_MODE instead.');
241
+	}
242
+
243
+	/**
244
+	 * Offset to retrieve.
245
+	 *
246
+	 * @link http://php.net/manual/en/arrayaccess.offsetget.php
247
+	 *
248
+	 * @param mixed $offset <p>
249
+	 *                      The offset to retrieve.
250
+	 *                      </p>
251
+	 *
252
+	 * @return mixed Can return all value types
253
+	 *
254
+	 * @since 5.0.0
255
+	 */
256
+	public function offsetGet($offset)
257
+	{
258
+		throw new TDBMInvalidOperationException('You cannot access this result set via index because it was fetched in CURSOR mode. Use ARRAY_MODE instead.');
259
+	}
260
+
261
+	/**
262
+	 * Offset to set.
263
+	 *
264
+	 * @link http://php.net/manual/en/arrayaccess.offsetset.php
265
+	 *
266
+	 * @param mixed $offset <p>
267
+	 *                      The offset to assign the value to.
268
+	 *                      </p>
269
+	 * @param mixed $value  <p>
270
+	 *                      The value to set.
271
+	 *                      </p>
272
+	 *
273
+	 * @since 5.0.0
274
+	 */
275
+	public function offsetSet($offset, $value)
276
+	{
277
+		throw new TDBMInvalidOperationException('You can set values in a TDBM result set.');
278
+	}
279
+
280
+	/**
281
+	 * Offset to unset.
282
+	 *
283
+	 * @link http://php.net/manual/en/arrayaccess.offsetunset.php
284
+	 *
285
+	 * @param mixed $offset <p>
286
+	 *                      The offset to unset.
287
+	 *                      </p>
288
+	 *
289
+	 * @since 5.0.0
290
+	 */
291
+	public function offsetUnset($offset)
292
+	{
293
+		throw new TDBMInvalidOperationException('You can unset values in a TDBM result set.');
294
+	}
295 295
 }
Please login to merge, or discard this patch.
src/Mouf/Database/TDBM/OrderByAnalyzer.php 2 patches
Indentation   +118 added lines, -118 removed lines patch added patch discarded remove patch
@@ -12,122 +12,122 @@
 block discarded – undo
12 12
  */
13 13
 class OrderByAnalyzer
14 14
 {
15
-    /**
16
-     * The content of the cache variable.
17
-     *
18
-     * @var Cache
19
-     */
20
-    private $cache;
21
-
22
-    /**
23
-     * @var string
24
-     */
25
-    private $cachePrefix;
26
-
27
-    /**
28
-     * OrderByAnalyzer constructor.
29
-     *
30
-     * @param Cache       $cache
31
-     * @param string|null $cachePrefix
32
-     */
33
-    public function __construct(Cache $cache, $cachePrefix = null)
34
-    {
35
-        $this->cache = $cache;
36
-        $this->cachePrefix = $cachePrefix;
37
-    }
38
-
39
-    /**
40
-     * Returns an array for each sorted "column" in the form:.
41
-     *
42
-     * [
43
-     *      [
44
-     *          'type' => 'colref',
45
-     *          'table' => null,
46
-     *          'column' => 'a',
47
-     *          'direction' => 'ASC'
48
-     *      ],
49
-     *      [
50
-     *          'type' => 'expr',
51
-     *          'expr' => 'RAND()',
52
-     *          'direction' => 'DESC'
53
-     *      ]
54
-     * ]
55
-     *
56
-     * @param string $orderBy
57
-     *
58
-     * @return array
59
-     */
60
-    public function analyzeOrderBy(string $orderBy) : array
61
-    {
62
-        $key = $this->cachePrefix.'_order_by_analysis_'.$orderBy;
63
-        $results = $this->cache->fetch($key);
64
-        if ($results !== false) {
65
-            return $results;
66
-        }
67
-        $results = $this->analyzeOrderByNoCache($orderBy);
68
-        $this->cache->save($key, $results);
69
-
70
-        return $results;
71
-    }
72
-
73
-    private function analyzeOrderByNoCache(string $orderBy) : array
74
-    {
75
-        $sqlParser = new PHPSQLParser();
76
-        $sql = 'SELECT 1 FROM a ORDER BY '.$orderBy;
77
-        $parsed = $sqlParser->parse($sql, true);
78
-
79
-        $results = [];
80
-
81
-        for ($i = 0, $count = count($parsed['ORDER']); $i < $count; ++$i) {
82
-            $orderItem = $parsed['ORDER'][$i];
83
-            if ($orderItem['expr_type'] === 'colref') {
84
-                $parts = $orderItem['no_quotes']['parts'];
85
-                $columnName = array_pop($parts);
86
-                if (!empty($parts)) {
87
-                    $tableName = array_pop($parts);
88
-                } else {
89
-                    $tableName = null;
90
-                }
91
-
92
-                $results[] = [
93
-                    'type' => 'colref',
94
-                    'table' => $tableName,
95
-                    'column' => $columnName,
96
-                    'direction' => $orderItem['direction'],
97
-                ];
98
-            } else {
99
-                $position = $orderItem['position'];
100
-                if ($i + 1 < $count) {
101
-                    $nextPosition = $parsed['ORDER'][$i + 1]['position'];
102
-                    $str = substr($sql, $position, $nextPosition - $position);
103
-                } else {
104
-                    $str = substr($sql, $position);
105
-                }
106
-
107
-                $str = trim($str, " \t\r\n,");
108
-
109
-                $results[] = [
110
-                    'type' => 'expr',
111
-                    'expr' => $this->trimDirection($str),
112
-                    'direction' => $orderItem['direction'],
113
-                ];
114
-            }
115
-        }
116
-
117
-        return $results;
118
-    }
119
-
120
-    /**
121
-     * Trims the ASC/DESC direction at the end of the string.
122
-     *
123
-     * @param string $sql
124
-     *
125
-     * @return string
126
-     */
127
-    private function trimDirection(string $sql) : string
128
-    {
129
-        preg_match('/^(.*)(\s+(DESC|ASC|))*$/Ui', $sql, $matches);
130
-
131
-        return $matches[1];
132
-    }
15
+	/**
16
+	 * The content of the cache variable.
17
+	 *
18
+	 * @var Cache
19
+	 */
20
+	private $cache;
21
+
22
+	/**
23
+	 * @var string
24
+	 */
25
+	private $cachePrefix;
26
+
27
+	/**
28
+	 * OrderByAnalyzer constructor.
29
+	 *
30
+	 * @param Cache       $cache
31
+	 * @param string|null $cachePrefix
32
+	 */
33
+	public function __construct(Cache $cache, $cachePrefix = null)
34
+	{
35
+		$this->cache = $cache;
36
+		$this->cachePrefix = $cachePrefix;
37
+	}
38
+
39
+	/**
40
+	 * Returns an array for each sorted "column" in the form:.
41
+	 *
42
+	 * [
43
+	 *      [
44
+	 *          'type' => 'colref',
45
+	 *          'table' => null,
46
+	 *          'column' => 'a',
47
+	 *          'direction' => 'ASC'
48
+	 *      ],
49
+	 *      [
50
+	 *          'type' => 'expr',
51
+	 *          'expr' => 'RAND()',
52
+	 *          'direction' => 'DESC'
53
+	 *      ]
54
+	 * ]
55
+	 *
56
+	 * @param string $orderBy
57
+	 *
58
+	 * @return array
59
+	 */
60
+	public function analyzeOrderBy(string $orderBy) : array
61
+	{
62
+		$key = $this->cachePrefix.'_order_by_analysis_'.$orderBy;
63
+		$results = $this->cache->fetch($key);
64
+		if ($results !== false) {
65
+			return $results;
66
+		}
67
+		$results = $this->analyzeOrderByNoCache($orderBy);
68
+		$this->cache->save($key, $results);
69
+
70
+		return $results;
71
+	}
72
+
73
+	private function analyzeOrderByNoCache(string $orderBy) : array
74
+	{
75
+		$sqlParser = new PHPSQLParser();
76
+		$sql = 'SELECT 1 FROM a ORDER BY '.$orderBy;
77
+		$parsed = $sqlParser->parse($sql, true);
78
+
79
+		$results = [];
80
+
81
+		for ($i = 0, $count = count($parsed['ORDER']); $i < $count; ++$i) {
82
+			$orderItem = $parsed['ORDER'][$i];
83
+			if ($orderItem['expr_type'] === 'colref') {
84
+				$parts = $orderItem['no_quotes']['parts'];
85
+				$columnName = array_pop($parts);
86
+				if (!empty($parts)) {
87
+					$tableName = array_pop($parts);
88
+				} else {
89
+					$tableName = null;
90
+				}
91
+
92
+				$results[] = [
93
+					'type' => 'colref',
94
+					'table' => $tableName,
95
+					'column' => $columnName,
96
+					'direction' => $orderItem['direction'],
97
+				];
98
+			} else {
99
+				$position = $orderItem['position'];
100
+				if ($i + 1 < $count) {
101
+					$nextPosition = $parsed['ORDER'][$i + 1]['position'];
102
+					$str = substr($sql, $position, $nextPosition - $position);
103
+				} else {
104
+					$str = substr($sql, $position);
105
+				}
106
+
107
+				$str = trim($str, " \t\r\n,");
108
+
109
+				$results[] = [
110
+					'type' => 'expr',
111
+					'expr' => $this->trimDirection($str),
112
+					'direction' => $orderItem['direction'],
113
+				];
114
+			}
115
+		}
116
+
117
+		return $results;
118
+	}
119
+
120
+	/**
121
+	 * Trims the ASC/DESC direction at the end of the string.
122
+	 *
123
+	 * @param string $sql
124
+	 *
125
+	 * @return string
126
+	 */
127
+	private function trimDirection(string $sql) : string
128
+	{
129
+		preg_match('/^(.*)(\s+(DESC|ASC|))*$/Ui', $sql, $matches);
130
+
131
+		return $matches[1];
132
+	}
133 133
 }
Please login to merge, or discard this patch.
Spacing   +4 added lines, -4 removed lines patch added patch discarded remove patch
@@ -78,7 +78,7 @@  discard block
 block discarded – undo
78 78
 
79 79
         $results = [];
80 80
 
81
-        for ($i = 0, $count = count($parsed['ORDER']); $i < $count; ++$i) {
81
+        for ($i = 0, $count = count($parsed['ORDER']); $i<$count; ++$i) {
82 82
             $orderItem = $parsed['ORDER'][$i];
83 83
             if ($orderItem['expr_type'] === 'colref') {
84 84
                 $parts = $orderItem['no_quotes']['parts'];
@@ -97,9 +97,9 @@  discard block
 block discarded – undo
97 97
                 ];
98 98
             } else {
99 99
                 $position = $orderItem['position'];
100
-                if ($i + 1 < $count) {
101
-                    $nextPosition = $parsed['ORDER'][$i + 1]['position'];
102
-                    $str = substr($sql, $position, $nextPosition - $position);
100
+                if ($i+1<$count) {
101
+                    $nextPosition = $parsed['ORDER'][$i+1]['position'];
102
+                    $str = substr($sql, $position, $nextPosition-$position);
103 103
                 } else {
104 104
                     $str = substr($sql, $position);
105 105
                 }
Please login to merge, or discard this patch.
src/Mouf/Database/TDBM/TDBMService.php 4 patches
Doc Comments   +2 added lines, -2 removed lines patch added patch discarded remove patch
@@ -1301,7 +1301,7 @@  discard block
 block discarded – undo
1301 1301
      * @param string                       $mainTable             The name of the table queried
1302 1302
      * @param string|array|null            $filter                The SQL filters to apply to the query (the WHERE part). Columns from tables different from $mainTable must be prefixed by the table name (in the form: table.column)
1303 1303
      * @param array                        $parameters
1304
-     * @param string|UncheckedOrderBy|null $orderString           The ORDER BY part of the query. Columns from tables different from $mainTable must be prefixed by the table name (in the form: table.column)
1304
+     * @param string|null $orderString           The ORDER BY part of the query. Columns from tables different from $mainTable must be prefixed by the table name (in the form: table.column)
1305 1305
      * @param array                        $additionalTablesFetch
1306 1306
      * @param int                          $mode
1307 1307
      * @param string                       $className             Optional: The name of the class to instantiate. This class must extend the TDBMObject class. If none is specified, a TDBMObject instance will be returned
@@ -1769,7 +1769,7 @@  discard block
 block discarded – undo
1769 1769
      * @param $pivotTableName
1770 1770
      * @param AbstractTDBMObject $bean
1771 1771
      *
1772
-     * @return AbstractTDBMObject[]
1772
+     * @return ResultIterator
1773 1773
      */
1774 1774
     public function _getRelatedBeans(string $pivotTableName, AbstractTDBMObject $bean)
1775 1775
     {
Please login to merge, or discard this patch.
Spacing   +7 added lines, -7 removed lines patch added patch discarded remove patch
@@ -1064,7 +1064,7 @@  discard block
 block discarded – undo
1064 1064
         sort($tables);
1065 1065
 
1066 1066
         return $this->fromCache($this->cachePrefix.'_linkbetweeninheritedtables_'.implode('__split__', $tables),
1067
-            function () use ($tables) {
1067
+            function() use ($tables) {
1068 1068
                 return $this->_getLinkBetweenInheritedTablesWithoutCache($tables);
1069 1069
             });
1070 1070
     }
@@ -1111,7 +1111,7 @@  discard block
 block discarded – undo
1111 1111
      */
1112 1112
     public function _getRelatedTablesByInheritance($table)
1113 1113
     {
1114
-        return $this->fromCache($this->cachePrefix.'_relatedtables_'.$table, function () use ($table) {
1114
+        return $this->fromCache($this->cachePrefix.'_relatedtables_'.$table, function() use ($table) {
1115 1115
             return $this->_getRelatedTablesByInheritanceWithoutCache($table);
1116 1116
         });
1117 1117
     }
@@ -1358,7 +1358,7 @@  discard block
 block discarded – undo
1358 1358
         $objects = $this->findObjects($mainTable, $filter, $parameters, null, $additionalTablesFetch, self::MODE_ARRAY, $className);
1359 1359
         $page = $objects->take(0, 2);
1360 1360
         $count = $page->count();
1361
-        if ($count > 1) {
1361
+        if ($count>1) {
1362 1362
             throw new DuplicateRowException("Error while querying an object for table '$mainTable': More than 1 row have been returned, but we should have received at most one.");
1363 1363
         } elseif ($count === 0) {
1364 1364
             return;
@@ -1385,7 +1385,7 @@  discard block
 block discarded – undo
1385 1385
         $objects = $this->findObjectsFromSql($mainTable, $from, $filter, $parameters, null, self::MODE_ARRAY, $className);
1386 1386
         $page = $objects->take(0, 2);
1387 1387
         $count = $page->count();
1388
-        if ($count > 1) {
1388
+        if ($count>1) {
1389 1389
             throw new DuplicateRowException("Error while querying an object for table '$mainTable': More than 1 row have been returned, but we should have received at most one.");
1390 1390
         } elseif ($count === 0) {
1391 1391
             return;
@@ -1503,7 +1503,7 @@  discard block
 block discarded – undo
1503 1503
         $remoteTable = $remoteFk->getForeignTableName();
1504 1504
 
1505 1505
         $primaryKeys = $this->getPrimaryKeyValues($bean);
1506
-        $columnNames = array_map(function ($name) use ($pivotTableName) {
1506
+        $columnNames = array_map(function($name) use ($pivotTableName) {
1507 1507
             return $pivotTableName.'.'.$name;
1508 1508
         }, $localFk->getLocalColumns());
1509 1509
 
@@ -1526,7 +1526,7 @@  discard block
 block discarded – undo
1526 1526
         $table1 = $fks[0]->getForeignTableName();
1527 1527
         $table2 = $fks[1]->getForeignTableName();
1528 1528
 
1529
-        $beanTables = array_map(function (DbRow $dbRow) {
1529
+        $beanTables = array_map(function(DbRow $dbRow) {
1530 1530
             return $dbRow->_getDbTableName();
1531 1531
         }, $bean->_getDbRows());
1532 1532
 
@@ -1584,7 +1584,7 @@  discard block
 block discarded – undo
1584 1584
     {
1585 1585
         if (!isset($typesForTable[$tableName])) {
1586 1586
             $columns = $this->tdbmSchemaAnalyzer->getSchema()->getTable($tableName)->getColumns();
1587
-            $typesForTable[$tableName] = array_map(function (Column $column) {
1587
+            $typesForTable[$tableName] = array_map(function(Column $column) {
1588 1588
                 return $column->getType();
1589 1589
             }, $columns);
1590 1590
         }
Please login to merge, or discard this patch.
Unused Use Statements   -2 removed lines patch added patch discarded remove patch
@@ -21,11 +21,9 @@
 block discarded – undo
21 21
 namespace Mouf\Database\TDBM;
22 22
 
23 23
 use Doctrine\Common\Cache\Cache;
24
-use Doctrine\Common\Cache\VoidCache;
25 24
 use Doctrine\DBAL\Connection;
26 25
 use Doctrine\DBAL\Schema\Column;
27 26
 use Doctrine\DBAL\Schema\ForeignKeyConstraint;
28
-use Doctrine\DBAL\Schema\Schema;
29 27
 use Doctrine\DBAL\Schema\Table;
30 28
 use Doctrine\DBAL\Types\Type;
31 29
 use Mouf\Database\MagicQuery;
Please login to merge, or discard this patch.
Indentation   +1364 added lines, -1364 removed lines patch added patch discarded remove patch
@@ -48,384 +48,384 @@  discard block
 block discarded – undo
48 48
  */
49 49
 class TDBMService
50 50
 {
51
-    const MODE_CURSOR = 1;
52
-    const MODE_ARRAY = 2;
53
-
54
-    /**
55
-     * The database connection.
56
-     *
57
-     * @var Connection
58
-     */
59
-    private $connection;
60
-
61
-    /**
62
-     * @var SchemaAnalyzer
63
-     */
64
-    private $schemaAnalyzer;
65
-
66
-    /**
67
-     * @var MagicQuery
68
-     */
69
-    private $magicQuery;
70
-
71
-    /**
72
-     * @var TDBMSchemaAnalyzer
73
-     */
74
-    private $tdbmSchemaAnalyzer;
75
-
76
-    /**
77
-     * @var string
78
-     */
79
-    private $cachePrefix;
80
-
81
-    /**
82
-     * Cache of table of primary keys.
83
-     * Primary keys are stored by tables, as an array of column.
84
-     * For instance $primary_key['my_table'][0] will return the first column of the primary key of table 'my_table'.
85
-     *
86
-     * @var string[]
87
-     */
88
-    private $primaryKeysColumns;
89
-
90
-    /**
91
-     * Service storing objects in memory.
92
-     * Access is done by table name and then by primary key.
93
-     * If the primary key is split on several columns, access is done by an array of columns, serialized.
94
-     *
95
-     * @var StandardObjectStorage|WeakrefObjectStorage
96
-     */
97
-    private $objectStorage;
98
-
99
-    /**
100
-     * The fetch mode of the result sets returned by `getObjects`.
101
-     * Can be one of: TDBMObjectArray::MODE_CURSOR or TDBMObjectArray::MODE_ARRAY or TDBMObjectArray::MODE_COMPATIBLE_ARRAY.
102
-     *
103
-     * In 'MODE_ARRAY' mode (default), the result is an array. Use this mode by default (unless the list returned is very big).
104
-     * In 'MODE_CURSOR' mode, the result is a Generator which is an iterable collection that can be scanned only once (only one "foreach") on it,
105
-     * and it cannot be accessed via key. Use this mode for large datasets processed by batch.
106
-     * In 'MODE_COMPATIBLE_ARRAY' mode, the result is an old TDBMObjectArray (used up to TDBM 3.2).
107
-     * You can access the array by key, or using foreach, several times.
108
-     *
109
-     * @var int
110
-     */
111
-    private $mode = self::MODE_ARRAY;
112
-
113
-    /**
114
-     * Table of new objects not yet inserted in database or objects modified that must be saved.
115
-     *
116
-     * @var \SplObjectStorage of DbRow objects
117
-     */
118
-    private $toSaveObjects;
119
-
120
-    /**
121
-     * A cache service to be used.
122
-     *
123
-     * @var Cache|null
124
-     */
125
-    private $cache;
126
-
127
-    /**
128
-     * Map associating a table name to a fully qualified Bean class name.
129
-     *
130
-     * @var array
131
-     */
132
-    private $tableToBeanMap = [];
133
-
134
-    /**
135
-     * @var \ReflectionClass[]
136
-     */
137
-    private $reflectionClassCache = array();
138
-
139
-    /**
140
-     * @var LoggerInterface
141
-     */
142
-    private $rootLogger;
143
-
144
-    /**
145
-     * @var LevelFilter|NullLogger
146
-     */
147
-    private $logger;
148
-
149
-    /**
150
-     * @var OrderByAnalyzer
151
-     */
152
-    private $orderByAnalyzer;
153
-
154
-    /**
155
-     * @var string
156
-     */
157
-    private $beanNamespace;
158
-
159
-    /**
160
-     * @var NamingStrategyInterface
161
-     */
162
-    private $namingStrategy;
163
-    /**
164
-     * @var ConfigurationInterface
165
-     */
166
-    private $configuration;
167
-
168
-    /**
169
-     * @param ConfigurationInterface $configuration The configuration object
170
-     */
171
-    public function __construct(ConfigurationInterface $configuration /*Connection $connection, Cache $cache = null, SchemaAnalyzer $schemaAnalyzer = null, LoggerInterface $logger = null*/)
172
-    {
173
-        if (extension_loaded('weakref')) {
174
-            $this->objectStorage = new WeakrefObjectStorage();
175
-        } else {
176
-            $this->objectStorage = new StandardObjectStorage();
177
-        }
178
-        $this->connection = $configuration->getConnection();
179
-        $this->cache = $configuration->getCache();
180
-        $this->schemaAnalyzer = $configuration->getSchemaAnalyzer();
181
-
182
-        $this->magicQuery = new MagicQuery($this->connection, $this->cache, $this->schemaAnalyzer);
183
-
184
-        $this->tdbmSchemaAnalyzer = new TDBMSchemaAnalyzer($this->connection, $this->cache, $this->schemaAnalyzer);
185
-        $this->cachePrefix = $this->tdbmSchemaAnalyzer->getCachePrefix();
186
-
187
-        $this->toSaveObjects = new \SplObjectStorage();
188
-        $logger = $configuration->getLogger();
189
-        if ($logger === null) {
190
-            $this->logger = new NullLogger();
191
-            $this->rootLogger = new NullLogger();
192
-        } else {
193
-            $this->rootLogger = $logger;
194
-            $this->setLogLevel(LogLevel::WARNING);
195
-        }
196
-        $this->orderByAnalyzer = new OrderByAnalyzer($this->cache, $this->cachePrefix);
197
-        $this->beanNamespace = $configuration->getBeanNamespace();
198
-        $this->namingStrategy = $configuration->getNamingStrategy();
199
-        $this->configuration = $configuration;
200
-    }
201
-
202
-    /**
203
-     * Returns the object used to connect to the database.
204
-     *
205
-     * @return Connection
206
-     */
207
-    public function getConnection(): Connection
208
-    {
209
-        return $this->connection;
210
-    }
211
-
212
-    /**
213
-     * Sets the default fetch mode of the result sets returned by `findObjects`.
214
-     * Can be one of: TDBMObjectArray::MODE_CURSOR or TDBMObjectArray::MODE_ARRAY.
215
-     *
216
-     * In 'MODE_ARRAY' mode (default), the result is a ResultIterator object that behaves like an array. Use this mode by default (unless the list returned is very big).
217
-     * In 'MODE_CURSOR' mode, the result is a ResultIterator object. If you scan it many times (by calling several time a foreach loop), the query will be run
218
-     * several times. In cursor mode, you cannot access the result set by key. Use this mode for large datasets processed by batch.
219
-     *
220
-     * @param int $mode
221
-     *
222
-     * @return $this
223
-     *
224
-     * @throws TDBMException
225
-     */
226
-    public function setFetchMode($mode)
227
-    {
228
-        if ($mode !== self::MODE_CURSOR && $mode !== self::MODE_ARRAY) {
229
-            throw new TDBMException("Unknown fetch mode: '".$this->mode."'");
230
-        }
231
-        $this->mode = $mode;
232
-
233
-        return $this;
234
-    }
235
-
236
-    /**
237
-     * Removes the given object from database.
238
-     * This cannot be called on an object that is not attached to this TDBMService
239
-     * (will throw a TDBMInvalidOperationException).
240
-     *
241
-     * @param AbstractTDBMObject $object the object to delete
242
-     *
243
-     * @throws TDBMException
244
-     * @throws TDBMInvalidOperationException
245
-     */
246
-    public function delete(AbstractTDBMObject $object)
247
-    {
248
-        switch ($object->_getStatus()) {
249
-            case TDBMObjectStateEnum::STATE_DELETED:
250
-                // Nothing to do, object already deleted.
251
-                return;
252
-            case TDBMObjectStateEnum::STATE_DETACHED:
253
-                throw new TDBMInvalidOperationException('Cannot delete a detached object');
254
-            case TDBMObjectStateEnum::STATE_NEW:
255
-                $this->deleteManyToManyRelationships($object);
256
-                foreach ($object->_getDbRows() as $dbRow) {
257
-                    $this->removeFromToSaveObjectList($dbRow);
258
-                }
259
-                break;
260
-            case TDBMObjectStateEnum::STATE_DIRTY:
261
-                foreach ($object->_getDbRows() as $dbRow) {
262
-                    $this->removeFromToSaveObjectList($dbRow);
263
-                }
264
-                // And continue deleting...
265
-            case TDBMObjectStateEnum::STATE_NOT_LOADED:
266
-            case TDBMObjectStateEnum::STATE_LOADED:
267
-                $this->deleteManyToManyRelationships($object);
268
-                // Let's delete db rows, in reverse order.
269
-                foreach (array_reverse($object->_getDbRows()) as $dbRow) {
270
-                    $tableName = $dbRow->_getDbTableName();
271
-                    $primaryKeys = $dbRow->_getPrimaryKeys();
272
-                    $this->connection->delete($tableName, $primaryKeys);
273
-                    $this->objectStorage->remove($dbRow->_getDbTableName(), $this->getObjectHash($primaryKeys));
274
-                }
275
-                break;
276
-            // @codeCoverageIgnoreStart
277
-            default:
278
-                throw new TDBMInvalidOperationException('Unexpected status for bean');
279
-            // @codeCoverageIgnoreEnd
280
-        }
281
-
282
-        $object->_setStatus(TDBMObjectStateEnum::STATE_DELETED);
283
-    }
284
-
285
-    /**
286
-     * Removes all many to many relationships for this object.
287
-     *
288
-     * @param AbstractTDBMObject $object
289
-     */
290
-    private function deleteManyToManyRelationships(AbstractTDBMObject $object)
291
-    {
292
-        foreach ($object->_getDbRows() as $tableName => $dbRow) {
293
-            $pivotTables = $this->tdbmSchemaAnalyzer->getPivotTableLinkedToTable($tableName);
294
-            foreach ($pivotTables as $pivotTable) {
295
-                $remoteBeans = $object->_getRelationships($pivotTable);
296
-                foreach ($remoteBeans as $remoteBean) {
297
-                    $object->_removeRelationship($pivotTable, $remoteBean);
298
-                }
299
-            }
300
-        }
301
-        $this->persistManyToManyRelationships($object);
302
-    }
303
-
304
-    /**
305
-     * This function removes the given object from the database. It will also remove all objects relied to the one given
306
-     * by parameter before all.
307
-     *
308
-     * Notice: if the object has a multiple primary key, the function will not work.
309
-     *
310
-     * @param AbstractTDBMObject $objToDelete
311
-     */
312
-    public function deleteCascade(AbstractTDBMObject $objToDelete)
313
-    {
314
-        $this->deleteAllConstraintWithThisObject($objToDelete);
315
-        $this->delete($objToDelete);
316
-    }
317
-
318
-    /**
319
-     * This function is used only in TDBMService (private function)
320
-     * It will call deleteCascade function foreach object relied with a foreign key to the object given by parameter.
321
-     *
322
-     * @param AbstractTDBMObject $obj
323
-     */
324
-    private function deleteAllConstraintWithThisObject(AbstractTDBMObject $obj)
325
-    {
326
-        $dbRows = $obj->_getDbRows();
327
-        foreach ($dbRows as $dbRow) {
328
-            $tableName = $dbRow->_getDbTableName();
329
-            $pks = array_values($dbRow->_getPrimaryKeys());
330
-            if (!empty($pks)) {
331
-                $incomingFks = $this->tdbmSchemaAnalyzer->getIncomingForeignKeys($tableName);
332
-
333
-                foreach ($incomingFks as $incomingFk) {
334
-                    $filter = array_combine($incomingFk->getLocalColumns(), $pks);
335
-
336
-                    $results = $this->findObjects($incomingFk->getLocalTableName(), $filter);
337
-
338
-                    foreach ($results as $bean) {
339
-                        $this->deleteCascade($bean);
340
-                    }
341
-                }
342
-            }
343
-        }
344
-    }
345
-
346
-    /**
347
-     * This function performs a save() of all the objects that have been modified.
348
-     */
349
-    public function completeSave()
350
-    {
351
-        foreach ($this->toSaveObjects as $dbRow) {
352
-            $this->save($dbRow->getTDBMObject());
353
-        }
354
-    }
355
-
356
-    /**
357
-     * Takes in input a filter_bag (which can be about anything from a string to an array of TDBMObjects... see above from documentation),
358
-     * and gives back a proper Filter object.
359
-     *
360
-     * @param mixed $filter_bag
361
-     * @param int   $counter
362
-     *
363
-     * @return array First item: filter string, second item: parameters
364
-     *
365
-     * @throws TDBMException
366
-     */
367
-    public function buildFilterFromFilterBag($filter_bag, $counter = 1)
368
-    {
369
-        if ($filter_bag === null) {
370
-            return ['', []];
371
-        } elseif (is_string($filter_bag)) {
372
-            return [$filter_bag, []];
373
-        } elseif (is_array($filter_bag)) {
374
-            $sqlParts = [];
375
-            $parameters = [];
376
-            foreach ($filter_bag as $column => $value) {
377
-                if (is_int($column)) {
378
-                    list($subSqlPart, $subParameters) = $this->buildFilterFromFilterBag($value, $counter);
379
-                    $sqlParts[] = $subSqlPart;
380
-                    $parameters += $subParameters;
381
-                } else {
382
-                    $paramName = 'tdbmparam'.$counter;
383
-                    if (is_array($value)) {
384
-                        $sqlParts[] = $this->connection->quoteIdentifier($column).' IN :'.$paramName;
385
-                    } else {
386
-                        $sqlParts[] = $this->connection->quoteIdentifier($column).' = :'.$paramName;
387
-                    }
388
-                    $parameters[$paramName] = $value;
389
-                    ++$counter;
390
-                }
391
-            }
392
-
393
-            return [implode(' AND ', $sqlParts), $parameters];
394
-        } elseif ($filter_bag instanceof AbstractTDBMObject) {
395
-            $sqlParts = [];
396
-            $parameters = [];
397
-            $dbRows = $filter_bag->_getDbRows();
398
-            $dbRow = reset($dbRows);
399
-            $primaryKeys = $dbRow->_getPrimaryKeys();
400
-
401
-            foreach ($primaryKeys as $column => $value) {
402
-                $paramName = 'tdbmparam'.$counter;
403
-                $sqlParts[] = $this->connection->quoteIdentifier($dbRow->_getDbTableName()).'.'.$this->connection->quoteIdentifier($column).' = :'.$paramName;
404
-                $parameters[$paramName] = $value;
405
-                ++$counter;
406
-            }
407
-
408
-            return [implode(' AND ', $sqlParts), $parameters];
409
-        } elseif ($filter_bag instanceof \Iterator) {
410
-            return $this->buildFilterFromFilterBag(iterator_to_array($filter_bag), $counter);
411
-        } else {
412
-            throw new TDBMException('Error in filter. An object has been passed that is neither a SQL string, nor an array, nor a bean, nor null.');
413
-        }
414
-    }
415
-
416
-    /**
417
-     * @param string $table
418
-     *
419
-     * @return string[]
420
-     */
421
-    public function getPrimaryKeyColumns($table)
422
-    {
423
-        if (!isset($this->primaryKeysColumns[$table])) {
424
-            $this->primaryKeysColumns[$table] = $this->tdbmSchemaAnalyzer->getSchema()->getTable($table)->getPrimaryKeyColumns();
425
-
426
-            // TODO TDBM4: See if we need to improve error reporting if table name does not exist.
427
-
428
-            /*$arr = array();
51
+	const MODE_CURSOR = 1;
52
+	const MODE_ARRAY = 2;
53
+
54
+	/**
55
+	 * The database connection.
56
+	 *
57
+	 * @var Connection
58
+	 */
59
+	private $connection;
60
+
61
+	/**
62
+	 * @var SchemaAnalyzer
63
+	 */
64
+	private $schemaAnalyzer;
65
+
66
+	/**
67
+	 * @var MagicQuery
68
+	 */
69
+	private $magicQuery;
70
+
71
+	/**
72
+	 * @var TDBMSchemaAnalyzer
73
+	 */
74
+	private $tdbmSchemaAnalyzer;
75
+
76
+	/**
77
+	 * @var string
78
+	 */
79
+	private $cachePrefix;
80
+
81
+	/**
82
+	 * Cache of table of primary keys.
83
+	 * Primary keys are stored by tables, as an array of column.
84
+	 * For instance $primary_key['my_table'][0] will return the first column of the primary key of table 'my_table'.
85
+	 *
86
+	 * @var string[]
87
+	 */
88
+	private $primaryKeysColumns;
89
+
90
+	/**
91
+	 * Service storing objects in memory.
92
+	 * Access is done by table name and then by primary key.
93
+	 * If the primary key is split on several columns, access is done by an array of columns, serialized.
94
+	 *
95
+	 * @var StandardObjectStorage|WeakrefObjectStorage
96
+	 */
97
+	private $objectStorage;
98
+
99
+	/**
100
+	 * The fetch mode of the result sets returned by `getObjects`.
101
+	 * Can be one of: TDBMObjectArray::MODE_CURSOR or TDBMObjectArray::MODE_ARRAY or TDBMObjectArray::MODE_COMPATIBLE_ARRAY.
102
+	 *
103
+	 * In 'MODE_ARRAY' mode (default), the result is an array. Use this mode by default (unless the list returned is very big).
104
+	 * In 'MODE_CURSOR' mode, the result is a Generator which is an iterable collection that can be scanned only once (only one "foreach") on it,
105
+	 * and it cannot be accessed via key. Use this mode for large datasets processed by batch.
106
+	 * In 'MODE_COMPATIBLE_ARRAY' mode, the result is an old TDBMObjectArray (used up to TDBM 3.2).
107
+	 * You can access the array by key, or using foreach, several times.
108
+	 *
109
+	 * @var int
110
+	 */
111
+	private $mode = self::MODE_ARRAY;
112
+
113
+	/**
114
+	 * Table of new objects not yet inserted in database or objects modified that must be saved.
115
+	 *
116
+	 * @var \SplObjectStorage of DbRow objects
117
+	 */
118
+	private $toSaveObjects;
119
+
120
+	/**
121
+	 * A cache service to be used.
122
+	 *
123
+	 * @var Cache|null
124
+	 */
125
+	private $cache;
126
+
127
+	/**
128
+	 * Map associating a table name to a fully qualified Bean class name.
129
+	 *
130
+	 * @var array
131
+	 */
132
+	private $tableToBeanMap = [];
133
+
134
+	/**
135
+	 * @var \ReflectionClass[]
136
+	 */
137
+	private $reflectionClassCache = array();
138
+
139
+	/**
140
+	 * @var LoggerInterface
141
+	 */
142
+	private $rootLogger;
143
+
144
+	/**
145
+	 * @var LevelFilter|NullLogger
146
+	 */
147
+	private $logger;
148
+
149
+	/**
150
+	 * @var OrderByAnalyzer
151
+	 */
152
+	private $orderByAnalyzer;
153
+
154
+	/**
155
+	 * @var string
156
+	 */
157
+	private $beanNamespace;
158
+
159
+	/**
160
+	 * @var NamingStrategyInterface
161
+	 */
162
+	private $namingStrategy;
163
+	/**
164
+	 * @var ConfigurationInterface
165
+	 */
166
+	private $configuration;
167
+
168
+	/**
169
+	 * @param ConfigurationInterface $configuration The configuration object
170
+	 */
171
+	public function __construct(ConfigurationInterface $configuration /*Connection $connection, Cache $cache = null, SchemaAnalyzer $schemaAnalyzer = null, LoggerInterface $logger = null*/)
172
+	{
173
+		if (extension_loaded('weakref')) {
174
+			$this->objectStorage = new WeakrefObjectStorage();
175
+		} else {
176
+			$this->objectStorage = new StandardObjectStorage();
177
+		}
178
+		$this->connection = $configuration->getConnection();
179
+		$this->cache = $configuration->getCache();
180
+		$this->schemaAnalyzer = $configuration->getSchemaAnalyzer();
181
+
182
+		$this->magicQuery = new MagicQuery($this->connection, $this->cache, $this->schemaAnalyzer);
183
+
184
+		$this->tdbmSchemaAnalyzer = new TDBMSchemaAnalyzer($this->connection, $this->cache, $this->schemaAnalyzer);
185
+		$this->cachePrefix = $this->tdbmSchemaAnalyzer->getCachePrefix();
186
+
187
+		$this->toSaveObjects = new \SplObjectStorage();
188
+		$logger = $configuration->getLogger();
189
+		if ($logger === null) {
190
+			$this->logger = new NullLogger();
191
+			$this->rootLogger = new NullLogger();
192
+		} else {
193
+			$this->rootLogger = $logger;
194
+			$this->setLogLevel(LogLevel::WARNING);
195
+		}
196
+		$this->orderByAnalyzer = new OrderByAnalyzer($this->cache, $this->cachePrefix);
197
+		$this->beanNamespace = $configuration->getBeanNamespace();
198
+		$this->namingStrategy = $configuration->getNamingStrategy();
199
+		$this->configuration = $configuration;
200
+	}
201
+
202
+	/**
203
+	 * Returns the object used to connect to the database.
204
+	 *
205
+	 * @return Connection
206
+	 */
207
+	public function getConnection(): Connection
208
+	{
209
+		return $this->connection;
210
+	}
211
+
212
+	/**
213
+	 * Sets the default fetch mode of the result sets returned by `findObjects`.
214
+	 * Can be one of: TDBMObjectArray::MODE_CURSOR or TDBMObjectArray::MODE_ARRAY.
215
+	 *
216
+	 * In 'MODE_ARRAY' mode (default), the result is a ResultIterator object that behaves like an array. Use this mode by default (unless the list returned is very big).
217
+	 * In 'MODE_CURSOR' mode, the result is a ResultIterator object. If you scan it many times (by calling several time a foreach loop), the query will be run
218
+	 * several times. In cursor mode, you cannot access the result set by key. Use this mode for large datasets processed by batch.
219
+	 *
220
+	 * @param int $mode
221
+	 *
222
+	 * @return $this
223
+	 *
224
+	 * @throws TDBMException
225
+	 */
226
+	public function setFetchMode($mode)
227
+	{
228
+		if ($mode !== self::MODE_CURSOR && $mode !== self::MODE_ARRAY) {
229
+			throw new TDBMException("Unknown fetch mode: '".$this->mode."'");
230
+		}
231
+		$this->mode = $mode;
232
+
233
+		return $this;
234
+	}
235
+
236
+	/**
237
+	 * Removes the given object from database.
238
+	 * This cannot be called on an object that is not attached to this TDBMService
239
+	 * (will throw a TDBMInvalidOperationException).
240
+	 *
241
+	 * @param AbstractTDBMObject $object the object to delete
242
+	 *
243
+	 * @throws TDBMException
244
+	 * @throws TDBMInvalidOperationException
245
+	 */
246
+	public function delete(AbstractTDBMObject $object)
247
+	{
248
+		switch ($object->_getStatus()) {
249
+			case TDBMObjectStateEnum::STATE_DELETED:
250
+				// Nothing to do, object already deleted.
251
+				return;
252
+			case TDBMObjectStateEnum::STATE_DETACHED:
253
+				throw new TDBMInvalidOperationException('Cannot delete a detached object');
254
+			case TDBMObjectStateEnum::STATE_NEW:
255
+				$this->deleteManyToManyRelationships($object);
256
+				foreach ($object->_getDbRows() as $dbRow) {
257
+					$this->removeFromToSaveObjectList($dbRow);
258
+				}
259
+				break;
260
+			case TDBMObjectStateEnum::STATE_DIRTY:
261
+				foreach ($object->_getDbRows() as $dbRow) {
262
+					$this->removeFromToSaveObjectList($dbRow);
263
+				}
264
+				// And continue deleting...
265
+			case TDBMObjectStateEnum::STATE_NOT_LOADED:
266
+			case TDBMObjectStateEnum::STATE_LOADED:
267
+				$this->deleteManyToManyRelationships($object);
268
+				// Let's delete db rows, in reverse order.
269
+				foreach (array_reverse($object->_getDbRows()) as $dbRow) {
270
+					$tableName = $dbRow->_getDbTableName();
271
+					$primaryKeys = $dbRow->_getPrimaryKeys();
272
+					$this->connection->delete($tableName, $primaryKeys);
273
+					$this->objectStorage->remove($dbRow->_getDbTableName(), $this->getObjectHash($primaryKeys));
274
+				}
275
+				break;
276
+			// @codeCoverageIgnoreStart
277
+			default:
278
+				throw new TDBMInvalidOperationException('Unexpected status for bean');
279
+			// @codeCoverageIgnoreEnd
280
+		}
281
+
282
+		$object->_setStatus(TDBMObjectStateEnum::STATE_DELETED);
283
+	}
284
+
285
+	/**
286
+	 * Removes all many to many relationships for this object.
287
+	 *
288
+	 * @param AbstractTDBMObject $object
289
+	 */
290
+	private function deleteManyToManyRelationships(AbstractTDBMObject $object)
291
+	{
292
+		foreach ($object->_getDbRows() as $tableName => $dbRow) {
293
+			$pivotTables = $this->tdbmSchemaAnalyzer->getPivotTableLinkedToTable($tableName);
294
+			foreach ($pivotTables as $pivotTable) {
295
+				$remoteBeans = $object->_getRelationships($pivotTable);
296
+				foreach ($remoteBeans as $remoteBean) {
297
+					$object->_removeRelationship($pivotTable, $remoteBean);
298
+				}
299
+			}
300
+		}
301
+		$this->persistManyToManyRelationships($object);
302
+	}
303
+
304
+	/**
305
+	 * This function removes the given object from the database. It will also remove all objects relied to the one given
306
+	 * by parameter before all.
307
+	 *
308
+	 * Notice: if the object has a multiple primary key, the function will not work.
309
+	 *
310
+	 * @param AbstractTDBMObject $objToDelete
311
+	 */
312
+	public function deleteCascade(AbstractTDBMObject $objToDelete)
313
+	{
314
+		$this->deleteAllConstraintWithThisObject($objToDelete);
315
+		$this->delete($objToDelete);
316
+	}
317
+
318
+	/**
319
+	 * This function is used only in TDBMService (private function)
320
+	 * It will call deleteCascade function foreach object relied with a foreign key to the object given by parameter.
321
+	 *
322
+	 * @param AbstractTDBMObject $obj
323
+	 */
324
+	private function deleteAllConstraintWithThisObject(AbstractTDBMObject $obj)
325
+	{
326
+		$dbRows = $obj->_getDbRows();
327
+		foreach ($dbRows as $dbRow) {
328
+			$tableName = $dbRow->_getDbTableName();
329
+			$pks = array_values($dbRow->_getPrimaryKeys());
330
+			if (!empty($pks)) {
331
+				$incomingFks = $this->tdbmSchemaAnalyzer->getIncomingForeignKeys($tableName);
332
+
333
+				foreach ($incomingFks as $incomingFk) {
334
+					$filter = array_combine($incomingFk->getLocalColumns(), $pks);
335
+
336
+					$results = $this->findObjects($incomingFk->getLocalTableName(), $filter);
337
+
338
+					foreach ($results as $bean) {
339
+						$this->deleteCascade($bean);
340
+					}
341
+				}
342
+			}
343
+		}
344
+	}
345
+
346
+	/**
347
+	 * This function performs a save() of all the objects that have been modified.
348
+	 */
349
+	public function completeSave()
350
+	{
351
+		foreach ($this->toSaveObjects as $dbRow) {
352
+			$this->save($dbRow->getTDBMObject());
353
+		}
354
+	}
355
+
356
+	/**
357
+	 * Takes in input a filter_bag (which can be about anything from a string to an array of TDBMObjects... see above from documentation),
358
+	 * and gives back a proper Filter object.
359
+	 *
360
+	 * @param mixed $filter_bag
361
+	 * @param int   $counter
362
+	 *
363
+	 * @return array First item: filter string, second item: parameters
364
+	 *
365
+	 * @throws TDBMException
366
+	 */
367
+	public function buildFilterFromFilterBag($filter_bag, $counter = 1)
368
+	{
369
+		if ($filter_bag === null) {
370
+			return ['', []];
371
+		} elseif (is_string($filter_bag)) {
372
+			return [$filter_bag, []];
373
+		} elseif (is_array($filter_bag)) {
374
+			$sqlParts = [];
375
+			$parameters = [];
376
+			foreach ($filter_bag as $column => $value) {
377
+				if (is_int($column)) {
378
+					list($subSqlPart, $subParameters) = $this->buildFilterFromFilterBag($value, $counter);
379
+					$sqlParts[] = $subSqlPart;
380
+					$parameters += $subParameters;
381
+				} else {
382
+					$paramName = 'tdbmparam'.$counter;
383
+					if (is_array($value)) {
384
+						$sqlParts[] = $this->connection->quoteIdentifier($column).' IN :'.$paramName;
385
+					} else {
386
+						$sqlParts[] = $this->connection->quoteIdentifier($column).' = :'.$paramName;
387
+					}
388
+					$parameters[$paramName] = $value;
389
+					++$counter;
390
+				}
391
+			}
392
+
393
+			return [implode(' AND ', $sqlParts), $parameters];
394
+		} elseif ($filter_bag instanceof AbstractTDBMObject) {
395
+			$sqlParts = [];
396
+			$parameters = [];
397
+			$dbRows = $filter_bag->_getDbRows();
398
+			$dbRow = reset($dbRows);
399
+			$primaryKeys = $dbRow->_getPrimaryKeys();
400
+
401
+			foreach ($primaryKeys as $column => $value) {
402
+				$paramName = 'tdbmparam'.$counter;
403
+				$sqlParts[] = $this->connection->quoteIdentifier($dbRow->_getDbTableName()).'.'.$this->connection->quoteIdentifier($column).' = :'.$paramName;
404
+				$parameters[$paramName] = $value;
405
+				++$counter;
406
+			}
407
+
408
+			return [implode(' AND ', $sqlParts), $parameters];
409
+		} elseif ($filter_bag instanceof \Iterator) {
410
+			return $this->buildFilterFromFilterBag(iterator_to_array($filter_bag), $counter);
411
+		} else {
412
+			throw new TDBMException('Error in filter. An object has been passed that is neither a SQL string, nor an array, nor a bean, nor null.');
413
+		}
414
+	}
415
+
416
+	/**
417
+	 * @param string $table
418
+	 *
419
+	 * @return string[]
420
+	 */
421
+	public function getPrimaryKeyColumns($table)
422
+	{
423
+		if (!isset($this->primaryKeysColumns[$table])) {
424
+			$this->primaryKeysColumns[$table] = $this->tdbmSchemaAnalyzer->getSchema()->getTable($table)->getPrimaryKeyColumns();
425
+
426
+			// TODO TDBM4: See if we need to improve error reporting if table name does not exist.
427
+
428
+			/*$arr = array();
429 429
             foreach ($this->connection->getPrimaryKey($table) as $col) {
430 430
                 $arr[] = $col->name;
431 431
             }
@@ -446,161 +446,161 @@  discard block
 block discarded – undo
446 446
                     throw new TDBMException($str);
447 447
                 }
448 448
             }*/
449
-        }
450
-
451
-        return $this->primaryKeysColumns[$table];
452
-    }
453
-
454
-    /**
455
-     * This is an internal function, you should not use it in your application.
456
-     * This is used internally by TDBM to add an object to the object cache.
457
-     *
458
-     * @param DbRow $dbRow
459
-     */
460
-    public function _addToCache(DbRow $dbRow)
461
-    {
462
-        $primaryKey = $this->getPrimaryKeysForObjectFromDbRow($dbRow);
463
-        $hash = $this->getObjectHash($primaryKey);
464
-        $this->objectStorage->set($dbRow->_getDbTableName(), $hash, $dbRow);
465
-    }
466
-
467
-    /**
468
-     * This is an internal function, you should not use it in your application.
469
-     * This is used internally by TDBM to remove the object from the list of objects that have been
470
-     * created/updated but not saved yet.
471
-     *
472
-     * @param DbRow $myObject
473
-     */
474
-    private function removeFromToSaveObjectList(DbRow $myObject)
475
-    {
476
-        unset($this->toSaveObjects[$myObject]);
477
-    }
478
-
479
-    /**
480
-     * This is an internal function, you should not use it in your application.
481
-     * This is used internally by TDBM to add an object to the list of objects that have been
482
-     * created/updated but not saved yet.
483
-     *
484
-     * @param DbRow $myObject
485
-     */
486
-    public function _addToToSaveObjectList(DbRow $myObject)
487
-    {
488
-        $this->toSaveObjects[$myObject] = true;
489
-    }
490
-
491
-    /**
492
-     * Generates all the daos and beans.
493
-     *
494
-     * @param string $composerFile        If it's set, location of custom Composer file. Relative to project root
495
-     *
496
-     * @return \string[] the list of tables (key) and bean name (value)
497
-     */
498
-    public function generateAllDaosAndBeans($composerFile = null)
499
-    {
500
-        // Purge cache before generating anything.
501
-        $this->cache->deleteAll();
502
-
503
-        $tdbmDaoGenerator = new TDBMDaoGenerator($this->configuration, $this->tdbmSchemaAnalyzer->getSchema(), $this->tdbmSchemaAnalyzer);
504
-        if (null !== $composerFile) {
505
-            $tdbmDaoGenerator->setComposerFile(__DIR__.'/../../../../../../../'.$composerFile);
506
-        }
507
-
508
-        $tdbmDaoGenerator->generateAllDaosAndBeans();
509
-    }
510
-
511
-    /**
512
-     * Returns the fully qualified class name of the bean associated with table $tableName.
513
-     *
514
-     *
515
-     * @param string $tableName
516
-     *
517
-     * @return string
518
-     */
519
-    public function getBeanClassName(string $tableName) : string
520
-    {
521
-        if (isset($this->tableToBeanMap[$tableName])) {
522
-            return $this->tableToBeanMap[$tableName];
523
-        } else {
524
-            $className = $this->beanNamespace.'\\'.$this->namingStrategy->getBeanClassName($tableName);
525
-
526
-            if (!class_exists($className)) {
527
-                throw new TDBMInvalidArgumentException(sprintf('Could not find class "%s". Does table "%s" exist? If yes, consider regenerating the DAOs and beans.', $className, $tableName));
528
-            }
529
-
530
-            $this->tableToBeanMap[$tableName] = $className;
531
-            return $className;
532
-        }
533
-    }
534
-
535
-    /**
536
-     * Saves $object by INSERTing or UPDAT(E)ing it in the database.
537
-     *
538
-     * @param AbstractTDBMObject $object
539
-     *
540
-     * @throws TDBMException
541
-     */
542
-    public function save(AbstractTDBMObject $object)
543
-    {
544
-        $status = $object->_getStatus();
545
-
546
-        if ($status === null) {
547
-            throw new TDBMException(sprintf('Your bean for class %s has no status. It is likely that you overloaded the __construct method and forgot to call parent::__construct.', get_class($object)));
548
-        }
549
-
550
-        // Let's attach this object if it is in detached state.
551
-        if ($status === TDBMObjectStateEnum::STATE_DETACHED) {
552
-            $object->_attach($this);
553
-            $status = $object->_getStatus();
554
-        }
555
-
556
-        if ($status === TDBMObjectStateEnum::STATE_NEW) {
557
-            $dbRows = $object->_getDbRows();
558
-
559
-            $unindexedPrimaryKeys = array();
560
-
561
-            foreach ($dbRows as $dbRow) {
562
-                if ($dbRow->_getStatus() == TDBMObjectStateEnum::STATE_SAVING) {
563
-                    throw TDBMCyclicReferenceException::createCyclicReference($dbRow->_getDbTableName(), $object);
564
-                }
565
-                $dbRow->_setStatus(TDBMObjectStateEnum::STATE_SAVING);
566
-                $tableName = $dbRow->_getDbTableName();
567
-
568
-                $schema = $this->tdbmSchemaAnalyzer->getSchema();
569
-                $tableDescriptor = $schema->getTable($tableName);
570
-
571
-                $primaryKeyColumns = $this->getPrimaryKeyColumns($tableName);
572
-
573
-                $references = $dbRow->_getReferences();
574
-
575
-                // Let's save all references in NEW or DETACHED state (we need their primary key)
576
-                foreach ($references as $fkName => $reference) {
577
-                    if ($reference !== null) {
578
-                        $refStatus = $reference->_getStatus();
579
-                        if ($refStatus === TDBMObjectStateEnum::STATE_NEW || $refStatus === TDBMObjectStateEnum::STATE_DETACHED) {
580
-                            try {
581
-                                $this->save($reference);
582
-                            } catch (TDBMCyclicReferenceException $e) {
583
-                                throw TDBMCyclicReferenceException::extendCyclicReference($e, $dbRow->_getDbTableName(), $object, $fkName);
584
-                            }
585
-                        }
586
-                    }
587
-                }
588
-
589
-                if (empty($unindexedPrimaryKeys)) {
590
-                    $primaryKeys = $this->getPrimaryKeysForObjectFromDbRow($dbRow);
591
-                } else {
592
-                    // First insert, the children must have the same primary key as the parent.
593
-                    $primaryKeys = $this->_getPrimaryKeysFromIndexedPrimaryKeys($tableName, $unindexedPrimaryKeys);
594
-                    $dbRow->_setPrimaryKeys($primaryKeys);
595
-                }
596
-
597
-                $dbRowData = $dbRow->_getDbRow();
598
-
599
-                // Let's see if the columns for primary key have been set before inserting.
600
-                // We assume that if one of the value of the PK has been set, the PK is set.
601
-                $isPkSet = !empty($primaryKeys);
602
-
603
-                /*if (!$isPkSet) {
449
+		}
450
+
451
+		return $this->primaryKeysColumns[$table];
452
+	}
453
+
454
+	/**
455
+	 * This is an internal function, you should not use it in your application.
456
+	 * This is used internally by TDBM to add an object to the object cache.
457
+	 *
458
+	 * @param DbRow $dbRow
459
+	 */
460
+	public function _addToCache(DbRow $dbRow)
461
+	{
462
+		$primaryKey = $this->getPrimaryKeysForObjectFromDbRow($dbRow);
463
+		$hash = $this->getObjectHash($primaryKey);
464
+		$this->objectStorage->set($dbRow->_getDbTableName(), $hash, $dbRow);
465
+	}
466
+
467
+	/**
468
+	 * This is an internal function, you should not use it in your application.
469
+	 * This is used internally by TDBM to remove the object from the list of objects that have been
470
+	 * created/updated but not saved yet.
471
+	 *
472
+	 * @param DbRow $myObject
473
+	 */
474
+	private function removeFromToSaveObjectList(DbRow $myObject)
475
+	{
476
+		unset($this->toSaveObjects[$myObject]);
477
+	}
478
+
479
+	/**
480
+	 * This is an internal function, you should not use it in your application.
481
+	 * This is used internally by TDBM to add an object to the list of objects that have been
482
+	 * created/updated but not saved yet.
483
+	 *
484
+	 * @param DbRow $myObject
485
+	 */
486
+	public function _addToToSaveObjectList(DbRow $myObject)
487
+	{
488
+		$this->toSaveObjects[$myObject] = true;
489
+	}
490
+
491
+	/**
492
+	 * Generates all the daos and beans.
493
+	 *
494
+	 * @param string $composerFile        If it's set, location of custom Composer file. Relative to project root
495
+	 *
496
+	 * @return \string[] the list of tables (key) and bean name (value)
497
+	 */
498
+	public function generateAllDaosAndBeans($composerFile = null)
499
+	{
500
+		// Purge cache before generating anything.
501
+		$this->cache->deleteAll();
502
+
503
+		$tdbmDaoGenerator = new TDBMDaoGenerator($this->configuration, $this->tdbmSchemaAnalyzer->getSchema(), $this->tdbmSchemaAnalyzer);
504
+		if (null !== $composerFile) {
505
+			$tdbmDaoGenerator->setComposerFile(__DIR__.'/../../../../../../../'.$composerFile);
506
+		}
507
+
508
+		$tdbmDaoGenerator->generateAllDaosAndBeans();
509
+	}
510
+
511
+	/**
512
+	 * Returns the fully qualified class name of the bean associated with table $tableName.
513
+	 *
514
+	 *
515
+	 * @param string $tableName
516
+	 *
517
+	 * @return string
518
+	 */
519
+	public function getBeanClassName(string $tableName) : string
520
+	{
521
+		if (isset($this->tableToBeanMap[$tableName])) {
522
+			return $this->tableToBeanMap[$tableName];
523
+		} else {
524
+			$className = $this->beanNamespace.'\\'.$this->namingStrategy->getBeanClassName($tableName);
525
+
526
+			if (!class_exists($className)) {
527
+				throw new TDBMInvalidArgumentException(sprintf('Could not find class "%s". Does table "%s" exist? If yes, consider regenerating the DAOs and beans.', $className, $tableName));
528
+			}
529
+
530
+			$this->tableToBeanMap[$tableName] = $className;
531
+			return $className;
532
+		}
533
+	}
534
+
535
+	/**
536
+	 * Saves $object by INSERTing or UPDAT(E)ing it in the database.
537
+	 *
538
+	 * @param AbstractTDBMObject $object
539
+	 *
540
+	 * @throws TDBMException
541
+	 */
542
+	public function save(AbstractTDBMObject $object)
543
+	{
544
+		$status = $object->_getStatus();
545
+
546
+		if ($status === null) {
547
+			throw new TDBMException(sprintf('Your bean for class %s has no status. It is likely that you overloaded the __construct method and forgot to call parent::__construct.', get_class($object)));
548
+		}
549
+
550
+		// Let's attach this object if it is in detached state.
551
+		if ($status === TDBMObjectStateEnum::STATE_DETACHED) {
552
+			$object->_attach($this);
553
+			$status = $object->_getStatus();
554
+		}
555
+
556
+		if ($status === TDBMObjectStateEnum::STATE_NEW) {
557
+			$dbRows = $object->_getDbRows();
558
+
559
+			$unindexedPrimaryKeys = array();
560
+
561
+			foreach ($dbRows as $dbRow) {
562
+				if ($dbRow->_getStatus() == TDBMObjectStateEnum::STATE_SAVING) {
563
+					throw TDBMCyclicReferenceException::createCyclicReference($dbRow->_getDbTableName(), $object);
564
+				}
565
+				$dbRow->_setStatus(TDBMObjectStateEnum::STATE_SAVING);
566
+				$tableName = $dbRow->_getDbTableName();
567
+
568
+				$schema = $this->tdbmSchemaAnalyzer->getSchema();
569
+				$tableDescriptor = $schema->getTable($tableName);
570
+
571
+				$primaryKeyColumns = $this->getPrimaryKeyColumns($tableName);
572
+
573
+				$references = $dbRow->_getReferences();
574
+
575
+				// Let's save all references in NEW or DETACHED state (we need their primary key)
576
+				foreach ($references as $fkName => $reference) {
577
+					if ($reference !== null) {
578
+						$refStatus = $reference->_getStatus();
579
+						if ($refStatus === TDBMObjectStateEnum::STATE_NEW || $refStatus === TDBMObjectStateEnum::STATE_DETACHED) {
580
+							try {
581
+								$this->save($reference);
582
+							} catch (TDBMCyclicReferenceException $e) {
583
+								throw TDBMCyclicReferenceException::extendCyclicReference($e, $dbRow->_getDbTableName(), $object, $fkName);
584
+							}
585
+						}
586
+					}
587
+				}
588
+
589
+				if (empty($unindexedPrimaryKeys)) {
590
+					$primaryKeys = $this->getPrimaryKeysForObjectFromDbRow($dbRow);
591
+				} else {
592
+					// First insert, the children must have the same primary key as the parent.
593
+					$primaryKeys = $this->_getPrimaryKeysFromIndexedPrimaryKeys($tableName, $unindexedPrimaryKeys);
594
+					$dbRow->_setPrimaryKeys($primaryKeys);
595
+				}
596
+
597
+				$dbRowData = $dbRow->_getDbRow();
598
+
599
+				// Let's see if the columns for primary key have been set before inserting.
600
+				// We assume that if one of the value of the PK has been set, the PK is set.
601
+				$isPkSet = !empty($primaryKeys);
602
+
603
+				/*if (!$isPkSet) {
604 604
                     // if there is no autoincrement and no pk set, let's go in error.
605 605
                     $isAutoIncrement = true;
606 606
 
@@ -618,30 +618,30 @@  discard block
 block discarded – undo
618 618
 
619 619
                 }*/
620 620
 
621
-                $types = [];
622
-                $escapedDbRowData = [];
621
+				$types = [];
622
+				$escapedDbRowData = [];
623 623
 
624
-                foreach ($dbRowData as $columnName => $value) {
625
-                    $columnDescriptor = $tableDescriptor->getColumn($columnName);
626
-                    $types[] = $columnDescriptor->getType();
627
-                    $escapedDbRowData[$this->connection->quoteIdentifier($columnName)] = $value;
628
-                }
624
+				foreach ($dbRowData as $columnName => $value) {
625
+					$columnDescriptor = $tableDescriptor->getColumn($columnName);
626
+					$types[] = $columnDescriptor->getType();
627
+					$escapedDbRowData[$this->connection->quoteIdentifier($columnName)] = $value;
628
+				}
629 629
 
630
-                $this->connection->insert($tableName, $escapedDbRowData, $types);
630
+				$this->connection->insert($tableName, $escapedDbRowData, $types);
631 631
 
632
-                if (!$isPkSet && count($primaryKeyColumns) == 1) {
633
-                    $id = $this->connection->lastInsertId();
634
-                    $pkColumn = $primaryKeyColumns[0];
635
-                    // lastInsertId returns a string but the column type is usually a int. Let's convert it back to the correct type.
636
-                    $id = $tableDescriptor->getColumn($pkColumn)->getType()->convertToPHPValue($id, $this->getConnection()->getDatabasePlatform());
637
-                    $primaryKeys[$pkColumn] = $id;
638
-                }
632
+				if (!$isPkSet && count($primaryKeyColumns) == 1) {
633
+					$id = $this->connection->lastInsertId();
634
+					$pkColumn = $primaryKeyColumns[0];
635
+					// lastInsertId returns a string but the column type is usually a int. Let's convert it back to the correct type.
636
+					$id = $tableDescriptor->getColumn($pkColumn)->getType()->convertToPHPValue($id, $this->getConnection()->getDatabasePlatform());
637
+					$primaryKeys[$pkColumn] = $id;
638
+				}
639 639
 
640
-                // TODO: change this to some private magic accessor in future
641
-                $dbRow->_setPrimaryKeys($primaryKeys);
642
-                $unindexedPrimaryKeys = array_values($primaryKeys);
640
+				// TODO: change this to some private magic accessor in future
641
+				$dbRow->_setPrimaryKeys($primaryKeys);
642
+				$unindexedPrimaryKeys = array_values($primaryKeys);
643 643
 
644
-                /*
644
+				/*
645 645
                  * When attached, on "save", we check if the column updated is part of a primary key
646 646
                  * If this is part of a primary key, we call the _update_id method that updates the id in the list of known objects.
647 647
                  * This method should first verify that the id is not already used (and is not auto-incremented)
@@ -651,7 +651,7 @@  discard block
 block discarded – undo
651 651
                  *
652 652
                  */
653 653
 
654
-                /*try {
654
+				/*try {
655 655
                     $this->db_connection->exec($sql);
656 656
                 } catch (TDBMException $e) {
657 657
                     $this->db_onerror = true;
@@ -670,405 +670,405 @@  discard block
 block discarded – undo
670 670
                     }
671 671
                 }*/
672 672
 
673
-                // Let's remove this object from the $new_objects static table.
674
-                $this->removeFromToSaveObjectList($dbRow);
675
-
676
-                // TODO: change this behaviour to something more sensible performance-wise
677
-                // Maybe a setting to trigger this globally?
678
-                //$this->status = TDBMObjectStateEnum::STATE_NOT_LOADED;
679
-                //$this->db_modified_state = false;
680
-                //$dbRow = array();
681
-
682
-                // Let's add this object to the list of objects in cache.
683
-                $this->_addToCache($dbRow);
684
-            }
685
-
686
-            $object->_setStatus(TDBMObjectStateEnum::STATE_LOADED);
687
-        } elseif ($status === TDBMObjectStateEnum::STATE_DIRTY) {
688
-            $dbRows = $object->_getDbRows();
689
-
690
-            foreach ($dbRows as $dbRow) {
691
-                $references = $dbRow->_getReferences();
692
-
693
-                // Let's save all references in NEW state (we need their primary key)
694
-                foreach ($references as $fkName => $reference) {
695
-                    if ($reference !== null && $reference->_getStatus() === TDBMObjectStateEnum::STATE_NEW) {
696
-                        $this->save($reference);
697
-                    }
698
-                }
699
-
700
-                // Let's first get the primary keys
701
-                $tableName = $dbRow->_getDbTableName();
702
-                $dbRowData = $dbRow->_getDbRow();
703
-
704
-                $schema = $this->tdbmSchemaAnalyzer->getSchema();
705
-                $tableDescriptor = $schema->getTable($tableName);
706
-
707
-                $primaryKeys = $dbRow->_getPrimaryKeys();
708
-
709
-                $types = [];
710
-                $escapedDbRowData = [];
711
-                $escapedPrimaryKeys = [];
712
-
713
-                foreach ($dbRowData as $columnName => $value) {
714
-                    $columnDescriptor = $tableDescriptor->getColumn($columnName);
715
-                    $types[] = $columnDescriptor->getType();
716
-                    $escapedDbRowData[$this->connection->quoteIdentifier($columnName)] = $value;
717
-                }
718
-                foreach ($primaryKeys as $columnName => $value) {
719
-                    $columnDescriptor = $tableDescriptor->getColumn($columnName);
720
-                    $types[] = $columnDescriptor->getType();
721
-                    $escapedPrimaryKeys[$this->connection->quoteIdentifier($columnName)] = $value;
722
-                }
723
-
724
-                $this->connection->update($tableName, $escapedDbRowData, $escapedPrimaryKeys, $types);
725
-
726
-                // Let's check if the primary key has been updated...
727
-                $needsUpdatePk = false;
728
-                foreach ($primaryKeys as $column => $value) {
729
-                    if (!isset($dbRowData[$column]) || $dbRowData[$column] != $value) {
730
-                        $needsUpdatePk = true;
731
-                        break;
732
-                    }
733
-                }
734
-                if ($needsUpdatePk) {
735
-                    $this->objectStorage->remove($tableName, $this->getObjectHash($primaryKeys));
736
-                    $newPrimaryKeys = $this->getPrimaryKeysForObjectFromDbRow($dbRow);
737
-                    $dbRow->_setPrimaryKeys($newPrimaryKeys);
738
-                    $this->objectStorage->set($tableName, $this->getObjectHash($primaryKeys), $dbRow);
739
-                }
740
-
741
-                // Let's remove this object from the list of objects to save.
742
-                $this->removeFromToSaveObjectList($dbRow);
743
-            }
744
-
745
-            $object->_setStatus(TDBMObjectStateEnum::STATE_LOADED);
746
-        } elseif ($status === TDBMObjectStateEnum::STATE_DELETED) {
747
-            throw new TDBMInvalidOperationException('This object has been deleted. It cannot be saved.');
748
-        }
749
-
750
-        // Finally, let's save all the many to many relationships to this bean.
751
-        $this->persistManyToManyRelationships($object);
752
-    }
753
-
754
-    private function persistManyToManyRelationships(AbstractTDBMObject $object)
755
-    {
756
-        foreach ($object->_getCachedRelationships() as $pivotTableName => $storage) {
757
-            $tableDescriptor = $this->tdbmSchemaAnalyzer->getSchema()->getTable($pivotTableName);
758
-            list($localFk, $remoteFk) = $this->getPivotTableForeignKeys($pivotTableName, $object);
759
-
760
-            $toRemoveFromStorage = [];
761
-
762
-            foreach ($storage as $remoteBean) {
763
-                /* @var $remoteBean AbstractTDBMObject */
764
-                $statusArr = $storage[$remoteBean];
765
-                $status = $statusArr['status'];
766
-                $reverse = $statusArr['reverse'];
767
-                if ($reverse) {
768
-                    continue;
769
-                }
770
-
771
-                if ($status === 'new') {
772
-                    $remoteBeanStatus = $remoteBean->_getStatus();
773
-                    if ($remoteBeanStatus === TDBMObjectStateEnum::STATE_NEW || $remoteBeanStatus === TDBMObjectStateEnum::STATE_DETACHED) {
774
-                        // Let's save remote bean if needed.
775
-                        $this->save($remoteBean);
776
-                    }
777
-
778
-                    $filters = $this->getPivotFilters($object, $remoteBean, $localFk, $remoteFk);
779
-
780
-                    $types = [];
781
-                    $escapedFilters = [];
782
-
783
-                    foreach ($filters as $columnName => $value) {
784
-                        $columnDescriptor = $tableDescriptor->getColumn($columnName);
785
-                        $types[] = $columnDescriptor->getType();
786
-                        $escapedFilters[$this->connection->quoteIdentifier($columnName)] = $value;
787
-                    }
788
-
789
-                    $this->connection->insert($pivotTableName, $escapedFilters, $types);
790
-
791
-                    // Finally, let's mark relationships as saved.
792
-                    $statusArr['status'] = 'loaded';
793
-                    $storage[$remoteBean] = $statusArr;
794
-                    $remoteStorage = $remoteBean->_getCachedRelationships()[$pivotTableName];
795
-                    $remoteStatusArr = $remoteStorage[$object];
796
-                    $remoteStatusArr['status'] = 'loaded';
797
-                    $remoteStorage[$object] = $remoteStatusArr;
798
-                } elseif ($status === 'delete') {
799
-                    $filters = $this->getPivotFilters($object, $remoteBean, $localFk, $remoteFk);
800
-
801
-                    $types = [];
802
-
803
-                    foreach ($filters as $columnName => $value) {
804
-                        $columnDescriptor = $tableDescriptor->getColumn($columnName);
805
-                        $types[] = $columnDescriptor->getType();
806
-                    }
807
-
808
-                    $this->connection->delete($pivotTableName, $filters, $types);
809
-
810
-                    // Finally, let's remove relationships completely from bean.
811
-                    $toRemoveFromStorage[] = $remoteBean;
812
-
813
-                    $remoteBean->_getCachedRelationships()[$pivotTableName]->detach($object);
814
-                }
815
-            }
816
-
817
-            // Note: due to https://bugs.php.net/bug.php?id=65629, we cannot delete an element inside a foreach loop on a SplStorageObject.
818
-            // Therefore, we cache elements in the $toRemoveFromStorage to remove them at a later stage.
819
-            foreach ($toRemoveFromStorage as $remoteBean) {
820
-                $storage->detach($remoteBean);
821
-            }
822
-        }
823
-    }
824
-
825
-    private function getPivotFilters(AbstractTDBMObject $localBean, AbstractTDBMObject $remoteBean, ForeignKeyConstraint $localFk, ForeignKeyConstraint $remoteFk)
826
-    {
827
-        $localBeanPk = $this->getPrimaryKeyValues($localBean);
828
-        $remoteBeanPk = $this->getPrimaryKeyValues($remoteBean);
829
-        $localColumns = $localFk->getLocalColumns();
830
-        $remoteColumns = $remoteFk->getLocalColumns();
831
-
832
-        $localFilters = array_combine($localColumns, $localBeanPk);
833
-        $remoteFilters = array_combine($remoteColumns, $remoteBeanPk);
834
-
835
-        return array_merge($localFilters, $remoteFilters);
836
-    }
837
-
838
-    /**
839
-     * Returns the "values" of the primary key.
840
-     * This returns the primary key from the $primaryKey attribute, not the one stored in the columns.
841
-     *
842
-     * @param AbstractTDBMObject $bean
843
-     *
844
-     * @return array numerically indexed array of values
845
-     */
846
-    private function getPrimaryKeyValues(AbstractTDBMObject $bean)
847
-    {
848
-        $dbRows = $bean->_getDbRows();
849
-        $dbRow = reset($dbRows);
850
-
851
-        return array_values($dbRow->_getPrimaryKeys());
852
-    }
853
-
854
-    /**
855
-     * Returns a unique hash used to store the object based on its primary key.
856
-     * If the array contains only one value, then the value is returned.
857
-     * Otherwise, a hash representing the array is returned.
858
-     *
859
-     * @param array $primaryKeys An array of columns => values forming the primary key
860
-     *
861
-     * @return string
862
-     */
863
-    public function getObjectHash(array $primaryKeys)
864
-    {
865
-        if (count($primaryKeys) === 1) {
866
-            return reset($primaryKeys);
867
-        } else {
868
-            ksort($primaryKeys);
869
-
870
-            return md5(json_encode($primaryKeys));
871
-        }
872
-    }
873
-
874
-    /**
875
-     * Returns an array of primary keys from the object.
876
-     * The primary keys are extracted from the object columns and not from the primary keys stored in the
877
-     * $primaryKeys variable of the object.
878
-     *
879
-     * @param DbRow $dbRow
880
-     *
881
-     * @return array Returns an array of column => value
882
-     */
883
-    public function getPrimaryKeysForObjectFromDbRow(DbRow $dbRow)
884
-    {
885
-        $table = $dbRow->_getDbTableName();
886
-        $dbRowData = $dbRow->_getDbRow();
887
-
888
-        return $this->_getPrimaryKeysFromObjectData($table, $dbRowData);
889
-    }
890
-
891
-    /**
892
-     * Returns an array of primary keys for the given row.
893
-     * The primary keys are extracted from the object columns.
894
-     *
895
-     * @param $table
896
-     * @param array $columns
897
-     *
898
-     * @return array
899
-     */
900
-    public function _getPrimaryKeysFromObjectData($table, array $columns)
901
-    {
902
-        $primaryKeyColumns = $this->getPrimaryKeyColumns($table);
903
-        $values = array();
904
-        foreach ($primaryKeyColumns as $column) {
905
-            if (isset($columns[$column])) {
906
-                $values[$column] = $columns[$column];
907
-            }
908
-        }
909
-
910
-        return $values;
911
-    }
912
-
913
-    /**
914
-     * Attaches $object to this TDBMService.
915
-     * The $object must be in DETACHED state and will pass in NEW state.
916
-     *
917
-     * @param AbstractTDBMObject $object
918
-     *
919
-     * @throws TDBMInvalidOperationException
920
-     */
921
-    public function attach(AbstractTDBMObject $object)
922
-    {
923
-        $object->_attach($this);
924
-    }
925
-
926
-    /**
927
-     * Returns an associative array (column => value) for the primary keys from the table name and an
928
-     * indexed array of primary key values.
929
-     *
930
-     * @param string $tableName
931
-     * @param array  $indexedPrimaryKeys
932
-     */
933
-    public function _getPrimaryKeysFromIndexedPrimaryKeys($tableName, array $indexedPrimaryKeys)
934
-    {
935
-        $primaryKeyColumns = $this->tdbmSchemaAnalyzer->getSchema()->getTable($tableName)->getPrimaryKeyColumns();
936
-
937
-        if (count($primaryKeyColumns) !== count($indexedPrimaryKeys)) {
938
-            throw new TDBMException(sprintf('Wrong number of columns passed for primary key. Expected %s columns for table "%s",
673
+				// Let's remove this object from the $new_objects static table.
674
+				$this->removeFromToSaveObjectList($dbRow);
675
+
676
+				// TODO: change this behaviour to something more sensible performance-wise
677
+				// Maybe a setting to trigger this globally?
678
+				//$this->status = TDBMObjectStateEnum::STATE_NOT_LOADED;
679
+				//$this->db_modified_state = false;
680
+				//$dbRow = array();
681
+
682
+				// Let's add this object to the list of objects in cache.
683
+				$this->_addToCache($dbRow);
684
+			}
685
+
686
+			$object->_setStatus(TDBMObjectStateEnum::STATE_LOADED);
687
+		} elseif ($status === TDBMObjectStateEnum::STATE_DIRTY) {
688
+			$dbRows = $object->_getDbRows();
689
+
690
+			foreach ($dbRows as $dbRow) {
691
+				$references = $dbRow->_getReferences();
692
+
693
+				// Let's save all references in NEW state (we need their primary key)
694
+				foreach ($references as $fkName => $reference) {
695
+					if ($reference !== null && $reference->_getStatus() === TDBMObjectStateEnum::STATE_NEW) {
696
+						$this->save($reference);
697
+					}
698
+				}
699
+
700
+				// Let's first get the primary keys
701
+				$tableName = $dbRow->_getDbTableName();
702
+				$dbRowData = $dbRow->_getDbRow();
703
+
704
+				$schema = $this->tdbmSchemaAnalyzer->getSchema();
705
+				$tableDescriptor = $schema->getTable($tableName);
706
+
707
+				$primaryKeys = $dbRow->_getPrimaryKeys();
708
+
709
+				$types = [];
710
+				$escapedDbRowData = [];
711
+				$escapedPrimaryKeys = [];
712
+
713
+				foreach ($dbRowData as $columnName => $value) {
714
+					$columnDescriptor = $tableDescriptor->getColumn($columnName);
715
+					$types[] = $columnDescriptor->getType();
716
+					$escapedDbRowData[$this->connection->quoteIdentifier($columnName)] = $value;
717
+				}
718
+				foreach ($primaryKeys as $columnName => $value) {
719
+					$columnDescriptor = $tableDescriptor->getColumn($columnName);
720
+					$types[] = $columnDescriptor->getType();
721
+					$escapedPrimaryKeys[$this->connection->quoteIdentifier($columnName)] = $value;
722
+				}
723
+
724
+				$this->connection->update($tableName, $escapedDbRowData, $escapedPrimaryKeys, $types);
725
+
726
+				// Let's check if the primary key has been updated...
727
+				$needsUpdatePk = false;
728
+				foreach ($primaryKeys as $column => $value) {
729
+					if (!isset($dbRowData[$column]) || $dbRowData[$column] != $value) {
730
+						$needsUpdatePk = true;
731
+						break;
732
+					}
733
+				}
734
+				if ($needsUpdatePk) {
735
+					$this->objectStorage->remove($tableName, $this->getObjectHash($primaryKeys));
736
+					$newPrimaryKeys = $this->getPrimaryKeysForObjectFromDbRow($dbRow);
737
+					$dbRow->_setPrimaryKeys($newPrimaryKeys);
738
+					$this->objectStorage->set($tableName, $this->getObjectHash($primaryKeys), $dbRow);
739
+				}
740
+
741
+				// Let's remove this object from the list of objects to save.
742
+				$this->removeFromToSaveObjectList($dbRow);
743
+			}
744
+
745
+			$object->_setStatus(TDBMObjectStateEnum::STATE_LOADED);
746
+		} elseif ($status === TDBMObjectStateEnum::STATE_DELETED) {
747
+			throw new TDBMInvalidOperationException('This object has been deleted. It cannot be saved.');
748
+		}
749
+
750
+		// Finally, let's save all the many to many relationships to this bean.
751
+		$this->persistManyToManyRelationships($object);
752
+	}
753
+
754
+	private function persistManyToManyRelationships(AbstractTDBMObject $object)
755
+	{
756
+		foreach ($object->_getCachedRelationships() as $pivotTableName => $storage) {
757
+			$tableDescriptor = $this->tdbmSchemaAnalyzer->getSchema()->getTable($pivotTableName);
758
+			list($localFk, $remoteFk) = $this->getPivotTableForeignKeys($pivotTableName, $object);
759
+
760
+			$toRemoveFromStorage = [];
761
+
762
+			foreach ($storage as $remoteBean) {
763
+				/* @var $remoteBean AbstractTDBMObject */
764
+				$statusArr = $storage[$remoteBean];
765
+				$status = $statusArr['status'];
766
+				$reverse = $statusArr['reverse'];
767
+				if ($reverse) {
768
+					continue;
769
+				}
770
+
771
+				if ($status === 'new') {
772
+					$remoteBeanStatus = $remoteBean->_getStatus();
773
+					if ($remoteBeanStatus === TDBMObjectStateEnum::STATE_NEW || $remoteBeanStatus === TDBMObjectStateEnum::STATE_DETACHED) {
774
+						// Let's save remote bean if needed.
775
+						$this->save($remoteBean);
776
+					}
777
+
778
+					$filters = $this->getPivotFilters($object, $remoteBean, $localFk, $remoteFk);
779
+
780
+					$types = [];
781
+					$escapedFilters = [];
782
+
783
+					foreach ($filters as $columnName => $value) {
784
+						$columnDescriptor = $tableDescriptor->getColumn($columnName);
785
+						$types[] = $columnDescriptor->getType();
786
+						$escapedFilters[$this->connection->quoteIdentifier($columnName)] = $value;
787
+					}
788
+
789
+					$this->connection->insert($pivotTableName, $escapedFilters, $types);
790
+
791
+					// Finally, let's mark relationships as saved.
792
+					$statusArr['status'] = 'loaded';
793
+					$storage[$remoteBean] = $statusArr;
794
+					$remoteStorage = $remoteBean->_getCachedRelationships()[$pivotTableName];
795
+					$remoteStatusArr = $remoteStorage[$object];
796
+					$remoteStatusArr['status'] = 'loaded';
797
+					$remoteStorage[$object] = $remoteStatusArr;
798
+				} elseif ($status === 'delete') {
799
+					$filters = $this->getPivotFilters($object, $remoteBean, $localFk, $remoteFk);
800
+
801
+					$types = [];
802
+
803
+					foreach ($filters as $columnName => $value) {
804
+						$columnDescriptor = $tableDescriptor->getColumn($columnName);
805
+						$types[] = $columnDescriptor->getType();
806
+					}
807
+
808
+					$this->connection->delete($pivotTableName, $filters, $types);
809
+
810
+					// Finally, let's remove relationships completely from bean.
811
+					$toRemoveFromStorage[] = $remoteBean;
812
+
813
+					$remoteBean->_getCachedRelationships()[$pivotTableName]->detach($object);
814
+				}
815
+			}
816
+
817
+			// Note: due to https://bugs.php.net/bug.php?id=65629, we cannot delete an element inside a foreach loop on a SplStorageObject.
818
+			// Therefore, we cache elements in the $toRemoveFromStorage to remove them at a later stage.
819
+			foreach ($toRemoveFromStorage as $remoteBean) {
820
+				$storage->detach($remoteBean);
821
+			}
822
+		}
823
+	}
824
+
825
+	private function getPivotFilters(AbstractTDBMObject $localBean, AbstractTDBMObject $remoteBean, ForeignKeyConstraint $localFk, ForeignKeyConstraint $remoteFk)
826
+	{
827
+		$localBeanPk = $this->getPrimaryKeyValues($localBean);
828
+		$remoteBeanPk = $this->getPrimaryKeyValues($remoteBean);
829
+		$localColumns = $localFk->getLocalColumns();
830
+		$remoteColumns = $remoteFk->getLocalColumns();
831
+
832
+		$localFilters = array_combine($localColumns, $localBeanPk);
833
+		$remoteFilters = array_combine($remoteColumns, $remoteBeanPk);
834
+
835
+		return array_merge($localFilters, $remoteFilters);
836
+	}
837
+
838
+	/**
839
+	 * Returns the "values" of the primary key.
840
+	 * This returns the primary key from the $primaryKey attribute, not the one stored in the columns.
841
+	 *
842
+	 * @param AbstractTDBMObject $bean
843
+	 *
844
+	 * @return array numerically indexed array of values
845
+	 */
846
+	private function getPrimaryKeyValues(AbstractTDBMObject $bean)
847
+	{
848
+		$dbRows = $bean->_getDbRows();
849
+		$dbRow = reset($dbRows);
850
+
851
+		return array_values($dbRow->_getPrimaryKeys());
852
+	}
853
+
854
+	/**
855
+	 * Returns a unique hash used to store the object based on its primary key.
856
+	 * If the array contains only one value, then the value is returned.
857
+	 * Otherwise, a hash representing the array is returned.
858
+	 *
859
+	 * @param array $primaryKeys An array of columns => values forming the primary key
860
+	 *
861
+	 * @return string
862
+	 */
863
+	public function getObjectHash(array $primaryKeys)
864
+	{
865
+		if (count($primaryKeys) === 1) {
866
+			return reset($primaryKeys);
867
+		} else {
868
+			ksort($primaryKeys);
869
+
870
+			return md5(json_encode($primaryKeys));
871
+		}
872
+	}
873
+
874
+	/**
875
+	 * Returns an array of primary keys from the object.
876
+	 * The primary keys are extracted from the object columns and not from the primary keys stored in the
877
+	 * $primaryKeys variable of the object.
878
+	 *
879
+	 * @param DbRow $dbRow
880
+	 *
881
+	 * @return array Returns an array of column => value
882
+	 */
883
+	public function getPrimaryKeysForObjectFromDbRow(DbRow $dbRow)
884
+	{
885
+		$table = $dbRow->_getDbTableName();
886
+		$dbRowData = $dbRow->_getDbRow();
887
+
888
+		return $this->_getPrimaryKeysFromObjectData($table, $dbRowData);
889
+	}
890
+
891
+	/**
892
+	 * Returns an array of primary keys for the given row.
893
+	 * The primary keys are extracted from the object columns.
894
+	 *
895
+	 * @param $table
896
+	 * @param array $columns
897
+	 *
898
+	 * @return array
899
+	 */
900
+	public function _getPrimaryKeysFromObjectData($table, array $columns)
901
+	{
902
+		$primaryKeyColumns = $this->getPrimaryKeyColumns($table);
903
+		$values = array();
904
+		foreach ($primaryKeyColumns as $column) {
905
+			if (isset($columns[$column])) {
906
+				$values[$column] = $columns[$column];
907
+			}
908
+		}
909
+
910
+		return $values;
911
+	}
912
+
913
+	/**
914
+	 * Attaches $object to this TDBMService.
915
+	 * The $object must be in DETACHED state and will pass in NEW state.
916
+	 *
917
+	 * @param AbstractTDBMObject $object
918
+	 *
919
+	 * @throws TDBMInvalidOperationException
920
+	 */
921
+	public function attach(AbstractTDBMObject $object)
922
+	{
923
+		$object->_attach($this);
924
+	}
925
+
926
+	/**
927
+	 * Returns an associative array (column => value) for the primary keys from the table name and an
928
+	 * indexed array of primary key values.
929
+	 *
930
+	 * @param string $tableName
931
+	 * @param array  $indexedPrimaryKeys
932
+	 */
933
+	public function _getPrimaryKeysFromIndexedPrimaryKeys($tableName, array $indexedPrimaryKeys)
934
+	{
935
+		$primaryKeyColumns = $this->tdbmSchemaAnalyzer->getSchema()->getTable($tableName)->getPrimaryKeyColumns();
936
+
937
+		if (count($primaryKeyColumns) !== count($indexedPrimaryKeys)) {
938
+			throw new TDBMException(sprintf('Wrong number of columns passed for primary key. Expected %s columns for table "%s",
939 939
 			got %s instead.', count($primaryKeyColumns), $tableName, count($indexedPrimaryKeys)));
940
-        }
941
-
942
-        return array_combine($primaryKeyColumns, $indexedPrimaryKeys);
943
-    }
944
-
945
-    /**
946
-     * Return the list of tables (from child to parent) joining the tables passed in parameter.
947
-     * Tables must be in a single line of inheritance. The method will find missing tables.
948
-     *
949
-     * Algorithm: one of those tables is the ultimate child. From this child, by recursively getting the parent,
950
-     * we must be able to find all other tables.
951
-     *
952
-     * @param string[] $tables
953
-     *
954
-     * @return string[]
955
-     */
956
-    public function _getLinkBetweenInheritedTables(array $tables)
957
-    {
958
-        sort($tables);
959
-
960
-        return $this->fromCache($this->cachePrefix.'_linkbetweeninheritedtables_'.implode('__split__', $tables),
961
-            function () use ($tables) {
962
-                return $this->_getLinkBetweenInheritedTablesWithoutCache($tables);
963
-            });
964
-    }
965
-
966
-    /**
967
-     * Return the list of tables (from child to parent) joining the tables passed in parameter.
968
-     * Tables must be in a single line of inheritance. The method will find missing tables.
969
-     *
970
-     * Algorithm: one of those tables is the ultimate child. From this child, by recursively getting the parent,
971
-     * we must be able to find all other tables.
972
-     *
973
-     * @param string[] $tables
974
-     *
975
-     * @return string[]
976
-     */
977
-    private function _getLinkBetweenInheritedTablesWithoutCache(array $tables)
978
-    {
979
-        $schemaAnalyzer = $this->schemaAnalyzer;
980
-
981
-        foreach ($tables as $currentTable) {
982
-            $allParents = [$currentTable];
983
-            while ($currentFk = $schemaAnalyzer->getParentRelationship($currentTable)) {
984
-                $currentTable = $currentFk->getForeignTableName();
985
-                $allParents[] = $currentTable;
986
-            }
987
-
988
-            // Now, does the $allParents contain all the tables we want?
989
-            $notFoundTables = array_diff($tables, $allParents);
990
-            if (empty($notFoundTables)) {
991
-                // We have a winner!
992
-                return $allParents;
993
-            }
994
-        }
995
-
996
-        throw TDBMInheritanceException::create($tables);
997
-    }
998
-
999
-    /**
1000
-     * Returns the list of tables related to this table (via a parent or child inheritance relationship).
1001
-     *
1002
-     * @param string $table
1003
-     *
1004
-     * @return string[]
1005
-     */
1006
-    public function _getRelatedTablesByInheritance($table)
1007
-    {
1008
-        return $this->fromCache($this->cachePrefix.'_relatedtables_'.$table, function () use ($table) {
1009
-            return $this->_getRelatedTablesByInheritanceWithoutCache($table);
1010
-        });
1011
-    }
1012
-
1013
-    /**
1014
-     * Returns the list of tables related to this table (via a parent or child inheritance relationship).
1015
-     *
1016
-     * @param string $table
1017
-     *
1018
-     * @return string[]
1019
-     */
1020
-    private function _getRelatedTablesByInheritanceWithoutCache($table)
1021
-    {
1022
-        $schemaAnalyzer = $this->schemaAnalyzer;
1023
-
1024
-        // Let's scan the parent tables
1025
-        $currentTable = $table;
1026
-
1027
-        $parentTables = [];
1028
-
1029
-        // Get parent relationship
1030
-        while ($currentFk = $schemaAnalyzer->getParentRelationship($currentTable)) {
1031
-            $currentTable = $currentFk->getForeignTableName();
1032
-            $parentTables[] = $currentTable;
1033
-        }
1034
-
1035
-        // Let's recurse in children
1036
-        $childrenTables = $this->exploreChildrenTablesRelationships($schemaAnalyzer, $table);
1037
-
1038
-        return array_merge(array_reverse($parentTables), $childrenTables);
1039
-    }
1040
-
1041
-    /**
1042
-     * Explore all the children and descendant of $table and returns ForeignKeyConstraints on those.
1043
-     *
1044
-     * @param string $table
1045
-     *
1046
-     * @return string[]
1047
-     */
1048
-    private function exploreChildrenTablesRelationships(SchemaAnalyzer $schemaAnalyzer, $table)
1049
-    {
1050
-        $tables = [$table];
1051
-        $keys = $schemaAnalyzer->getChildrenRelationships($table);
1052
-
1053
-        foreach ($keys as $key) {
1054
-            $tables = array_merge($tables, $this->exploreChildrenTablesRelationships($schemaAnalyzer, $key->getLocalTableName()));
1055
-        }
1056
-
1057
-        return $tables;
1058
-    }
1059
-
1060
-    /**
1061
-     * Casts a foreign key into SQL, assuming table name is used with no alias.
1062
-     * The returned value does contain only one table. For instance:.
1063
-     *
1064
-     * " LEFT JOIN table2 ON table1.id = table2.table1_id"
1065
-     *
1066
-     * @param ForeignKeyConstraint $fk
1067
-     * @param bool                 $leftTableIsLocal
1068
-     *
1069
-     * @return string
1070
-     */
1071
-    /*private function foreignKeyToSql(ForeignKeyConstraint $fk, $leftTableIsLocal) {
940
+		}
941
+
942
+		return array_combine($primaryKeyColumns, $indexedPrimaryKeys);
943
+	}
944
+
945
+	/**
946
+	 * Return the list of tables (from child to parent) joining the tables passed in parameter.
947
+	 * Tables must be in a single line of inheritance. The method will find missing tables.
948
+	 *
949
+	 * Algorithm: one of those tables is the ultimate child. From this child, by recursively getting the parent,
950
+	 * we must be able to find all other tables.
951
+	 *
952
+	 * @param string[] $tables
953
+	 *
954
+	 * @return string[]
955
+	 */
956
+	public function _getLinkBetweenInheritedTables(array $tables)
957
+	{
958
+		sort($tables);
959
+
960
+		return $this->fromCache($this->cachePrefix.'_linkbetweeninheritedtables_'.implode('__split__', $tables),
961
+			function () use ($tables) {
962
+				return $this->_getLinkBetweenInheritedTablesWithoutCache($tables);
963
+			});
964
+	}
965
+
966
+	/**
967
+	 * Return the list of tables (from child to parent) joining the tables passed in parameter.
968
+	 * Tables must be in a single line of inheritance. The method will find missing tables.
969
+	 *
970
+	 * Algorithm: one of those tables is the ultimate child. From this child, by recursively getting the parent,
971
+	 * we must be able to find all other tables.
972
+	 *
973
+	 * @param string[] $tables
974
+	 *
975
+	 * @return string[]
976
+	 */
977
+	private function _getLinkBetweenInheritedTablesWithoutCache(array $tables)
978
+	{
979
+		$schemaAnalyzer = $this->schemaAnalyzer;
980
+
981
+		foreach ($tables as $currentTable) {
982
+			$allParents = [$currentTable];
983
+			while ($currentFk = $schemaAnalyzer->getParentRelationship($currentTable)) {
984
+				$currentTable = $currentFk->getForeignTableName();
985
+				$allParents[] = $currentTable;
986
+			}
987
+
988
+			// Now, does the $allParents contain all the tables we want?
989
+			$notFoundTables = array_diff($tables, $allParents);
990
+			if (empty($notFoundTables)) {
991
+				// We have a winner!
992
+				return $allParents;
993
+			}
994
+		}
995
+
996
+		throw TDBMInheritanceException::create($tables);
997
+	}
998
+
999
+	/**
1000
+	 * Returns the list of tables related to this table (via a parent or child inheritance relationship).
1001
+	 *
1002
+	 * @param string $table
1003
+	 *
1004
+	 * @return string[]
1005
+	 */
1006
+	public function _getRelatedTablesByInheritance($table)
1007
+	{
1008
+		return $this->fromCache($this->cachePrefix.'_relatedtables_'.$table, function () use ($table) {
1009
+			return $this->_getRelatedTablesByInheritanceWithoutCache($table);
1010
+		});
1011
+	}
1012
+
1013
+	/**
1014
+	 * Returns the list of tables related to this table (via a parent or child inheritance relationship).
1015
+	 *
1016
+	 * @param string $table
1017
+	 *
1018
+	 * @return string[]
1019
+	 */
1020
+	private function _getRelatedTablesByInheritanceWithoutCache($table)
1021
+	{
1022
+		$schemaAnalyzer = $this->schemaAnalyzer;
1023
+
1024
+		// Let's scan the parent tables
1025
+		$currentTable = $table;
1026
+
1027
+		$parentTables = [];
1028
+
1029
+		// Get parent relationship
1030
+		while ($currentFk = $schemaAnalyzer->getParentRelationship($currentTable)) {
1031
+			$currentTable = $currentFk->getForeignTableName();
1032
+			$parentTables[] = $currentTable;
1033
+		}
1034
+
1035
+		// Let's recurse in children
1036
+		$childrenTables = $this->exploreChildrenTablesRelationships($schemaAnalyzer, $table);
1037
+
1038
+		return array_merge(array_reverse($parentTables), $childrenTables);
1039
+	}
1040
+
1041
+	/**
1042
+	 * Explore all the children and descendant of $table and returns ForeignKeyConstraints on those.
1043
+	 *
1044
+	 * @param string $table
1045
+	 *
1046
+	 * @return string[]
1047
+	 */
1048
+	private function exploreChildrenTablesRelationships(SchemaAnalyzer $schemaAnalyzer, $table)
1049
+	{
1050
+		$tables = [$table];
1051
+		$keys = $schemaAnalyzer->getChildrenRelationships($table);
1052
+
1053
+		foreach ($keys as $key) {
1054
+			$tables = array_merge($tables, $this->exploreChildrenTablesRelationships($schemaAnalyzer, $key->getLocalTableName()));
1055
+		}
1056
+
1057
+		return $tables;
1058
+	}
1059
+
1060
+	/**
1061
+	 * Casts a foreign key into SQL, assuming table name is used with no alias.
1062
+	 * The returned value does contain only one table. For instance:.
1063
+	 *
1064
+	 * " LEFT JOIN table2 ON table1.id = table2.table1_id"
1065
+	 *
1066
+	 * @param ForeignKeyConstraint $fk
1067
+	 * @param bool                 $leftTableIsLocal
1068
+	 *
1069
+	 * @return string
1070
+	 */
1071
+	/*private function foreignKeyToSql(ForeignKeyConstraint $fk, $leftTableIsLocal) {
1072 1072
         $onClauses = [];
1073 1073
         $foreignTableName = $this->connection->quoteIdentifier($fk->getForeignTableName());
1074 1074
         $foreignColumns = $fk->getForeignColumns();
@@ -1094,417 +1094,417 @@  discard block
 block discarded – undo
1094 1094
         }
1095 1095
     }*/
1096 1096
 
1097
-    /**
1098
-     * Returns a `ResultIterator` object representing filtered records of "$mainTable" .
1099
-     *
1100
-     * The findObjects method should be the most used query method in TDBM if you want to query the database for objects.
1101
-     * (Note: if you want to query the database for an object by its primary key, use the findObjectByPk method).
1102
-     *
1103
-     * The findObjects method takes in parameter:
1104
-     * 	- mainTable: the kind of bean you want to retrieve. In TDBM, a bean matches a database row, so the
1105
-     * 			`$mainTable` parameter should be the name of an existing table in database.
1106
-     *  - filter: The filter is a filter bag. It is what you use to filter your request (the WHERE part in SQL).
1107
-     *          It can be a string (SQL Where clause), or even a bean or an associative array (key = column to filter, value = value to find)
1108
-     *  - parameters: The parameters used in the filter. If you pass a SQL string as a filter, be sure to avoid
1109
-     *          concatenating parameters in the string (this leads to SQL injection and also to poor caching performance).
1110
-     *          Instead, please consider passing parameters (see documentation for more details).
1111
-     *  - additionalTablesFetch: An array of SQL tables names. The beans related to those tables will be fetched along
1112
-     *          the main table. This is useful to avoid hitting the database with numerous subqueries.
1113
-     *  - mode: The fetch mode of the result. See `setFetchMode()` method for more details.
1114
-     *
1115
-     * The `findObjects` method will return a `ResultIterator`. A `ResultIterator` is an object that behaves as an array
1116
-     * (in ARRAY mode) at least. It can be iterated using a `foreach` loop.
1117
-     *
1118
-     * Finally, if filter_bag is null, the whole table is returned.
1119
-     *
1120
-     * @param string                       $mainTable             The name of the table queried
1121
-     * @param string|array|null            $filter                The SQL filters to apply to the query (the WHERE part). Columns from tables different from $mainTable must be prefixed by the table name (in the form: table.column)
1122
-     * @param array                        $parameters
1123
-     * @param string|UncheckedOrderBy|null $orderString           The ORDER BY part of the query. Columns from tables different from $mainTable must be prefixed by the table name (in the form: table.column)
1124
-     * @param array                        $additionalTablesFetch
1125
-     * @param int                          $mode
1126
-     * @param string                       $className             Optional: The name of the class to instantiate. This class must extend the TDBMObject class. If none is specified, a TDBMObject instance will be returned
1127
-     *
1128
-     * @return ResultIterator An object representing an array of results
1129
-     *
1130
-     * @throws TDBMException
1131
-     */
1132
-    public function findObjects(string $mainTable, $filter = null, array $parameters = array(), $orderString = null, array $additionalTablesFetch = array(), $mode = null, string $className = null)
1133
-    {
1134
-        // $mainTable is not secured in MagicJoin, let's add a bit of security to avoid SQL injection.
1135
-        if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $mainTable)) {
1136
-            throw new TDBMException(sprintf("Invalid table name: '%s'", $mainTable));
1137
-        }
1138
-
1139
-        $mode = $mode ?: $this->mode;
1140
-
1141
-        list($filterString, $additionalParameters) = $this->buildFilterFromFilterBag($filter);
1142
-
1143
-        $parameters = array_merge($parameters, $additionalParameters);
1144
-
1145
-        $queryFactory = new FindObjectsQueryFactory($mainTable, $additionalTablesFetch, $filterString, $orderString, $this, $this->tdbmSchemaAnalyzer->getSchema(), $this->orderByAnalyzer);
1146
-
1147
-        return new ResultIterator($queryFactory, $parameters, $this->objectStorage, $className, $this, $this->magicQuery, $mode, $this->logger);
1148
-    }
1149
-
1150
-    /**
1151
-     * @param string                       $mainTable   The name of the table queried
1152
-     * @param string                       $from        The from sql statement
1153
-     * @param string|array|null            $filter      The SQL filters to apply to the query (the WHERE part). All columns must be prefixed by the table name (in the form: table.column)
1154
-     * @param array                        $parameters
1155
-     * @param string|UncheckedOrderBy|null $orderString The ORDER BY part of the query. All columns must be prefixed by the table name (in the form: table.column)
1156
-     * @param int                          $mode
1157
-     * @param string                       $className   Optional: The name of the class to instantiate. This class must extend the TDBMObject class. If none is specified, a TDBMObject instance will be returned
1158
-     *
1159
-     * @return ResultIterator An object representing an array of results
1160
-     *
1161
-     * @throws TDBMException
1162
-     */
1163
-    public function findObjectsFromSql(string $mainTable, string $from, $filter = null, array $parameters = array(), $orderString = null, $mode = null, string $className = null)
1164
-    {
1165
-        // $mainTable is not secured in MagicJoin, let's add a bit of security to avoid SQL injection.
1166
-        if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $mainTable)) {
1167
-            throw new TDBMException(sprintf("Invalid table name: '%s'", $mainTable));
1168
-        }
1169
-
1170
-        $mode = $mode ?: $this->mode;
1171
-
1172
-        list($filterString, $additionalParameters) = $this->buildFilterFromFilterBag($filter);
1173
-
1174
-        $parameters = array_merge($parameters, $additionalParameters);
1175
-
1176
-        $queryFactory = new FindObjectsFromSqlQueryFactory($mainTable, $from, $filterString, $orderString, $this, $this->tdbmSchemaAnalyzer->getSchema(), $this->orderByAnalyzer, $this->schemaAnalyzer, $this->cache, $this->cachePrefix);
1177
-
1178
-        return new ResultIterator($queryFactory, $parameters, $this->objectStorage, $className, $this, $this->magicQuery, $mode, $this->logger);
1179
-    }
1180
-
1181
-    /**
1182
-     * @param $table
1183
-     * @param array  $primaryKeys
1184
-     * @param array  $additionalTablesFetch
1185
-     * @param bool   $lazy                  Whether to perform lazy loading on this object or not
1186
-     * @param string $className
1187
-     *
1188
-     * @return AbstractTDBMObject
1189
-     *
1190
-     * @throws TDBMException
1191
-     */
1192
-    public function findObjectByPk(string $table, array $primaryKeys, array $additionalTablesFetch = array(), bool $lazy = false, string $className = null)
1193
-    {
1194
-        $primaryKeys = $this->_getPrimaryKeysFromObjectData($table, $primaryKeys);
1195
-        $hash = $this->getObjectHash($primaryKeys);
1196
-
1197
-        if ($this->objectStorage->has($table, $hash)) {
1198
-            $dbRow = $this->objectStorage->get($table, $hash);
1199
-            $bean = $dbRow->getTDBMObject();
1200
-            if ($className !== null && !is_a($bean, $className)) {
1201
-                throw new TDBMException("TDBM cannot create a bean of class '".$className."'. The requested object was already loaded and its class is '".get_class($bean)."'");
1202
-            }
1203
-
1204
-            return $bean;
1205
-        }
1206
-
1207
-        // Are we performing lazy fetching?
1208
-        if ($lazy === true) {
1209
-            // Can we perform lazy fetching?
1210
-            $tables = $this->_getRelatedTablesByInheritance($table);
1211
-            // Only allowed if no inheritance.
1212
-            if (count($tables) === 1) {
1213
-                if ($className === null) {
1214
-                    try {
1215
-                        $className = $this->getBeanClassName($table);
1216
-                    } catch (TDBMInvalidArgumentException $e) {
1217
-                        $className = TDBMObject::class;
1218
-                    }
1219
-                }
1220
-
1221
-                // Let's construct the bean
1222
-                if (!isset($this->reflectionClassCache[$className])) {
1223
-                    $this->reflectionClassCache[$className] = new \ReflectionClass($className);
1224
-                }
1225
-                // Let's bypass the constructor when creating the bean!
1226
-                $bean = $this->reflectionClassCache[$className]->newInstanceWithoutConstructor();
1227
-                /* @var $bean AbstractTDBMObject */
1228
-                $bean->_constructLazy($table, $primaryKeys, $this);
1229
-
1230
-                return $bean;
1231
-            }
1232
-        }
1233
-
1234
-        // Did not find the object in cache? Let's query it!
1235
-        return $this->findObjectOrFail($table, $primaryKeys, [], $additionalTablesFetch, $className);
1236
-    }
1237
-
1238
-    /**
1239
-     * Returns a unique bean (or null) according to the filters passed in parameter.
1240
-     *
1241
-     * @param string            $mainTable             The name of the table queried
1242
-     * @param string|array|null $filter                The SQL filters to apply to the query (the WHERE part). All columns must be prefixed by the table name (in the form: table.column)
1243
-     * @param array             $parameters
1244
-     * @param array             $additionalTablesFetch
1245
-     * @param string            $className             Optional: The name of the class to instantiate. This class must extend the TDBMObject class. If none is specified, a TDBMObject instance will be returned
1246
-     *
1247
-     * @return AbstractTDBMObject|null The object we want, or null if no object matches the filters
1248
-     *
1249
-     * @throws TDBMException
1250
-     */
1251
-    public function findObject(string $mainTable, $filter = null, array $parameters = array(), array $additionalTablesFetch = array(), string $className = null)
1252
-    {
1253
-        $objects = $this->findObjects($mainTable, $filter, $parameters, null, $additionalTablesFetch, self::MODE_ARRAY, $className);
1254
-        $page = $objects->take(0, 2);
1255
-        $count = $page->count();
1256
-        if ($count > 1) {
1257
-            throw new DuplicateRowException("Error while querying an object for table '$mainTable': More than 1 row have been returned, but we should have received at most one.");
1258
-        } elseif ($count === 0) {
1259
-            return;
1260
-        }
1261
-
1262
-        return $page[0];
1263
-    }
1264
-
1265
-    /**
1266
-     * Returns a unique bean (or null) according to the filters passed in parameter.
1267
-     *
1268
-     * @param string            $mainTable  The name of the table queried
1269
-     * @param string            $from       The from sql statement
1270
-     * @param string|array|null $filter     The SQL filters to apply to the query (the WHERE part). All columns must be prefixed by the table name (in the form: table.column)
1271
-     * @param array             $parameters
1272
-     * @param string            $className  Optional: The name of the class to instantiate. This class must extend the TDBMObject class. If none is specified, a TDBMObject instance will be returned
1273
-     *
1274
-     * @return AbstractTDBMObject|null The object we want, or null if no object matches the filters
1275
-     *
1276
-     * @throws TDBMException
1277
-     */
1278
-    public function findObjectFromSql($mainTable, $from, $filter = null, array $parameters = array(), $className = null)
1279
-    {
1280
-        $objects = $this->findObjectsFromSql($mainTable, $from, $filter, $parameters, null, self::MODE_ARRAY, $className);
1281
-        $page = $objects->take(0, 2);
1282
-        $count = $page->count();
1283
-        if ($count > 1) {
1284
-            throw new DuplicateRowException("Error while querying an object for table '$mainTable': More than 1 row have been returned, but we should have received at most one.");
1285
-        } elseif ($count === 0) {
1286
-            return;
1287
-        }
1288
-
1289
-        return $page[0];
1290
-    }
1291
-
1292
-    /**
1293
-     * Returns a unique bean according to the filters passed in parameter.
1294
-     * Throws a NoBeanFoundException if no bean was found for the filter passed in parameter.
1295
-     *
1296
-     * @param string            $mainTable             The name of the table queried
1297
-     * @param string|array|null $filter                The SQL filters to apply to the query (the WHERE part). All columns must be prefixed by the table name (in the form: table.column)
1298
-     * @param array             $parameters
1299
-     * @param array             $additionalTablesFetch
1300
-     * @param string            $className             Optional: The name of the class to instantiate. This class must extend the TDBMObject class. If none is specified, a TDBMObject instance will be returned
1301
-     *
1302
-     * @return AbstractTDBMObject The object we want
1303
-     *
1304
-     * @throws TDBMException
1305
-     */
1306
-    public function findObjectOrFail(string $mainTable, $filter = null, array $parameters = array(), array $additionalTablesFetch = array(), string $className = null)
1307
-    {
1308
-        $bean = $this->findObject($mainTable, $filter, $parameters, $additionalTablesFetch, $className);
1309
-        if ($bean === null) {
1310
-            throw new NoBeanFoundException("No result found for query on table '".$mainTable."'");
1311
-        }
1312
-
1313
-        return $bean;
1314
-    }
1315
-
1316
-    /**
1317
-     * @param array $beanData An array of data: array<table, array<column, value>>
1318
-     *
1319
-     * @return array an array with first item = class name, second item = table name and third item = list of tables needed
1320
-     *
1321
-     * @throws TDBMInheritanceException
1322
-     */
1323
-    public function _getClassNameFromBeanData(array $beanData)
1324
-    {
1325
-        if (count($beanData) === 1) {
1326
-            $tableName = array_keys($beanData)[0];
1327
-            $allTables = [$tableName];
1328
-        } else {
1329
-            $tables = [];
1330
-            foreach ($beanData as $table => $row) {
1331
-                $primaryKeyColumns = $this->getPrimaryKeyColumns($table);
1332
-                $pkSet = false;
1333
-                foreach ($primaryKeyColumns as $columnName) {
1334
-                    if ($row[$columnName] !== null) {
1335
-                        $pkSet = true;
1336
-                        break;
1337
-                    }
1338
-                }
1339
-                if ($pkSet) {
1340
-                    $tables[] = $table;
1341
-                }
1342
-            }
1343
-
1344
-            // $tables contains the tables for this bean. Let's view the top most part of the hierarchy
1345
-            try {
1346
-                $allTables = $this->_getLinkBetweenInheritedTables($tables);
1347
-            } catch (TDBMInheritanceException $e) {
1348
-                throw TDBMInheritanceException::extendException($e, $this, $beanData);
1349
-            }
1350
-            $tableName = $allTables[0];
1351
-        }
1352
-
1353
-        // Only one table in this bean. Life is sweat, let's look at its type:
1354
-        try {
1355
-            $className = $this->getBeanClassName($tableName);
1356
-        } catch (TDBMInvalidArgumentException $e) {
1357
-            $className = 'Mouf\\Database\\TDBM\\TDBMObject';
1358
-        }
1359
-
1360
-        return [$className, $tableName, $allTables];
1361
-    }
1362
-
1363
-    /**
1364
-     * Returns an item from cache or computes it using $closure and puts it in cache.
1365
-     *
1366
-     * @param string   $key
1367
-     * @param callable $closure
1368
-     *
1369
-     * @return mixed
1370
-     */
1371
-    private function fromCache(string $key, callable $closure)
1372
-    {
1373
-        $item = $this->cache->fetch($key);
1374
-        if ($item === false) {
1375
-            $item = $closure();
1376
-            $this->cache->save($key, $item);
1377
-        }
1378
-
1379
-        return $item;
1380
-    }
1381
-
1382
-    /**
1383
-     * Returns the foreign key object.
1384
-     *
1385
-     * @param string $table
1386
-     * @param string $fkName
1387
-     *
1388
-     * @return ForeignKeyConstraint
1389
-     */
1390
-    public function _getForeignKeyByName(string $table, string $fkName)
1391
-    {
1392
-        return $this->tdbmSchemaAnalyzer->getSchema()->getTable($table)->getForeignKey($fkName);
1393
-    }
1394
-
1395
-    /**
1396
-     * @param $pivotTableName
1397
-     * @param AbstractTDBMObject $bean
1398
-     *
1399
-     * @return AbstractTDBMObject[]
1400
-     */
1401
-    public function _getRelatedBeans(string $pivotTableName, AbstractTDBMObject $bean)
1402
-    {
1403
-        list($localFk, $remoteFk) = $this->getPivotTableForeignKeys($pivotTableName, $bean);
1404
-        /* @var $localFk ForeignKeyConstraint */
1405
-        /* @var $remoteFk ForeignKeyConstraint */
1406
-        $remoteTable = $remoteFk->getForeignTableName();
1407
-
1408
-        $primaryKeys = $this->getPrimaryKeyValues($bean);
1409
-        $columnNames = array_map(function ($name) use ($pivotTableName) {
1410
-            return $pivotTableName.'.'.$name;
1411
-        }, $localFk->getLocalColumns());
1412
-
1413
-        $filter = array_combine($columnNames, $primaryKeys);
1414
-
1415
-        return $this->findObjects($remoteTable, $filter);
1416
-    }
1417
-
1418
-    /**
1419
-     * @param $pivotTableName
1420
-     * @param AbstractTDBMObject $bean The LOCAL bean
1421
-     *
1422
-     * @return ForeignKeyConstraint[] First item: the LOCAL bean, second item: the REMOTE bean
1423
-     *
1424
-     * @throws TDBMException
1425
-     */
1426
-    private function getPivotTableForeignKeys(string $pivotTableName, AbstractTDBMObject $bean)
1427
-    {
1428
-        $fks = array_values($this->tdbmSchemaAnalyzer->getSchema()->getTable($pivotTableName)->getForeignKeys());
1429
-        $table1 = $fks[0]->getForeignTableName();
1430
-        $table2 = $fks[1]->getForeignTableName();
1431
-
1432
-        $beanTables = array_map(function (DbRow $dbRow) {
1433
-            return $dbRow->_getDbTableName();
1434
-        }, $bean->_getDbRows());
1435
-
1436
-        if (in_array($table1, $beanTables)) {
1437
-            return [$fks[0], $fks[1]];
1438
-        } elseif (in_array($table2, $beanTables)) {
1439
-            return [$fks[1], $fks[0]];
1440
-        } else {
1441
-            throw new TDBMException("Unexpected bean type in getPivotTableForeignKeys. Awaiting beans from table {$table1} and {$table2} for pivot table {$pivotTableName}");
1442
-        }
1443
-    }
1444
-
1445
-    /**
1446
-     * Returns a list of pivot tables linked to $bean.
1447
-     *
1448
-     * @param AbstractTDBMObject $bean
1449
-     *
1450
-     * @return string[]
1451
-     */
1452
-    public function _getPivotTablesLinkedToBean(AbstractTDBMObject $bean)
1453
-    {
1454
-        $junctionTables = [];
1455
-        $allJunctionTables = $this->schemaAnalyzer->detectJunctionTables(true);
1456
-        foreach ($bean->_getDbRows() as $dbRow) {
1457
-            foreach ($allJunctionTables as $table) {
1458
-                // There are exactly 2 FKs since this is a pivot table.
1459
-                $fks = array_values($table->getForeignKeys());
1460
-
1461
-                if ($fks[0]->getForeignTableName() === $dbRow->_getDbTableName() || $fks[1]->getForeignTableName() === $dbRow->_getDbTableName()) {
1462
-                    $junctionTables[] = $table->getName();
1463
-                }
1464
-            }
1465
-        }
1466
-
1467
-        return $junctionTables;
1468
-    }
1469
-
1470
-    /**
1471
-     * Array of types for tables.
1472
-     * Key: table name
1473
-     * Value: array of types indexed by column.
1474
-     *
1475
-     * @var array[]
1476
-     */
1477
-    private $typesForTable = [];
1478
-
1479
-    /**
1480
-     * @internal
1481
-     *
1482
-     * @param string $tableName
1483
-     *
1484
-     * @return Type[]
1485
-     */
1486
-    public function _getColumnTypesForTable(string $tableName)
1487
-    {
1488
-        if (!isset($typesForTable[$tableName])) {
1489
-            $columns = $this->tdbmSchemaAnalyzer->getSchema()->getTable($tableName)->getColumns();
1490
-            $typesForTable[$tableName] = array_map(function (Column $column) {
1491
-                return $column->getType();
1492
-            }, $columns);
1493
-        }
1494
-
1495
-        return $typesForTable[$tableName];
1496
-    }
1497
-
1498
-    /**
1499
-     * Sets the minimum log level.
1500
-     * $level must be one of Psr\Log\LogLevel::xxx.
1501
-     *
1502
-     * Defaults to LogLevel::WARNING
1503
-     *
1504
-     * @param string $level
1505
-     */
1506
-    public function setLogLevel(string $level)
1507
-    {
1508
-        $this->logger = new LevelFilter($this->rootLogger, $level);
1509
-    }
1097
+	/**
1098
+	 * Returns a `ResultIterator` object representing filtered records of "$mainTable" .
1099
+	 *
1100
+	 * The findObjects method should be the most used query method in TDBM if you want to query the database for objects.
1101
+	 * (Note: if you want to query the database for an object by its primary key, use the findObjectByPk method).
1102
+	 *
1103
+	 * The findObjects method takes in parameter:
1104
+	 * 	- mainTable: the kind of bean you want to retrieve. In TDBM, a bean matches a database row, so the
1105
+	 * 			`$mainTable` parameter should be the name of an existing table in database.
1106
+	 *  - filter: The filter is a filter bag. It is what you use to filter your request (the WHERE part in SQL).
1107
+	 *          It can be a string (SQL Where clause), or even a bean or an associative array (key = column to filter, value = value to find)
1108
+	 *  - parameters: The parameters used in the filter. If you pass a SQL string as a filter, be sure to avoid
1109
+	 *          concatenating parameters in the string (this leads to SQL injection and also to poor caching performance).
1110
+	 *          Instead, please consider passing parameters (see documentation for more details).
1111
+	 *  - additionalTablesFetch: An array of SQL tables names. The beans related to those tables will be fetched along
1112
+	 *          the main table. This is useful to avoid hitting the database with numerous subqueries.
1113
+	 *  - mode: The fetch mode of the result. See `setFetchMode()` method for more details.
1114
+	 *
1115
+	 * The `findObjects` method will return a `ResultIterator`. A `ResultIterator` is an object that behaves as an array
1116
+	 * (in ARRAY mode) at least. It can be iterated using a `foreach` loop.
1117
+	 *
1118
+	 * Finally, if filter_bag is null, the whole table is returned.
1119
+	 *
1120
+	 * @param string                       $mainTable             The name of the table queried
1121
+	 * @param string|array|null            $filter                The SQL filters to apply to the query (the WHERE part). Columns from tables different from $mainTable must be prefixed by the table name (in the form: table.column)
1122
+	 * @param array                        $parameters
1123
+	 * @param string|UncheckedOrderBy|null $orderString           The ORDER BY part of the query. Columns from tables different from $mainTable must be prefixed by the table name (in the form: table.column)
1124
+	 * @param array                        $additionalTablesFetch
1125
+	 * @param int                          $mode
1126
+	 * @param string                       $className             Optional: The name of the class to instantiate. This class must extend the TDBMObject class. If none is specified, a TDBMObject instance will be returned
1127
+	 *
1128
+	 * @return ResultIterator An object representing an array of results
1129
+	 *
1130
+	 * @throws TDBMException
1131
+	 */
1132
+	public function findObjects(string $mainTable, $filter = null, array $parameters = array(), $orderString = null, array $additionalTablesFetch = array(), $mode = null, string $className = null)
1133
+	{
1134
+		// $mainTable is not secured in MagicJoin, let's add a bit of security to avoid SQL injection.
1135
+		if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $mainTable)) {
1136
+			throw new TDBMException(sprintf("Invalid table name: '%s'", $mainTable));
1137
+		}
1138
+
1139
+		$mode = $mode ?: $this->mode;
1140
+
1141
+		list($filterString, $additionalParameters) = $this->buildFilterFromFilterBag($filter);
1142
+
1143
+		$parameters = array_merge($parameters, $additionalParameters);
1144
+
1145
+		$queryFactory = new FindObjectsQueryFactory($mainTable, $additionalTablesFetch, $filterString, $orderString, $this, $this->tdbmSchemaAnalyzer->getSchema(), $this->orderByAnalyzer);
1146
+
1147
+		return new ResultIterator($queryFactory, $parameters, $this->objectStorage, $className, $this, $this->magicQuery, $mode, $this->logger);
1148
+	}
1149
+
1150
+	/**
1151
+	 * @param string                       $mainTable   The name of the table queried
1152
+	 * @param string                       $from        The from sql statement
1153
+	 * @param string|array|null            $filter      The SQL filters to apply to the query (the WHERE part). All columns must be prefixed by the table name (in the form: table.column)
1154
+	 * @param array                        $parameters
1155
+	 * @param string|UncheckedOrderBy|null $orderString The ORDER BY part of the query. All columns must be prefixed by the table name (in the form: table.column)
1156
+	 * @param int                          $mode
1157
+	 * @param string                       $className   Optional: The name of the class to instantiate. This class must extend the TDBMObject class. If none is specified, a TDBMObject instance will be returned
1158
+	 *
1159
+	 * @return ResultIterator An object representing an array of results
1160
+	 *
1161
+	 * @throws TDBMException
1162
+	 */
1163
+	public function findObjectsFromSql(string $mainTable, string $from, $filter = null, array $parameters = array(), $orderString = null, $mode = null, string $className = null)
1164
+	{
1165
+		// $mainTable is not secured in MagicJoin, let's add a bit of security to avoid SQL injection.
1166
+		if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $mainTable)) {
1167
+			throw new TDBMException(sprintf("Invalid table name: '%s'", $mainTable));
1168
+		}
1169
+
1170
+		$mode = $mode ?: $this->mode;
1171
+
1172
+		list($filterString, $additionalParameters) = $this->buildFilterFromFilterBag($filter);
1173
+
1174
+		$parameters = array_merge($parameters, $additionalParameters);
1175
+
1176
+		$queryFactory = new FindObjectsFromSqlQueryFactory($mainTable, $from, $filterString, $orderString, $this, $this->tdbmSchemaAnalyzer->getSchema(), $this->orderByAnalyzer, $this->schemaAnalyzer, $this->cache, $this->cachePrefix);
1177
+
1178
+		return new ResultIterator($queryFactory, $parameters, $this->objectStorage, $className, $this, $this->magicQuery, $mode, $this->logger);
1179
+	}
1180
+
1181
+	/**
1182
+	 * @param $table
1183
+	 * @param array  $primaryKeys
1184
+	 * @param array  $additionalTablesFetch
1185
+	 * @param bool   $lazy                  Whether to perform lazy loading on this object or not
1186
+	 * @param string $className
1187
+	 *
1188
+	 * @return AbstractTDBMObject
1189
+	 *
1190
+	 * @throws TDBMException
1191
+	 */
1192
+	public function findObjectByPk(string $table, array $primaryKeys, array $additionalTablesFetch = array(), bool $lazy = false, string $className = null)
1193
+	{
1194
+		$primaryKeys = $this->_getPrimaryKeysFromObjectData($table, $primaryKeys);
1195
+		$hash = $this->getObjectHash($primaryKeys);
1196
+
1197
+		if ($this->objectStorage->has($table, $hash)) {
1198
+			$dbRow = $this->objectStorage->get($table, $hash);
1199
+			$bean = $dbRow->getTDBMObject();
1200
+			if ($className !== null && !is_a($bean, $className)) {
1201
+				throw new TDBMException("TDBM cannot create a bean of class '".$className."'. The requested object was already loaded and its class is '".get_class($bean)."'");
1202
+			}
1203
+
1204
+			return $bean;
1205
+		}
1206
+
1207
+		// Are we performing lazy fetching?
1208
+		if ($lazy === true) {
1209
+			// Can we perform lazy fetching?
1210
+			$tables = $this->_getRelatedTablesByInheritance($table);
1211
+			// Only allowed if no inheritance.
1212
+			if (count($tables) === 1) {
1213
+				if ($className === null) {
1214
+					try {
1215
+						$className = $this->getBeanClassName($table);
1216
+					} catch (TDBMInvalidArgumentException $e) {
1217
+						$className = TDBMObject::class;
1218
+					}
1219
+				}
1220
+
1221
+				// Let's construct the bean
1222
+				if (!isset($this->reflectionClassCache[$className])) {
1223
+					$this->reflectionClassCache[$className] = new \ReflectionClass($className);
1224
+				}
1225
+				// Let's bypass the constructor when creating the bean!
1226
+				$bean = $this->reflectionClassCache[$className]->newInstanceWithoutConstructor();
1227
+				/* @var $bean AbstractTDBMObject */
1228
+				$bean->_constructLazy($table, $primaryKeys, $this);
1229
+
1230
+				return $bean;
1231
+			}
1232
+		}
1233
+
1234
+		// Did not find the object in cache? Let's query it!
1235
+		return $this->findObjectOrFail($table, $primaryKeys, [], $additionalTablesFetch, $className);
1236
+	}
1237
+
1238
+	/**
1239
+	 * Returns a unique bean (or null) according to the filters passed in parameter.
1240
+	 *
1241
+	 * @param string            $mainTable             The name of the table queried
1242
+	 * @param string|array|null $filter                The SQL filters to apply to the query (the WHERE part). All columns must be prefixed by the table name (in the form: table.column)
1243
+	 * @param array             $parameters
1244
+	 * @param array             $additionalTablesFetch
1245
+	 * @param string            $className             Optional: The name of the class to instantiate. This class must extend the TDBMObject class. If none is specified, a TDBMObject instance will be returned
1246
+	 *
1247
+	 * @return AbstractTDBMObject|null The object we want, or null if no object matches the filters
1248
+	 *
1249
+	 * @throws TDBMException
1250
+	 */
1251
+	public function findObject(string $mainTable, $filter = null, array $parameters = array(), array $additionalTablesFetch = array(), string $className = null)
1252
+	{
1253
+		$objects = $this->findObjects($mainTable, $filter, $parameters, null, $additionalTablesFetch, self::MODE_ARRAY, $className);
1254
+		$page = $objects->take(0, 2);
1255
+		$count = $page->count();
1256
+		if ($count > 1) {
1257
+			throw new DuplicateRowException("Error while querying an object for table '$mainTable': More than 1 row have been returned, but we should have received at most one.");
1258
+		} elseif ($count === 0) {
1259
+			return;
1260
+		}
1261
+
1262
+		return $page[0];
1263
+	}
1264
+
1265
+	/**
1266
+	 * Returns a unique bean (or null) according to the filters passed in parameter.
1267
+	 *
1268
+	 * @param string            $mainTable  The name of the table queried
1269
+	 * @param string            $from       The from sql statement
1270
+	 * @param string|array|null $filter     The SQL filters to apply to the query (the WHERE part). All columns must be prefixed by the table name (in the form: table.column)
1271
+	 * @param array             $parameters
1272
+	 * @param string            $className  Optional: The name of the class to instantiate. This class must extend the TDBMObject class. If none is specified, a TDBMObject instance will be returned
1273
+	 *
1274
+	 * @return AbstractTDBMObject|null The object we want, or null if no object matches the filters
1275
+	 *
1276
+	 * @throws TDBMException
1277
+	 */
1278
+	public function findObjectFromSql($mainTable, $from, $filter = null, array $parameters = array(), $className = null)
1279
+	{
1280
+		$objects = $this->findObjectsFromSql($mainTable, $from, $filter, $parameters, null, self::MODE_ARRAY, $className);
1281
+		$page = $objects->take(0, 2);
1282
+		$count = $page->count();
1283
+		if ($count > 1) {
1284
+			throw new DuplicateRowException("Error while querying an object for table '$mainTable': More than 1 row have been returned, but we should have received at most one.");
1285
+		} elseif ($count === 0) {
1286
+			return;
1287
+		}
1288
+
1289
+		return $page[0];
1290
+	}
1291
+
1292
+	/**
1293
+	 * Returns a unique bean according to the filters passed in parameter.
1294
+	 * Throws a NoBeanFoundException if no bean was found for the filter passed in parameter.
1295
+	 *
1296
+	 * @param string            $mainTable             The name of the table queried
1297
+	 * @param string|array|null $filter                The SQL filters to apply to the query (the WHERE part). All columns must be prefixed by the table name (in the form: table.column)
1298
+	 * @param array             $parameters
1299
+	 * @param array             $additionalTablesFetch
1300
+	 * @param string            $className             Optional: The name of the class to instantiate. This class must extend the TDBMObject class. If none is specified, a TDBMObject instance will be returned
1301
+	 *
1302
+	 * @return AbstractTDBMObject The object we want
1303
+	 *
1304
+	 * @throws TDBMException
1305
+	 */
1306
+	public function findObjectOrFail(string $mainTable, $filter = null, array $parameters = array(), array $additionalTablesFetch = array(), string $className = null)
1307
+	{
1308
+		$bean = $this->findObject($mainTable, $filter, $parameters, $additionalTablesFetch, $className);
1309
+		if ($bean === null) {
1310
+			throw new NoBeanFoundException("No result found for query on table '".$mainTable."'");
1311
+		}
1312
+
1313
+		return $bean;
1314
+	}
1315
+
1316
+	/**
1317
+	 * @param array $beanData An array of data: array<table, array<column, value>>
1318
+	 *
1319
+	 * @return array an array with first item = class name, second item = table name and third item = list of tables needed
1320
+	 *
1321
+	 * @throws TDBMInheritanceException
1322
+	 */
1323
+	public function _getClassNameFromBeanData(array $beanData)
1324
+	{
1325
+		if (count($beanData) === 1) {
1326
+			$tableName = array_keys($beanData)[0];
1327
+			$allTables = [$tableName];
1328
+		} else {
1329
+			$tables = [];
1330
+			foreach ($beanData as $table => $row) {
1331
+				$primaryKeyColumns = $this->getPrimaryKeyColumns($table);
1332
+				$pkSet = false;
1333
+				foreach ($primaryKeyColumns as $columnName) {
1334
+					if ($row[$columnName] !== null) {
1335
+						$pkSet = true;
1336
+						break;
1337
+					}
1338
+				}
1339
+				if ($pkSet) {
1340
+					$tables[] = $table;
1341
+				}
1342
+			}
1343
+
1344
+			// $tables contains the tables for this bean. Let's view the top most part of the hierarchy
1345
+			try {
1346
+				$allTables = $this->_getLinkBetweenInheritedTables($tables);
1347
+			} catch (TDBMInheritanceException $e) {
1348
+				throw TDBMInheritanceException::extendException($e, $this, $beanData);
1349
+			}
1350
+			$tableName = $allTables[0];
1351
+		}
1352
+
1353
+		// Only one table in this bean. Life is sweat, let's look at its type:
1354
+		try {
1355
+			$className = $this->getBeanClassName($tableName);
1356
+		} catch (TDBMInvalidArgumentException $e) {
1357
+			$className = 'Mouf\\Database\\TDBM\\TDBMObject';
1358
+		}
1359
+
1360
+		return [$className, $tableName, $allTables];
1361
+	}
1362
+
1363
+	/**
1364
+	 * Returns an item from cache or computes it using $closure and puts it in cache.
1365
+	 *
1366
+	 * @param string   $key
1367
+	 * @param callable $closure
1368
+	 *
1369
+	 * @return mixed
1370
+	 */
1371
+	private function fromCache(string $key, callable $closure)
1372
+	{
1373
+		$item = $this->cache->fetch($key);
1374
+		if ($item === false) {
1375
+			$item = $closure();
1376
+			$this->cache->save($key, $item);
1377
+		}
1378
+
1379
+		return $item;
1380
+	}
1381
+
1382
+	/**
1383
+	 * Returns the foreign key object.
1384
+	 *
1385
+	 * @param string $table
1386
+	 * @param string $fkName
1387
+	 *
1388
+	 * @return ForeignKeyConstraint
1389
+	 */
1390
+	public function _getForeignKeyByName(string $table, string $fkName)
1391
+	{
1392
+		return $this->tdbmSchemaAnalyzer->getSchema()->getTable($table)->getForeignKey($fkName);
1393
+	}
1394
+
1395
+	/**
1396
+	 * @param $pivotTableName
1397
+	 * @param AbstractTDBMObject $bean
1398
+	 *
1399
+	 * @return AbstractTDBMObject[]
1400
+	 */
1401
+	public function _getRelatedBeans(string $pivotTableName, AbstractTDBMObject $bean)
1402
+	{
1403
+		list($localFk, $remoteFk) = $this->getPivotTableForeignKeys($pivotTableName, $bean);
1404
+		/* @var $localFk ForeignKeyConstraint */
1405
+		/* @var $remoteFk ForeignKeyConstraint */
1406
+		$remoteTable = $remoteFk->getForeignTableName();
1407
+
1408
+		$primaryKeys = $this->getPrimaryKeyValues($bean);
1409
+		$columnNames = array_map(function ($name) use ($pivotTableName) {
1410
+			return $pivotTableName.'.'.$name;
1411
+		}, $localFk->getLocalColumns());
1412
+
1413
+		$filter = array_combine($columnNames, $primaryKeys);
1414
+
1415
+		return $this->findObjects($remoteTable, $filter);
1416
+	}
1417
+
1418
+	/**
1419
+	 * @param $pivotTableName
1420
+	 * @param AbstractTDBMObject $bean The LOCAL bean
1421
+	 *
1422
+	 * @return ForeignKeyConstraint[] First item: the LOCAL bean, second item: the REMOTE bean
1423
+	 *
1424
+	 * @throws TDBMException
1425
+	 */
1426
+	private function getPivotTableForeignKeys(string $pivotTableName, AbstractTDBMObject $bean)
1427
+	{
1428
+		$fks = array_values($this->tdbmSchemaAnalyzer->getSchema()->getTable($pivotTableName)->getForeignKeys());
1429
+		$table1 = $fks[0]->getForeignTableName();
1430
+		$table2 = $fks[1]->getForeignTableName();
1431
+
1432
+		$beanTables = array_map(function (DbRow $dbRow) {
1433
+			return $dbRow->_getDbTableName();
1434
+		}, $bean->_getDbRows());
1435
+
1436
+		if (in_array($table1, $beanTables)) {
1437
+			return [$fks[0], $fks[1]];
1438
+		} elseif (in_array($table2, $beanTables)) {
1439
+			return [$fks[1], $fks[0]];
1440
+		} else {
1441
+			throw new TDBMException("Unexpected bean type in getPivotTableForeignKeys. Awaiting beans from table {$table1} and {$table2} for pivot table {$pivotTableName}");
1442
+		}
1443
+	}
1444
+
1445
+	/**
1446
+	 * Returns a list of pivot tables linked to $bean.
1447
+	 *
1448
+	 * @param AbstractTDBMObject $bean
1449
+	 *
1450
+	 * @return string[]
1451
+	 */
1452
+	public function _getPivotTablesLinkedToBean(AbstractTDBMObject $bean)
1453
+	{
1454
+		$junctionTables = [];
1455
+		$allJunctionTables = $this->schemaAnalyzer->detectJunctionTables(true);
1456
+		foreach ($bean->_getDbRows() as $dbRow) {
1457
+			foreach ($allJunctionTables as $table) {
1458
+				// There are exactly 2 FKs since this is a pivot table.
1459
+				$fks = array_values($table->getForeignKeys());
1460
+
1461
+				if ($fks[0]->getForeignTableName() === $dbRow->_getDbTableName() || $fks[1]->getForeignTableName() === $dbRow->_getDbTableName()) {
1462
+					$junctionTables[] = $table->getName();
1463
+				}
1464
+			}
1465
+		}
1466
+
1467
+		return $junctionTables;
1468
+	}
1469
+
1470
+	/**
1471
+	 * Array of types for tables.
1472
+	 * Key: table name
1473
+	 * Value: array of types indexed by column.
1474
+	 *
1475
+	 * @var array[]
1476
+	 */
1477
+	private $typesForTable = [];
1478
+
1479
+	/**
1480
+	 * @internal
1481
+	 *
1482
+	 * @param string $tableName
1483
+	 *
1484
+	 * @return Type[]
1485
+	 */
1486
+	public function _getColumnTypesForTable(string $tableName)
1487
+	{
1488
+		if (!isset($typesForTable[$tableName])) {
1489
+			$columns = $this->tdbmSchemaAnalyzer->getSchema()->getTable($tableName)->getColumns();
1490
+			$typesForTable[$tableName] = array_map(function (Column $column) {
1491
+				return $column->getType();
1492
+			}, $columns);
1493
+		}
1494
+
1495
+		return $typesForTable[$tableName];
1496
+	}
1497
+
1498
+	/**
1499
+	 * Sets the minimum log level.
1500
+	 * $level must be one of Psr\Log\LogLevel::xxx.
1501
+	 *
1502
+	 * Defaults to LogLevel::WARNING
1503
+	 *
1504
+	 * @param string $level
1505
+	 */
1506
+	public function setLogLevel(string $level)
1507
+	{
1508
+		$this->logger = new LevelFilter($this->rootLogger, $level);
1509
+	}
1510 1510
 }
Please login to merge, or discard this patch.
src/Mouf/Database/TDBM/UncheckedOrderBy.php 1 patch
Indentation   +18 added lines, -18 removed lines patch added patch discarded remove patch
@@ -20,24 +20,24 @@
 block discarded – undo
20 20
  */
21 21
 class UncheckedOrderBy
22 22
 {
23
-    /**
24
-     * @var string
25
-     */
26
-    private $orderBy;
23
+	/**
24
+	 * @var string
25
+	 */
26
+	private $orderBy;
27 27
 
28
-    /**
29
-     * @param $orderBy
30
-     */
31
-    public function __construct(string $orderBy)
32
-    {
33
-        $this->orderBy = $orderBy;
34
-    }
28
+	/**
29
+	 * @param $orderBy
30
+	 */
31
+	public function __construct(string $orderBy)
32
+	{
33
+		$this->orderBy = $orderBy;
34
+	}
35 35
 
36
-    /**
37
-     * @return string
38
-     */
39
-    public function getOrderBy() : string
40
-    {
41
-        return $this->orderBy;
42
-    }
36
+	/**
37
+	 * @return string
38
+	 */
39
+	public function getOrderBy() : string
40
+	{
41
+		return $this->orderBy;
42
+	}
43 43
 }
Please login to merge, or discard this patch.
src/Mouf/Database/TDBM/QueryFactory/FindObjectsQueryFactory.php 2 patches
Spacing   +1 added lines, -1 removed lines patch added patch discarded remove patch
@@ -39,7 +39,7 @@
 block discarded – undo
39 39
         $sql = 'SELECT DISTINCT '.implode(', ', $columnsList).' FROM MAGICJOIN('.$this->mainTable.')';
40 40
 
41 41
         $pkColumnNames = $this->schema->getTable($this->mainTable)->getPrimaryKeyColumns();
42
-        $pkColumnNames = array_map(function ($pkColumn) {
42
+        $pkColumnNames = array_map(function($pkColumn) {
43 43
             return $this->tdbmService->getConnection()->quoteIdentifier($this->mainTable).'.'.$this->tdbmService->getConnection()->quoteIdentifier($pkColumn);
44 44
         }, $pkColumnNames);
45 45
 
Please login to merge, or discard this patch.
Indentation   +38 added lines, -38 removed lines patch added patch discarded remove patch
@@ -11,44 +11,44 @@
 block discarded – undo
11 11
  */
12 12
 class FindObjectsQueryFactory extends AbstractQueryFactory
13 13
 {
14
-    private $mainTable;
15
-    private $additionalTablesFetch;
16
-    private $filterString;
17
-
18
-    public function __construct(string $mainTable, array $additionalTablesFetch, $filterString, $orderBy, TDBMService $tdbmService, Schema $schema, OrderByAnalyzer $orderByAnalyzer)
19
-    {
20
-        parent::__construct($tdbmService, $schema, $orderByAnalyzer, $orderBy);
21
-        $this->mainTable = $mainTable;
22
-        $this->additionalTablesFetch = $additionalTablesFetch;
23
-        $this->filterString = $filterString;
24
-    }
25
-
26
-    protected function compute()
27
-    {
28
-        list($columnDescList, $columnsList, $orderString) = $this->getColumnsList($this->mainTable, $this->additionalTablesFetch, $this->orderBy);
29
-
30
-        $sql = 'SELECT DISTINCT '.implode(', ', $columnsList).' FROM MAGICJOIN('.$this->mainTable.')';
31
-
32
-        $pkColumnNames = $this->schema->getTable($this->mainTable)->getPrimaryKeyColumns();
33
-        $pkColumnNames = array_map(function ($pkColumn) {
34
-            return $this->tdbmService->getConnection()->quoteIdentifier($this->mainTable).'.'.$this->tdbmService->getConnection()->quoteIdentifier($pkColumn);
35
-        }, $pkColumnNames);
36
-
37
-        $countSql = 'SELECT COUNT(DISTINCT '.implode(', ', $pkColumnNames).') FROM MAGICJOIN('.$this->mainTable.')';
38
-
39
-        if (!empty($this->filterString)) {
40
-            $sql .= ' WHERE '.$this->filterString;
41
-            $countSql .= ' WHERE '.$this->filterString;
42
-        }
43
-
44
-        if (!empty($orderString)) {
45
-            $sql .= ' ORDER BY '.$orderString;
46
-        }
47
-
48
-        $this->magicSql = $sql;
49
-        $this->magicSqlCount = $countSql;
50
-        $this->columnDescList = $columnDescList;
51
-    }
14
+	private $mainTable;
15
+	private $additionalTablesFetch;
16
+	private $filterString;
17
+
18
+	public function __construct(string $mainTable, array $additionalTablesFetch, $filterString, $orderBy, TDBMService $tdbmService, Schema $schema, OrderByAnalyzer $orderByAnalyzer)
19
+	{
20
+		parent::__construct($tdbmService, $schema, $orderByAnalyzer, $orderBy);
21
+		$this->mainTable = $mainTable;
22
+		$this->additionalTablesFetch = $additionalTablesFetch;
23
+		$this->filterString = $filterString;
24
+	}
25
+
26
+	protected function compute()
27
+	{
28
+		list($columnDescList, $columnsList, $orderString) = $this->getColumnsList($this->mainTable, $this->additionalTablesFetch, $this->orderBy);
29
+
30
+		$sql = 'SELECT DISTINCT '.implode(', ', $columnsList).' FROM MAGICJOIN('.$this->mainTable.')';
31
+
32
+		$pkColumnNames = $this->schema->getTable($this->mainTable)->getPrimaryKeyColumns();
33
+		$pkColumnNames = array_map(function ($pkColumn) {
34
+			return $this->tdbmService->getConnection()->quoteIdentifier($this->mainTable).'.'.$this->tdbmService->getConnection()->quoteIdentifier($pkColumn);
35
+		}, $pkColumnNames);
36
+
37
+		$countSql = 'SELECT COUNT(DISTINCT '.implode(', ', $pkColumnNames).') FROM MAGICJOIN('.$this->mainTable.')';
38
+
39
+		if (!empty($this->filterString)) {
40
+			$sql .= ' WHERE '.$this->filterString;
41
+			$countSql .= ' WHERE '.$this->filterString;
42
+		}
43
+
44
+		if (!empty($orderString)) {
45
+			$sql .= ' ORDER BY '.$orderString;
46
+		}
47
+
48
+		$this->magicSql = $sql;
49
+		$this->magicSqlCount = $countSql;
50
+		$this->columnDescList = $columnDescList;
51
+	}
52 52
 
53 53
 
54 54
 }
Please login to merge, or discard this patch.
src/Mouf/Database/TDBM/QueryFactory/QueryFactory.php 1 patch
Indentation   +18 added lines, -18 removed lines patch added patch discarded remove patch
@@ -9,25 +9,25 @@
 block discarded – undo
9 9
  */
10 10
 interface QueryFactory
11 11
 {
12
-    /**
13
-     * Sets the ORDER BY directive executed in SQL.
14
-     *
15
-     * For instance:
16
-     *
17
-     *  $queryFactory->sort('label ASC, status DESC');
18
-     *
19
-     * **Important:** TDBM does its best to protect you from SQL injection. In particular, it will only allow column names in the "ORDER BY" clause. This means you are safe to pass input from the user directly in the ORDER BY parameter.
20
-     * If you want to pass an expression to the ORDER BY clause, you will need to tell TDBM to stop checking for SQL injections. You do this by passing a `UncheckedOrderBy` object as a parameter:
21
-     *
22
-     *  $queryFactory->sort(new UncheckedOrderBy('RAND()'))
23
-     *
24
-     * @param string|UncheckedOrderBy|null $orderBy
25
-     */
26
-    public function sort($orderBy);
12
+	/**
13
+	 * Sets the ORDER BY directive executed in SQL.
14
+	 *
15
+	 * For instance:
16
+	 *
17
+	 *  $queryFactory->sort('label ASC, status DESC');
18
+	 *
19
+	 * **Important:** TDBM does its best to protect you from SQL injection. In particular, it will only allow column names in the "ORDER BY" clause. This means you are safe to pass input from the user directly in the ORDER BY parameter.
20
+	 * If you want to pass an expression to the ORDER BY clause, you will need to tell TDBM to stop checking for SQL injections. You do this by passing a `UncheckedOrderBy` object as a parameter:
21
+	 *
22
+	 *  $queryFactory->sort(new UncheckedOrderBy('RAND()'))
23
+	 *
24
+	 * @param string|UncheckedOrderBy|null $orderBy
25
+	 */
26
+	public function sort($orderBy);
27 27
 
28
-    public function getMagicSql() : string;
28
+	public function getMagicSql() : string;
29 29
 
30
-    public function getMagicSqlCount() : string;
30
+	public function getMagicSqlCount() : string;
31 31
 
32
-    public function getColumnDescriptors() : array;
32
+	public function getColumnDescriptors() : array;
33 33
 }
Please login to merge, or discard this patch.
src/Mouf/Database/TDBM/QueryFactory/AbstractQueryFactory.php 1 patch
Indentation   +210 added lines, -210 removed lines patch added patch discarded remove patch
@@ -10,214 +10,214 @@
 block discarded – undo
10 10
 
11 11
 abstract class AbstractQueryFactory implements QueryFactory
12 12
 {
13
-    /**
14
-     * @var TDBMService
15
-     */
16
-    protected $tdbmService;
17
-
18
-    /**
19
-     * @var Schema
20
-     */
21
-    protected $schema;
22
-
23
-    /**
24
-     * @var OrderByAnalyzer
25
-     */
26
-    protected $orderByAnalyzer;
27
-
28
-    /**
29
-     * @var string|UncheckedOrderBy|null
30
-     */
31
-    protected $orderBy;
32
-
33
-    protected $magicSql;
34
-    protected $magicSqlCount;
35
-    protected $columnDescList;
36
-
37
-    /**
38
-     * @param TDBMService $tdbmService
39
-     */
40
-    public function __construct(TDBMService $tdbmService, Schema $schema, OrderByAnalyzer $orderByAnalyzer, $orderBy)
41
-    {
42
-        $this->tdbmService = $tdbmService;
43
-        $this->schema = $schema;
44
-        $this->orderByAnalyzer = $orderByAnalyzer;
45
-        $this->orderBy = $orderBy;
46
-    }
47
-
48
-    /**
49
-     * Returns the column list that must be fetched for the SQL request.
50
-     *
51
-     * Note: MySQL dictates that ORDER BYed columns should appear in the SELECT clause.
52
-     *
53
-     * @param string                       $mainTable
54
-     * @param array                        $additionalTablesFetch
55
-     * @param string|UncheckedOrderBy|null $orderBy
56
-     *
57
-     * @return array
58
-     *
59
-     * @throws \Doctrine\DBAL\Schema\SchemaException
60
-     */
61
-    protected function getColumnsList(string $mainTable, array $additionalTablesFetch = array(), $orderBy = null)
62
-    {
63
-        // From the table name and the additional tables we want to fetch, let's build a list of all tables
64
-        // that must be part of the select columns.
65
-
66
-        $connection = $this->tdbmService->getConnection();
67
-
68
-        $tableGroups = [];
69
-        $allFetchedTables = $this->tdbmService->_getRelatedTablesByInheritance($mainTable);
70
-        $tableGroupName = $this->getTableGroupName($allFetchedTables);
71
-        foreach ($allFetchedTables as $table) {
72
-            $tableGroups[$table] = $tableGroupName;
73
-        }
74
-
75
-        $columnsList = [];
76
-        $columnDescList = [];
77
-        $sortColumn = 0;
78
-        $reconstructedOrderBy = null;
79
-
80
-        if (is_string($orderBy)) {
81
-            $orderBy = trim($orderBy);
82
-            if ($orderBy === '') {
83
-                $orderBy = null;
84
-            }
85
-        }
86
-
87
-        // Now, let's deal with "order by columns"
88
-        if ($orderBy !== null) {
89
-            if ($orderBy instanceof UncheckedOrderBy) {
90
-                $securedOrderBy = false;
91
-                $orderBy = $orderBy->getOrderBy();
92
-                $reconstructedOrderBy = $orderBy;
93
-            } else {
94
-                $securedOrderBy = true;
95
-                $reconstructedOrderBys = [];
96
-            }
97
-            $orderByColumns = $this->orderByAnalyzer->analyzeOrderBy($orderBy);
98
-
99
-            // If we sort by a column, there is a high chance we will fetch the bean containing this column.
100
-            // Hence, we should add the table to the $additionalTablesFetch
101
-            foreach ($orderByColumns as $orderByColumn) {
102
-                if ($orderByColumn['type'] === 'colref') {
103
-                    if ($orderByColumn['table'] !== null) {
104
-                        $additionalTablesFetch[] = $orderByColumn['table'];
105
-                    }
106
-                    if ($securedOrderBy) {
107
-                        $reconstructedOrderBys[] = ($orderByColumn['table'] !== null ? $connection->quoteIdentifier($orderByColumn['table']).'.' : '').$connection->quoteIdentifier($orderByColumn['column']).' '.$orderByColumn['direction'];
108
-                    }
109
-                } elseif ($orderByColumn['type'] === 'expr') {
110
-                    $sortColumnName = 'sort_column_'.$sortColumn;
111
-                    $columnsList[] = $orderByColumn['expr'].' as '.$sortColumnName;
112
-                    $columnDescList[] = [
113
-                        'tableGroup' => null,
114
-                    ];
115
-                    ++$sortColumn;
116
-
117
-                    if ($securedOrderBy) {
118
-                        throw new TDBMInvalidArgumentException('Invalid ORDER BY column: "'.$orderByColumn['expr'].'". If you want to use expression in your ORDER BY clause, you must wrap them in a UncheckedOrderBy object. For instance: new UncheckedOrderBy("col1 + col2 DESC")');
119
-                    }
120
-                }
121
-            }
122
-
123
-            if ($reconstructedOrderBy === null) {
124
-                $reconstructedOrderBy = implode(', ', $reconstructedOrderBys);
125
-            }
126
-        }
127
-
128
-        foreach ($additionalTablesFetch as $additionalTable) {
129
-            $relatedTables = $this->tdbmService->_getRelatedTablesByInheritance($additionalTable);
130
-            $tableGroupName = $this->getTableGroupName($relatedTables);
131
-            foreach ($relatedTables as $table) {
132
-                $tableGroups[$table] = $tableGroupName;
133
-            }
134
-            $allFetchedTables = array_merge($allFetchedTables, $relatedTables);
135
-        }
136
-
137
-        // Let's remove any duplicate
138
-        $allFetchedTables = array_flip(array_flip($allFetchedTables));
139
-
140
-        // Now, let's build the column list
141
-        foreach ($allFetchedTables as $table) {
142
-            foreach ($this->schema->getTable($table)->getColumns() as $column) {
143
-                $columnName = $column->getName();
144
-                $columnDescList[] = [
145
-                    'as' => $table.'____'.$columnName,
146
-                    'table' => $table,
147
-                    'column' => $columnName,
148
-                    'type' => $column->getType(),
149
-                    'tableGroup' => $tableGroups[$table],
150
-                ];
151
-                $columnsList[] = $connection->quoteIdentifier($table).'.'.$connection->quoteIdentifier($columnName).' as '.
152
-                    $connection->quoteIdentifier($table.'____'.$columnName);
153
-            }
154
-        }
155
-
156
-        return [$columnDescList, $columnsList, $reconstructedOrderBy];
157
-    }
158
-
159
-    abstract protected function compute();
160
-
161
-    /**
162
-     * Returns an identifier for the group of tables passed in parameter.
163
-     *
164
-     * @param string[] $relatedTables
165
-     *
166
-     * @return string
167
-     */
168
-    protected function getTableGroupName(array $relatedTables)
169
-    {
170
-        sort($relatedTables);
171
-
172
-        return implode('_``_', $relatedTables);
173
-    }
174
-
175
-    public function getMagicSql() : string
176
-    {
177
-        if ($this->magicSql === null) {
178
-            $this->compute();
179
-        }
180
-
181
-        return $this->magicSql;
182
-    }
183
-
184
-    public function getMagicSqlCount() : string
185
-    {
186
-        if ($this->magicSqlCount === null) {
187
-            $this->compute();
188
-        }
189
-
190
-        return $this->magicSqlCount;
191
-    }
192
-
193
-    public function getColumnDescriptors() : array
194
-    {
195
-        if ($this->columnDescList === null) {
196
-            $this->compute();
197
-        }
198
-
199
-        return $this->columnDescList;
200
-    }
201
-
202
-    /**
203
-     * Sets the ORDER BY directive executed in SQL.
204
-     *
205
-     * For instance:
206
-     *
207
-     *  $queryFactory->sort('label ASC, status DESC');
208
-     *
209
-     * **Important:** TDBM does its best to protect you from SQL injection. In particular, it will only allow column names in the "ORDER BY" clause. This means you are safe to pass input from the user directly in the ORDER BY parameter.
210
-     * If you want to pass an expression to the ORDER BY clause, you will need to tell TDBM to stop checking for SQL injections. You do this by passing a `UncheckedOrderBy` object as a parameter:
211
-     *
212
-     *  $queryFactory->sort(new UncheckedOrderBy('RAND()'))
213
-     *
214
-     * @param string|UncheckedOrderBy|null $orderBy
215
-     */
216
-    public function sort($orderBy)
217
-    {
218
-        $this->orderBy = $orderBy;
219
-        $this->magicSql = null;
220
-        $this->magicSqlCount = null;
221
-        $this->columnDescList = null;
222
-    }
13
+	/**
14
+	 * @var TDBMService
15
+	 */
16
+	protected $tdbmService;
17
+
18
+	/**
19
+	 * @var Schema
20
+	 */
21
+	protected $schema;
22
+
23
+	/**
24
+	 * @var OrderByAnalyzer
25
+	 */
26
+	protected $orderByAnalyzer;
27
+
28
+	/**
29
+	 * @var string|UncheckedOrderBy|null
30
+	 */
31
+	protected $orderBy;
32
+
33
+	protected $magicSql;
34
+	protected $magicSqlCount;
35
+	protected $columnDescList;
36
+
37
+	/**
38
+	 * @param TDBMService $tdbmService
39
+	 */
40
+	public function __construct(TDBMService $tdbmService, Schema $schema, OrderByAnalyzer $orderByAnalyzer, $orderBy)
41
+	{
42
+		$this->tdbmService = $tdbmService;
43
+		$this->schema = $schema;
44
+		$this->orderByAnalyzer = $orderByAnalyzer;
45
+		$this->orderBy = $orderBy;
46
+	}
47
+
48
+	/**
49
+	 * Returns the column list that must be fetched for the SQL request.
50
+	 *
51
+	 * Note: MySQL dictates that ORDER BYed columns should appear in the SELECT clause.
52
+	 *
53
+	 * @param string                       $mainTable
54
+	 * @param array                        $additionalTablesFetch
55
+	 * @param string|UncheckedOrderBy|null $orderBy
56
+	 *
57
+	 * @return array
58
+	 *
59
+	 * @throws \Doctrine\DBAL\Schema\SchemaException
60
+	 */
61
+	protected function getColumnsList(string $mainTable, array $additionalTablesFetch = array(), $orderBy = null)
62
+	{
63
+		// From the table name and the additional tables we want to fetch, let's build a list of all tables
64
+		// that must be part of the select columns.
65
+
66
+		$connection = $this->tdbmService->getConnection();
67
+
68
+		$tableGroups = [];
69
+		$allFetchedTables = $this->tdbmService->_getRelatedTablesByInheritance($mainTable);
70
+		$tableGroupName = $this->getTableGroupName($allFetchedTables);
71
+		foreach ($allFetchedTables as $table) {
72
+			$tableGroups[$table] = $tableGroupName;
73
+		}
74
+
75
+		$columnsList = [];
76
+		$columnDescList = [];
77
+		$sortColumn = 0;
78
+		$reconstructedOrderBy = null;
79
+
80
+		if (is_string($orderBy)) {
81
+			$orderBy = trim($orderBy);
82
+			if ($orderBy === '') {
83
+				$orderBy = null;
84
+			}
85
+		}
86
+
87
+		// Now, let's deal with "order by columns"
88
+		if ($orderBy !== null) {
89
+			if ($orderBy instanceof UncheckedOrderBy) {
90
+				$securedOrderBy = false;
91
+				$orderBy = $orderBy->getOrderBy();
92
+				$reconstructedOrderBy = $orderBy;
93
+			} else {
94
+				$securedOrderBy = true;
95
+				$reconstructedOrderBys = [];
96
+			}
97
+			$orderByColumns = $this->orderByAnalyzer->analyzeOrderBy($orderBy);
98
+
99
+			// If we sort by a column, there is a high chance we will fetch the bean containing this column.
100
+			// Hence, we should add the table to the $additionalTablesFetch
101
+			foreach ($orderByColumns as $orderByColumn) {
102
+				if ($orderByColumn['type'] === 'colref') {
103
+					if ($orderByColumn['table'] !== null) {
104
+						$additionalTablesFetch[] = $orderByColumn['table'];
105
+					}
106
+					if ($securedOrderBy) {
107
+						$reconstructedOrderBys[] = ($orderByColumn['table'] !== null ? $connection->quoteIdentifier($orderByColumn['table']).'.' : '').$connection->quoteIdentifier($orderByColumn['column']).' '.$orderByColumn['direction'];
108
+					}
109
+				} elseif ($orderByColumn['type'] === 'expr') {
110
+					$sortColumnName = 'sort_column_'.$sortColumn;
111
+					$columnsList[] = $orderByColumn['expr'].' as '.$sortColumnName;
112
+					$columnDescList[] = [
113
+						'tableGroup' => null,
114
+					];
115
+					++$sortColumn;
116
+
117
+					if ($securedOrderBy) {
118
+						throw new TDBMInvalidArgumentException('Invalid ORDER BY column: "'.$orderByColumn['expr'].'". If you want to use expression in your ORDER BY clause, you must wrap them in a UncheckedOrderBy object. For instance: new UncheckedOrderBy("col1 + col2 DESC")');
119
+					}
120
+				}
121
+			}
122
+
123
+			if ($reconstructedOrderBy === null) {
124
+				$reconstructedOrderBy = implode(', ', $reconstructedOrderBys);
125
+			}
126
+		}
127
+
128
+		foreach ($additionalTablesFetch as $additionalTable) {
129
+			$relatedTables = $this->tdbmService->_getRelatedTablesByInheritance($additionalTable);
130
+			$tableGroupName = $this->getTableGroupName($relatedTables);
131
+			foreach ($relatedTables as $table) {
132
+				$tableGroups[$table] = $tableGroupName;
133
+			}
134
+			$allFetchedTables = array_merge($allFetchedTables, $relatedTables);
135
+		}
136
+
137
+		// Let's remove any duplicate
138
+		$allFetchedTables = array_flip(array_flip($allFetchedTables));
139
+
140
+		// Now, let's build the column list
141
+		foreach ($allFetchedTables as $table) {
142
+			foreach ($this->schema->getTable($table)->getColumns() as $column) {
143
+				$columnName = $column->getName();
144
+				$columnDescList[] = [
145
+					'as' => $table.'____'.$columnName,
146
+					'table' => $table,
147
+					'column' => $columnName,
148
+					'type' => $column->getType(),
149
+					'tableGroup' => $tableGroups[$table],
150
+				];
151
+				$columnsList[] = $connection->quoteIdentifier($table).'.'.$connection->quoteIdentifier($columnName).' as '.
152
+					$connection->quoteIdentifier($table.'____'.$columnName);
153
+			}
154
+		}
155
+
156
+		return [$columnDescList, $columnsList, $reconstructedOrderBy];
157
+	}
158
+
159
+	abstract protected function compute();
160
+
161
+	/**
162
+	 * Returns an identifier for the group of tables passed in parameter.
163
+	 *
164
+	 * @param string[] $relatedTables
165
+	 *
166
+	 * @return string
167
+	 */
168
+	protected function getTableGroupName(array $relatedTables)
169
+	{
170
+		sort($relatedTables);
171
+
172
+		return implode('_``_', $relatedTables);
173
+	}
174
+
175
+	public function getMagicSql() : string
176
+	{
177
+		if ($this->magicSql === null) {
178
+			$this->compute();
179
+		}
180
+
181
+		return $this->magicSql;
182
+	}
183
+
184
+	public function getMagicSqlCount() : string
185
+	{
186
+		if ($this->magicSqlCount === null) {
187
+			$this->compute();
188
+		}
189
+
190
+		return $this->magicSqlCount;
191
+	}
192
+
193
+	public function getColumnDescriptors() : array
194
+	{
195
+		if ($this->columnDescList === null) {
196
+			$this->compute();
197
+		}
198
+
199
+		return $this->columnDescList;
200
+	}
201
+
202
+	/**
203
+	 * Sets the ORDER BY directive executed in SQL.
204
+	 *
205
+	 * For instance:
206
+	 *
207
+	 *  $queryFactory->sort('label ASC, status DESC');
208
+	 *
209
+	 * **Important:** TDBM does its best to protect you from SQL injection. In particular, it will only allow column names in the "ORDER BY" clause. This means you are safe to pass input from the user directly in the ORDER BY parameter.
210
+	 * If you want to pass an expression to the ORDER BY clause, you will need to tell TDBM to stop checking for SQL injections. You do this by passing a `UncheckedOrderBy` object as a parameter:
211
+	 *
212
+	 *  $queryFactory->sort(new UncheckedOrderBy('RAND()'))
213
+	 *
214
+	 * @param string|UncheckedOrderBy|null $orderBy
215
+	 */
216
+	public function sort($orderBy)
217
+	{
218
+		$this->orderBy = $orderBy;
219
+		$this->magicSql = null;
220
+		$this->magicSqlCount = null;
221
+		$this->columnDescList = null;
222
+	}
223 223
 }
Please login to merge, or discard this patch.
src/Mouf/Database/TDBM/QueryFactory/FindObjectsFromSqlQueryFactory.php 2 patches
Indentation   +187 added lines, -187 removed lines patch added patch discarded remove patch
@@ -15,191 +15,191 @@
 block discarded – undo
15 15
  */
16 16
 class FindObjectsFromSqlQueryFactory extends AbstractQueryFactory
17 17
 {
18
-    private $mainTable;
19
-    private $from;
20
-    private $filterString;
21
-    private $cache;
22
-    private $cachePrefix;
23
-
24
-    public function __construct(string $mainTable, string $from, $filterString, $orderBy, TDBMService $tdbmService, Schema $schema, OrderByAnalyzer $orderByAnalyzer, SchemaAnalyzer $schemaAnalyzer, Cache $cache, string $cachePrefix)
25
-    {
26
-        parent::__construct($tdbmService, $schema, $orderByAnalyzer, $orderBy);
27
-        $this->mainTable = $mainTable;
28
-        $this->from = $from;
29
-        $this->filterString = $filterString;
30
-        $this->schemaAnalyzer = $schemaAnalyzer;
31
-        $this->cache = $cache;
32
-        $this->cachePrefix = $cachePrefix;
33
-    }
34
-
35
-    protected function compute()
36
-    {
37
-        $connection = $this->tdbmService->getConnection();
38
-
39
-        $columnsList = null;
40
-
41
-        $allFetchedTables = $this->tdbmService->_getRelatedTablesByInheritance($this->mainTable);
42
-
43
-        $columnDescList = [];
44
-
45
-        $tableGroupName = $this->getTableGroupName($allFetchedTables);
46
-
47
-        foreach ($this->schema->getTable($this->mainTable)->getColumns() as $column) {
48
-            $columnName = $column->getName();
49
-            $columnDescList[] = [
50
-                'as' => $columnName,
51
-                'table' => $this->mainTable,
52
-                'column' => $columnName,
53
-                'type' => $column->getType(),
54
-                'tableGroup' => $tableGroupName,
55
-            ];
56
-        }
57
-
58
-        $sql = 'SELECT DISTINCT '.implode(', ', array_map(function ($columnDesc) {
59
-            return $this->tdbmService->getConnection()->quoteIdentifier($this->mainTable).'.'.$this->tdbmService->getConnection()->quoteIdentifier($columnDesc['column']);
60
-        }, $columnDescList)).' FROM '.$this->from;
61
-
62
-        if (count($allFetchedTables) > 1) {
63
-            list($columnDescList, $columnsList, $orderString) = $this->getColumnsList($this->mainTable, [], $this->orderBy);
64
-        } elseif ($this->orderBy) {
65
-            list(, , $orderString) = $this->getColumnsList($this->mainTable, [], $this->orderBy);
66
-        }
67
-
68
-        // Let's compute the COUNT.
69
-        $pkColumnNames = $this->schema->getTable($this->mainTable)->getPrimaryKeyColumns();
70
-        $pkColumnNames = array_map(function ($pkColumn) {
71
-            return $this->tdbmService->getConnection()->quoteIdentifier($this->mainTable).'.'.$this->tdbmService->getConnection()->quoteIdentifier($pkColumn);
72
-        }, $pkColumnNames);
73
-
74
-        $countSql = 'SELECT COUNT(DISTINCT '.implode(', ', $pkColumnNames).') FROM '.$this->from;
75
-
76
-        if (!empty($this->filterString)) {
77
-            $sql .= ' WHERE '.$this->filterString;
78
-            $countSql .= ' WHERE '.$this->filterString;
79
-        }
80
-
81
-        if (!empty($orderString)) {
82
-            $sql .= ' ORDER BY '.$orderString;
83
-        }
84
-
85
-        if (stripos($countSql, 'GROUP BY') !== false) {
86
-            throw new TDBMException('Unsupported use of GROUP BY in SQL request.');
87
-        }
88
-
89
-        if ($columnsList !== null) {
90
-            $joinSql = '';
91
-            $parentFks = $this->getParentRelationshipForeignKeys($this->mainTable);
92
-            foreach ($parentFks as $fk) {
93
-                $joinSql .= sprintf(' JOIN %s ON (%s.%s = %s.%s)',
94
-                    $connection->quoteIdentifier($fk->getForeignTableName()),
95
-                    $connection->quoteIdentifier($fk->getLocalTableName()),
96
-                    $connection->quoteIdentifier($fk->getLocalColumns()[0]),
97
-                    $connection->quoteIdentifier($fk->getForeignTableName()),
98
-                    $connection->quoteIdentifier($fk->getForeignColumns()[0])
99
-                );
100
-            }
101
-
102
-            $childrenFks = $this->getChildrenRelationshipForeignKeys($this->mainTable);
103
-            foreach ($childrenFks as $fk) {
104
-                $joinSql .= sprintf(' LEFT JOIN %s ON (%s.%s = %s.%s)',
105
-                    $connection->quoteIdentifier($fk->getLocalTableName()),
106
-                    $connection->quoteIdentifier($fk->getForeignTableName()),
107
-                    $connection->quoteIdentifier($fk->getForeignColumns()[0]),
108
-                    $connection->quoteIdentifier($fk->getLocalTableName()),
109
-                    $connection->quoteIdentifier($fk->getLocalColumns()[0])
110
-                );
111
-            }
112
-
113
-            $sql = 'SELECT '.implode(', ', $columnsList).' FROM ('.$sql.') AS '.$this->mainTable.' '.$joinSql;
114
-            if (!empty($orderString)) {
115
-                $sql .= ' ORDER BY '.$orderString;
116
-            }
117
-        }
118
-
119
-        $this->magicSql = $sql;
120
-        $this->magicSqlCount = $countSql;
121
-        $this->columnDescList = $columnDescList;
122
-    }
123
-
124
-    /**
125
-     * @param string $tableName
126
-     *
127
-     * @return ForeignKeyConstraint[]
128
-     */
129
-    private function getParentRelationshipForeignKeys($tableName)
130
-    {
131
-        return $this->fromCache($this->cachePrefix.'_parentrelationshipfks_'.$tableName, function () use ($tableName) {
132
-            return $this->getParentRelationshipForeignKeysWithoutCache($tableName);
133
-        });
134
-    }
135
-
136
-    /**
137
-     * @param string $tableName
138
-     *
139
-     * @return ForeignKeyConstraint[]
140
-     */
141
-    private function getParentRelationshipForeignKeysWithoutCache($tableName)
142
-    {
143
-        $parentFks = [];
144
-        $currentTable = $tableName;
145
-        while ($currentFk = $this->schemaAnalyzer->getParentRelationship($currentTable)) {
146
-            $currentTable = $currentFk->getForeignTableName();
147
-            $parentFks[] = $currentFk;
148
-        }
149
-
150
-        return $parentFks;
151
-    }
152
-
153
-    /**
154
-     * @param string $tableName
155
-     *
156
-     * @return ForeignKeyConstraint[]
157
-     */
158
-    private function getChildrenRelationshipForeignKeys(string $tableName) : array
159
-    {
160
-        return $this->fromCache($this->cachePrefix.'_childrenrelationshipfks_'.$tableName, function () use ($tableName) {
161
-            return $this->getChildrenRelationshipForeignKeysWithoutCache($tableName);
162
-        });
163
-    }
164
-
165
-    /**
166
-     * @param string $tableName
167
-     *
168
-     * @return ForeignKeyConstraint[]
169
-     */
170
-    private function getChildrenRelationshipForeignKeysWithoutCache(string $tableName) : array
171
-    {
172
-        $children = $this->schemaAnalyzer->getChildrenRelationships($tableName);
173
-
174
-        if (!empty($children)) {
175
-            $fksTables = array_map(function (ForeignKeyConstraint $fk) {
176
-                return $this->getChildrenRelationshipForeignKeys($fk->getLocalTableName());
177
-            }, $children);
178
-
179
-            $fks = array_merge($children, call_user_func_array('array_merge', $fksTables));
180
-
181
-            return $fks;
182
-        } else {
183
-            return [];
184
-        }
185
-    }
186
-
187
-    /**
188
-     * Returns an item from cache or computes it using $closure and puts it in cache.
189
-     *
190
-     * @param string   $key
191
-     * @param callable $closure
192
-     *
193
-     * @return mixed
194
-     */
195
-    protected function fromCache(string $key, callable $closure)
196
-    {
197
-        $item = $this->cache->fetch($key);
198
-        if ($item === false) {
199
-            $item = $closure();
200
-            $this->cache->save($key, $item);
201
-        }
202
-
203
-        return $item;
204
-    }
18
+	private $mainTable;
19
+	private $from;
20
+	private $filterString;
21
+	private $cache;
22
+	private $cachePrefix;
23
+
24
+	public function __construct(string $mainTable, string $from, $filterString, $orderBy, TDBMService $tdbmService, Schema $schema, OrderByAnalyzer $orderByAnalyzer, SchemaAnalyzer $schemaAnalyzer, Cache $cache, string $cachePrefix)
25
+	{
26
+		parent::__construct($tdbmService, $schema, $orderByAnalyzer, $orderBy);
27
+		$this->mainTable = $mainTable;
28
+		$this->from = $from;
29
+		$this->filterString = $filterString;
30
+		$this->schemaAnalyzer = $schemaAnalyzer;
31
+		$this->cache = $cache;
32
+		$this->cachePrefix = $cachePrefix;
33
+	}
34
+
35
+	protected function compute()
36
+	{
37
+		$connection = $this->tdbmService->getConnection();
38
+
39
+		$columnsList = null;
40
+
41
+		$allFetchedTables = $this->tdbmService->_getRelatedTablesByInheritance($this->mainTable);
42
+
43
+		$columnDescList = [];
44
+
45
+		$tableGroupName = $this->getTableGroupName($allFetchedTables);
46
+
47
+		foreach ($this->schema->getTable($this->mainTable)->getColumns() as $column) {
48
+			$columnName = $column->getName();
49
+			$columnDescList[] = [
50
+				'as' => $columnName,
51
+				'table' => $this->mainTable,
52
+				'column' => $columnName,
53
+				'type' => $column->getType(),
54
+				'tableGroup' => $tableGroupName,
55
+			];
56
+		}
57
+
58
+		$sql = 'SELECT DISTINCT '.implode(', ', array_map(function ($columnDesc) {
59
+			return $this->tdbmService->getConnection()->quoteIdentifier($this->mainTable).'.'.$this->tdbmService->getConnection()->quoteIdentifier($columnDesc['column']);
60
+		}, $columnDescList)).' FROM '.$this->from;
61
+
62
+		if (count($allFetchedTables) > 1) {
63
+			list($columnDescList, $columnsList, $orderString) = $this->getColumnsList($this->mainTable, [], $this->orderBy);
64
+		} elseif ($this->orderBy) {
65
+			list(, , $orderString) = $this->getColumnsList($this->mainTable, [], $this->orderBy);
66
+		}
67
+
68
+		// Let's compute the COUNT.
69
+		$pkColumnNames = $this->schema->getTable($this->mainTable)->getPrimaryKeyColumns();
70
+		$pkColumnNames = array_map(function ($pkColumn) {
71
+			return $this->tdbmService->getConnection()->quoteIdentifier($this->mainTable).'.'.$this->tdbmService->getConnection()->quoteIdentifier($pkColumn);
72
+		}, $pkColumnNames);
73
+
74
+		$countSql = 'SELECT COUNT(DISTINCT '.implode(', ', $pkColumnNames).') FROM '.$this->from;
75
+
76
+		if (!empty($this->filterString)) {
77
+			$sql .= ' WHERE '.$this->filterString;
78
+			$countSql .= ' WHERE '.$this->filterString;
79
+		}
80
+
81
+		if (!empty($orderString)) {
82
+			$sql .= ' ORDER BY '.$orderString;
83
+		}
84
+
85
+		if (stripos($countSql, 'GROUP BY') !== false) {
86
+			throw new TDBMException('Unsupported use of GROUP BY in SQL request.');
87
+		}
88
+
89
+		if ($columnsList !== null) {
90
+			$joinSql = '';
91
+			$parentFks = $this->getParentRelationshipForeignKeys($this->mainTable);
92
+			foreach ($parentFks as $fk) {
93
+				$joinSql .= sprintf(' JOIN %s ON (%s.%s = %s.%s)',
94
+					$connection->quoteIdentifier($fk->getForeignTableName()),
95
+					$connection->quoteIdentifier($fk->getLocalTableName()),
96
+					$connection->quoteIdentifier($fk->getLocalColumns()[0]),
97
+					$connection->quoteIdentifier($fk->getForeignTableName()),
98
+					$connection->quoteIdentifier($fk->getForeignColumns()[0])
99
+				);
100
+			}
101
+
102
+			$childrenFks = $this->getChildrenRelationshipForeignKeys($this->mainTable);
103
+			foreach ($childrenFks as $fk) {
104
+				$joinSql .= sprintf(' LEFT JOIN %s ON (%s.%s = %s.%s)',
105
+					$connection->quoteIdentifier($fk->getLocalTableName()),
106
+					$connection->quoteIdentifier($fk->getForeignTableName()),
107
+					$connection->quoteIdentifier($fk->getForeignColumns()[0]),
108
+					$connection->quoteIdentifier($fk->getLocalTableName()),
109
+					$connection->quoteIdentifier($fk->getLocalColumns()[0])
110
+				);
111
+			}
112
+
113
+			$sql = 'SELECT '.implode(', ', $columnsList).' FROM ('.$sql.') AS '.$this->mainTable.' '.$joinSql;
114
+			if (!empty($orderString)) {
115
+				$sql .= ' ORDER BY '.$orderString;
116
+			}
117
+		}
118
+
119
+		$this->magicSql = $sql;
120
+		$this->magicSqlCount = $countSql;
121
+		$this->columnDescList = $columnDescList;
122
+	}
123
+
124
+	/**
125
+	 * @param string $tableName
126
+	 *
127
+	 * @return ForeignKeyConstraint[]
128
+	 */
129
+	private function getParentRelationshipForeignKeys($tableName)
130
+	{
131
+		return $this->fromCache($this->cachePrefix.'_parentrelationshipfks_'.$tableName, function () use ($tableName) {
132
+			return $this->getParentRelationshipForeignKeysWithoutCache($tableName);
133
+		});
134
+	}
135
+
136
+	/**
137
+	 * @param string $tableName
138
+	 *
139
+	 * @return ForeignKeyConstraint[]
140
+	 */
141
+	private function getParentRelationshipForeignKeysWithoutCache($tableName)
142
+	{
143
+		$parentFks = [];
144
+		$currentTable = $tableName;
145
+		while ($currentFk = $this->schemaAnalyzer->getParentRelationship($currentTable)) {
146
+			$currentTable = $currentFk->getForeignTableName();
147
+			$parentFks[] = $currentFk;
148
+		}
149
+
150
+		return $parentFks;
151
+	}
152
+
153
+	/**
154
+	 * @param string $tableName
155
+	 *
156
+	 * @return ForeignKeyConstraint[]
157
+	 */
158
+	private function getChildrenRelationshipForeignKeys(string $tableName) : array
159
+	{
160
+		return $this->fromCache($this->cachePrefix.'_childrenrelationshipfks_'.$tableName, function () use ($tableName) {
161
+			return $this->getChildrenRelationshipForeignKeysWithoutCache($tableName);
162
+		});
163
+	}
164
+
165
+	/**
166
+	 * @param string $tableName
167
+	 *
168
+	 * @return ForeignKeyConstraint[]
169
+	 */
170
+	private function getChildrenRelationshipForeignKeysWithoutCache(string $tableName) : array
171
+	{
172
+		$children = $this->schemaAnalyzer->getChildrenRelationships($tableName);
173
+
174
+		if (!empty($children)) {
175
+			$fksTables = array_map(function (ForeignKeyConstraint $fk) {
176
+				return $this->getChildrenRelationshipForeignKeys($fk->getLocalTableName());
177
+			}, $children);
178
+
179
+			$fks = array_merge($children, call_user_func_array('array_merge', $fksTables));
180
+
181
+			return $fks;
182
+		} else {
183
+			return [];
184
+		}
185
+	}
186
+
187
+	/**
188
+	 * Returns an item from cache or computes it using $closure and puts it in cache.
189
+	 *
190
+	 * @param string   $key
191
+	 * @param callable $closure
192
+	 *
193
+	 * @return mixed
194
+	 */
195
+	protected function fromCache(string $key, callable $closure)
196
+	{
197
+		$item = $this->cache->fetch($key);
198
+		if ($item === false) {
199
+			$item = $closure();
200
+			$this->cache->save($key, $item);
201
+		}
202
+
203
+		return $item;
204
+	}
205 205
 }
Please login to merge, or discard this patch.
Spacing   +7 added lines, -7 removed lines patch added patch discarded remove patch
@@ -55,19 +55,19 @@  discard block
 block discarded – undo
55 55
             ];
56 56
         }
57 57
 
58
-        $sql = 'SELECT DISTINCT '.implode(', ', array_map(function ($columnDesc) {
58
+        $sql = 'SELECT DISTINCT '.implode(', ', array_map(function($columnDesc) {
59 59
             return $this->tdbmService->getConnection()->quoteIdentifier($this->mainTable).'.'.$this->tdbmService->getConnection()->quoteIdentifier($columnDesc['column']);
60 60
         }, $columnDescList)).' FROM '.$this->from;
61 61
 
62
-        if (count($allFetchedTables) > 1) {
62
+        if (count($allFetchedTables)>1) {
63 63
             list($columnDescList, $columnsList, $orderString) = $this->getColumnsList($this->mainTable, [], $this->orderBy);
64 64
         } elseif ($this->orderBy) {
65
-            list(, , $orderString) = $this->getColumnsList($this->mainTable, [], $this->orderBy);
65
+            list(,, $orderString) = $this->getColumnsList($this->mainTable, [], $this->orderBy);
66 66
         }
67 67
 
68 68
         // Let's compute the COUNT.
69 69
         $pkColumnNames = $this->schema->getTable($this->mainTable)->getPrimaryKeyColumns();
70
-        $pkColumnNames = array_map(function ($pkColumn) {
70
+        $pkColumnNames = array_map(function($pkColumn) {
71 71
             return $this->tdbmService->getConnection()->quoteIdentifier($this->mainTable).'.'.$this->tdbmService->getConnection()->quoteIdentifier($pkColumn);
72 72
         }, $pkColumnNames);
73 73
 
@@ -128,7 +128,7 @@  discard block
 block discarded – undo
128 128
      */
129 129
     private function getParentRelationshipForeignKeys($tableName)
130 130
     {
131
-        return $this->fromCache($this->cachePrefix.'_parentrelationshipfks_'.$tableName, function () use ($tableName) {
131
+        return $this->fromCache($this->cachePrefix.'_parentrelationshipfks_'.$tableName, function() use ($tableName) {
132 132
             return $this->getParentRelationshipForeignKeysWithoutCache($tableName);
133 133
         });
134 134
     }
@@ -157,7 +157,7 @@  discard block
 block discarded – undo
157 157
      */
158 158
     private function getChildrenRelationshipForeignKeys(string $tableName) : array
159 159
     {
160
-        return $this->fromCache($this->cachePrefix.'_childrenrelationshipfks_'.$tableName, function () use ($tableName) {
160
+        return $this->fromCache($this->cachePrefix.'_childrenrelationshipfks_'.$tableName, function() use ($tableName) {
161 161
             return $this->getChildrenRelationshipForeignKeysWithoutCache($tableName);
162 162
         });
163 163
     }
@@ -172,7 +172,7 @@  discard block
 block discarded – undo
172 172
         $children = $this->schemaAnalyzer->getChildrenRelationships($tableName);
173 173
 
174 174
         if (!empty($children)) {
175
-            $fksTables = array_map(function (ForeignKeyConstraint $fk) {
175
+            $fksTables = array_map(function(ForeignKeyConstraint $fk) {
176 176
                 return $this->getChildrenRelationshipForeignKeys($fk->getLocalTableName());
177 177
             }, $children);
178 178
 
Please login to merge, or discard this patch.
src/Mouf/Database/TDBM/Utils/MethodDescriptorInterface.php 1 patch
Indentation   +28 added lines, -28 removed lines patch added patch discarded remove patch
@@ -4,36 +4,36 @@
 block discarded – undo
4 4
 
5 5
 interface MethodDescriptorInterface
6 6
 {
7
-    /**
8
-     * Returns the name of the method to be generated.
9
-     *
10
-     * @return string
11
-     */
12
-    public function getName() : string;
7
+	/**
8
+	 * Returns the name of the method to be generated.
9
+	 *
10
+	 * @return string
11
+	 */
12
+	public function getName() : string;
13 13
 
14
-    /**
15
-     * Requests the use of an alternative name for this method.
16
-     */
17
-    public function useAlternativeName();
14
+	/**
15
+	 * Requests the use of an alternative name for this method.
16
+	 */
17
+	public function useAlternativeName();
18 18
 
19
-    /**
20
-     * Returns the code of the method.
21
-     *
22
-     * @return string
23
-     */
24
-    public function getCode() : string;
19
+	/**
20
+	 * Returns the code of the method.
21
+	 *
22
+	 * @return string
23
+	 */
24
+	public function getCode() : string;
25 25
 
26
-    /**
27
-     * Returns an array of classes that needs a "use" for this method.
28
-     *
29
-     * @return string[]
30
-     */
31
-    public function getUsedClasses() : array;
26
+	/**
27
+	 * Returns an array of classes that needs a "use" for this method.
28
+	 *
29
+	 * @return string[]
30
+	 */
31
+	public function getUsedClasses() : array;
32 32
 
33
-    /**
34
-     * Returns the code to past in jsonSerialize.
35
-     *
36
-     * @return string
37
-     */
38
-    public function getJsonSerializeCode() : string;
33
+	/**
34
+	 * Returns the code to past in jsonSerialize.
35
+	 *
36
+	 * @return string
37
+	 */
38
+	public function getJsonSerializeCode() : string;
39 39
 }
Please login to merge, or discard this patch.