TYPO3CMS  8
 All Classes Namespaces Files Functions Variables Pages
Typo3DbBackend.php
Go to the documentation of this file.
1 <?php
2 namespace TYPO3\CMS\Extbase\Persistence\Generic\Storage;
3 
4 /*
5  * This file is part of the TYPO3 CMS project.
6  *
7  * It is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License, either version 2
9  * of the License, or any later version.
10  *
11  * For the full copyright and license information, please read the
12  * LICENSE.txt file that was distributed with this source code.
13  *
14  * The TYPO3 project - inspiring people to share!
15  */
16 
17 use Doctrine\DBAL\DBALException;
27 use TYPO3\CMS\Extbase\Persistence\Generic\Qom;
30 
35 {
39  protected $connectionPool;
40 
44  protected $dataMapper;
45 
51  protected $pageRepository;
52 
58  protected $pageTSConfigCache = [];
59 
64 
68  protected $cacheService;
69 
74 
78  protected $objectManager;
79 
83  public function injectDataMapper(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper $dataMapper)
84  {
85  $this->dataMapper = $dataMapper;
86  }
87 
92  {
93  $this->configurationManager = $configurationManager;
94  }
95 
99  public function injectCacheService(\TYPO3\CMS\Extbase\Service\CacheService $cacheService)
100  {
101  $this->cacheService = $cacheService;
102  }
103 
107  public function injectEnvironmentService(\TYPO3\CMS\Extbase\Service\EnvironmentService $environmentService)
108  {
109  $this->environmentService = $environmentService;
110  }
111 
115  public function injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager)
116  {
117  $this->objectManager = $objectManager;
118  }
119 
123  public function __construct()
124  {
125  $this->connectionPool = GeneralUtility::makeInstance(ConnectionPool::class);
126  }
127 
137  public function addRow($tableName, array $fieldValues, $isRelation = false)
138  {
139  if (isset($fieldValues['uid'])) {
140  unset($fieldValues['uid']);
141  }
142  try {
143  $connection = $this->connectionPool->getConnectionForTable($tableName);
144  $connection->insert($tableName, $fieldValues);
145  } catch (DBALException $e) {
146  throw new SqlErrorException($e->getPrevious()->getMessage(), 1470230766);
147  }
148 
149  $uid = $connection->lastInsertId($tableName);
150 
151  if (!$isRelation) {
152  $this->clearPageCache($tableName, $uid);
153  }
154  return (int)$uid;
155  }
156 
167  public function updateRow($tableName, array $fieldValues, $isRelation = false)
168  {
169  if (!isset($fieldValues['uid'])) {
170  throw new \InvalidArgumentException('The given row must contain a value for "uid".', 1476045164);
171  }
172 
173  $uid = (int)$fieldValues['uid'];
174  unset($fieldValues['uid']);
175 
176  try {
177  $this->connectionPool->getConnectionForTable($tableName)
178  ->update($tableName, $fieldValues, ['uid' => $uid]);
179  } catch (DBALException $e) {
180  throw new SqlErrorException($e->getPrevious()->getMessage(), 1470230767);
181  }
182 
183  if (!$isRelation) {
184  $this->clearPageCache($tableName, $uid);
185  }
186 
187  // always returns true
188  return true;
189  }
190 
200  public function updateRelationTableRow($tableName, array $fieldValues)
201  {
202  if (!isset($fieldValues['uid_local']) && !isset($fieldValues['uid_foreign'])) {
203  throw new \InvalidArgumentException(
204  'The given fieldValues must contain a value for "uid_local" and "uid_foreign".', 1360500126
205  );
206  }
207 
208  $where['uid_local'] = (int)$fieldValues['uid_local'];
209  $where['uid_foreign'] = (int)$fieldValues['uid_foreign'];
210  unset($fieldValues['uid_local']);
211  unset($fieldValues['uid_foreign']);
212 
213  if (!empty($fieldValues['tablenames'])) {
214  $where['tablenames'] = $fieldValues['tablenames'];
215  unset($fieldValues['tablenames']);
216  }
217  if (!empty($fieldValues['fieldname'])) {
218  $where['fieldname'] = $fieldValues['fieldname'];
219  unset($fieldValues['fieldname']);
220  }
221 
222  try {
223  $this->connectionPool->getConnectionForTable($tableName)
224  ->update($tableName, $fieldValues, $where);
225  } catch (DBALException $e) {
226  throw new SqlErrorException($e->getPrevious()->getMessage(), 1470230768);
227  }
228 
229  // always returns true
230  return true;
231  }
232 
242  public function removeRow($tableName, array $where, $isRelation = false)
243  {
244  try {
245  $this->connectionPool->getConnectionForTable($tableName)->delete($tableName, $where);
246  } catch (DBALException $e) {
247  throw new SqlErrorException($e->getPrevious()->getMessage(), 1470230769);
248  }
249 
250  if (!$isRelation && isset($where['uid'])) {
251  $this->clearPageCache($tableName, $where['uid']);
252  }
253 
254  // always returns true
255  return true;
256  }
257 
267  public function getMaxValueFromTable($tableName, array $where, $columnName)
268  {
269  try {
270  $queryBuilder = $this->connectionPool->getQueryBuilderForTable($tableName);
271  $queryBuilder->getRestrictions()->removeAll();
272  $queryBuilder
273  ->select($columnName)
274  ->from($tableName)
275  ->orderBy($columnName, 'DESC')
276  ->setMaxResults(1);
277 
278  foreach ($where as $fieldName => $value) {
279  $queryBuilder->andWhere(
280  $queryBuilder->expr()->eq($fieldName, $queryBuilder->createNamedParameter($value, \PDO::PARAM_STR))
281  );
282  }
283 
284  $result = $queryBuilder->execute()->fetchColumn(0);
285  } catch (DBALException $e) {
286  throw new SqlErrorException($e->getPrevious()->getMessage(), 1470230770);
287  }
288  return $result;
289  }
290 
299  public function getRowByIdentifier($tableName, array $where)
300  {
301  try {
302  $queryBuilder = $this->connectionPool->getQueryBuilderForTable($tableName);
303  $queryBuilder->getRestrictions()->removeAll();
304  $queryBuilder
305  ->select('*')
306  ->from($tableName);
307 
308  foreach ($where as $fieldName => $value) {
309  $queryBuilder->andWhere(
310  $queryBuilder->expr()->eq($fieldName, $queryBuilder->createNamedParameter($value, \PDO::PARAM_STR))
311  );
312  }
313 
314  $row = $queryBuilder->execute()->fetch();
315  } catch (DBALException $e) {
316  throw new SqlErrorException($e->getPrevious()->getMessage(), 1470230771);
317  }
318  return $row ?: false;
319  }
320 
328  public function getObjectDataByQuery(QueryInterface $query)
329  {
330  $statement = $query->getStatement();
331  if ($statement instanceof Qom\Statement) {
332  $rows = $this->getObjectDataByRawQuery($statement);
333  } else {
334  $queryBuilder = $this->objectManager->get(Typo3DbQueryParser::class)
335  ->convertQueryToDoctrineQueryBuilder($query);
336  if ($query->getOffset()) {
337  $queryBuilder->setFirstResult($query->getOffset());
338  }
339  if ($query->getLimit()) {
340  $queryBuilder->setMaxResults($query->getLimit());
341  }
342  try {
343  $rows = $queryBuilder->execute()->fetchAll();
344  } catch (DBALException $e) {
345  throw new SqlErrorException($e->getPrevious()->getMessage(), 1472074485);
346  }
347  }
348 
349  $rows = $this->doLanguageAndWorkspaceOverlay($query->getSource(), $rows, $query->getQuerySettings());
350  return $rows;
351  }
352 
360  protected function getObjectDataByRawQuery(Qom\Statement $statement)
361  {
362  $realStatement = $statement->getStatement();
363  $parameters = $statement->getBoundVariables();
364 
365  // The real statement is an instance of the Doctrine DBAL QueryBuilder, so fetching
366  // this directly is possible
367  if ($realStatement instanceof QueryBuilder) {
368  try {
369  $result = $realStatement->execute();
370  } catch (DBALException $e) {
371  throw new SqlErrorException($e->getPrevious()->getMessage(), 1472064721);
372  }
373  $rows = $result->fetchAll();
374  } elseif ($realStatement instanceof \Doctrine\DBAL\Statement) {
375  try {
376  $result = $realStatement->execute($parameters);
377  } catch (DBALException $e) {
378  throw new SqlErrorException($e->getPrevious()->getMessage(), 1481281404);
379  }
380  $rows = $result->fetchAll();
381  } elseif ($realStatement instanceof \TYPO3\CMS\Core\Database\PreparedStatement) {
382  $realStatement->execute($parameters);
383  $rows = $realStatement->fetchAll();
384 
385  $realStatement->free();
386  } else {
387  // Do a real raw query. This is very stupid, as it does not allow to use DBAL's real power if
388  // several tables are on different databases, so this is used with caution and could be removed
389  // in the future
390  try {
391  $connection = $this->connectionPool->getConnectionByName(ConnectionPool::DEFAULT_CONNECTION_NAME);
392  $statement = $connection->executeQuery($realStatement);
393  } catch (DBALException $e) {
394  throw new SqlErrorException($e->getPrevious()->getMessage(), 1472064775);
395  }
396 
397  $rows = $statement->fetchAll();
398  }
399 
400  return $rows;
401  }
402 
411  public function getObjectCountByQuery(QueryInterface $query)
412  {
413  if ($query->getConstraint() instanceof Qom\Statement) {
414  throw new \TYPO3\CMS\Extbase\Persistence\Generic\Storage\Exception\BadConstraintException('Could not execute count on queries with a constraint of type TYPO3\\CMS\\Extbase\\Persistence\\Generic\\Qom\\Statement', 1256661045);
415  }
416 
417  $queryBuilder = $this->objectManager->get(Typo3DbQueryParser::class)
418  ->convertQueryToDoctrineQueryBuilder($query);
419  try {
420  $count = $queryBuilder->count('*')->execute()->fetchColumn(0);
421  } catch (DBALException $e) {
422  throw new SqlErrorException($e->getPrevious()->getMessage(), 1472074379);
423  }
424  if ($query->getOffset()) {
425  $count -= $query->getOffset();
426  }
427  if ($query->getLimit()) {
428  $count = min($count, $query->getLimit());
429  }
430  return (int)max(0, $count);
431  }
432 
441  {
442  $dataMap = $this->dataMapper->getDataMap(get_class($object));
443  $tableName = $dataMap->getTableName();
444  $queryBuilder = $this->connectionPool->getQueryBuilderForTable($tableName);
445  if ($this->environmentService->isEnvironmentInFrontendMode()) {
446  $queryBuilder->setRestrictions(GeneralUtility::makeInstance(FrontendRestrictionContainer::class));
447  }
448  $whereClause = [];
449  // loop over all properties of the object to exactly set the values of each database field
450  $properties = $object->_getProperties();
451  foreach ($properties as $propertyName => $propertyValue) {
452  // @todo We couple the Backend to the Entity implementation (uid, isClone); changes there breaks this method
453  if ($dataMap->isPersistableProperty($propertyName) && $propertyName !== 'uid' && $propertyName !== 'pid' && $propertyName !== 'isClone') {
454  $fieldName = $dataMap->getColumnMap($propertyName)->getColumnName();
455  if ($propertyValue === null) {
456  $whereClause[] = $queryBuilder->expr()->isNull($fieldName);
457  } else {
458  $whereClause[] = $queryBuilder->expr()->eq($fieldName, $queryBuilder->createNamedParameter($this->dataMapper->getPlainValue($propertyValue)));
459  }
460  }
461  }
462  $queryBuilder
463  ->select('uid')
464  ->from($tableName)
465  ->where(...$whereClause);
466 
467  try {
468  $uid = (int)$queryBuilder
469  ->execute()
470  ->fetchColumn(0);
471  if ($uid > 0) {
472  return $uid;
473  } else {
474  return false;
475  }
476  } catch (DBALException $e) {
477  throw new SqlErrorException($e->getPrevious()->getMessage(), 1470231748);
478  }
479  }
480 
491  protected function doLanguageAndWorkspaceOverlay(Qom\SourceInterface $source, array $rows, \TYPO3\CMS\Extbase\Persistence\Generic\QuerySettingsInterface $querySettings, $workspaceUid = null)
492  {
493  if ($source instanceof Qom\SelectorInterface) {
494  $tableName = $source->getSelectorName();
495  } elseif ($source instanceof Qom\JoinInterface) {
496  $tableName = $source->getRight()->getSelectorName();
497  } else {
498  // No proper source, so we do not have a table name here
499  // we cannot do an overlay and return the original rows instead.
500  return $rows;
501  }
502 
504  if (is_object($GLOBALS['TSFE'])) {
505  if ($workspaceUid !== null) {
506  $pageRepository->versioningWorkspaceId = $workspaceUid;
507  }
508  } else {
509  if ($workspaceUid === null) {
510  $workspaceUid = $GLOBALS['BE_USER']->workspace;
511  }
512  $pageRepository->versioningWorkspaceId = $workspaceUid;
513  }
514 
515  // Fetches the move-placeholder in case it is supported
516  // by the table and if there's only one row in the result set
517  // (applying this to all rows does not work, since the sorting
518  // order would be destroyed and possible limits not met anymore)
519  if (!empty($pageRepository->versioningWorkspaceId)
521  && count($rows) === 1
522  ) {
523  $versionId = $pageRepository->versioningWorkspaceId;
524  $queryBuilder = $this->connectionPool->getQueryBuilderForTable($tableName);
525  $queryBuilder->getRestrictions()->removeAll();
526  $movePlaceholder = $queryBuilder->select($tableName . '.*')
527  ->from($tableName)
528  ->where(
529  $queryBuilder->expr()->eq('t3ver_state', $queryBuilder->createNamedParameter(3, \PDO::PARAM_INT)),
530  $queryBuilder->expr()->eq('t3ver_wsid', $queryBuilder->createNamedParameter($versionId, \PDO::PARAM_INT)),
531  $queryBuilder->expr()->eq('t3ver_move_id', $queryBuilder->createNamedParameter($rows[0]['uid'], \PDO::PARAM_INT))
532  )
533  ->setMaxResults(1)
534  ->execute()
535  ->fetch();
536  if (!empty($movePlaceholder)) {
537  $rows = [$movePlaceholder];
538  }
539  }
540 
541  $overlaidRows = [];
542  foreach ($rows as $row) {
543  // If current row is a translation select its parent
544  if (isset($tableName) && isset($GLOBALS['TCA'][$tableName])
545  && isset($GLOBALS['TCA'][$tableName]['ctrl']['languageField'])
546  && isset($GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField'])
547  && $tableName !== 'pages_language_overlay'
548  ) {
549  if (isset($row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']])
550  && $row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']] > 0
551  ) {
552  $queryBuilder = $this->connectionPool->getQueryBuilderForTable($tableName);
553  $queryBuilder->getRestrictions()->removeAll();
554  $row = $queryBuilder->select($tableName . '.*')
555  ->from($tableName)
556  ->where(
557  $queryBuilder->expr()->eq(
558  $tableName . '.uid',
559  $queryBuilder->createNamedParameter(
560  $row[$GLOBALS['TCA'][$tableName]['ctrl']['transOrigPointerField']],
561  \PDO::PARAM_INT
562  )
563  ),
564  $queryBuilder->expr()->eq(
565  $tableName . '.' . $GLOBALS['TCA'][$tableName]['ctrl']['languageField'],
566  $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT)
567  )
568  )
569  ->setMaxResults(1)
570  ->execute()
571  ->fetch();
572  }
573  }
574  $pageRepository->versionOL($tableName, $row, true);
575  if ($tableName == 'pages') {
576  $row = $pageRepository->getPageOverlay($row, $querySettings->getLanguageUid());
577  } elseif (isset($GLOBALS['TCA'][$tableName]['ctrl']['languageField'])
578  && $GLOBALS['TCA'][$tableName]['ctrl']['languageField'] !== ''
579  && $tableName !== 'pages_language_overlay'
580  ) {
581  if (in_array($row[$GLOBALS['TCA'][$tableName]['ctrl']['languageField']], [-1, 0])) {
582  $overlayMode = $querySettings->getLanguageMode() === 'strict' ? 'hideNonTranslated' : '';
583  $row = $pageRepository->getRecordOverlay($tableName, $row, $querySettings->getLanguageUid(), $overlayMode);
584  }
585  }
586  if ($row !== null && is_array($row)) {
587  $overlaidRows[] = $row;
588  }
589  }
590  return $overlaidRows;
591  }
592 
596  protected function getPageRepository()
597  {
598  if (!$this->pageRepository instanceof \TYPO3\CMS\Frontend\Page\PageRepository) {
599  if ($this->environmentService->isEnvironmentInFrontendMode() && is_object($GLOBALS['TSFE'])) {
600  $this->pageRepository = $GLOBALS['TSFE']->sys_page;
601  } else {
602  $this->pageRepository = GeneralUtility::makeInstance(\TYPO3\CMS\Frontend\Page\PageRepository::class);
603  }
604  }
605 
606  return $this->pageRepository;
607  }
608 
620  protected function clearPageCache($tableName, $uid)
621  {
622  $frameworkConfiguration = $this->configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK);
623  if (isset($frameworkConfiguration['persistence']['enableAutomaticCacheClearing']) && $frameworkConfiguration['persistence']['enableAutomaticCacheClearing'] === '1') {
624  } else {
625  // if disabled, return
626  return;
627  }
628  $pageIdsToClear = [];
629  $storagePage = null;
630  $columns = GeneralUtility::makeInstance(ConnectionPool::class)
631  ->getConnectionForTable($tableName)
632  ->getSchemaManager()
633  ->listTableColumns($tableName);
634  if (array_key_exists('pid', $columns)) {
635  $queryBuilder = $this->connectionPool->getQueryBuilderForTable($tableName);
636  $queryBuilder->getRestrictions()->removeAll();
637  $result = $queryBuilder
638  ->select('pid')
639  ->from($tableName)
640  ->where(
641  $queryBuilder->expr()->eq(
642  'uid',
643  $queryBuilder->createNamedParameter($uid, \PDO::PARAM_INT)
644  )
645  )
646  ->execute();
647  if ($row = $result->fetch()) {
648  $storagePage = $row['pid'];
649  $pageIdsToClear[] = $storagePage;
650  }
651  } elseif (isset($GLOBALS['TSFE'])) {
652  // No PID column - we can do a best-effort to clear the cache of the current page if in FE
653  $storagePage = $GLOBALS['TSFE']->id;
654  $pageIdsToClear[] = $storagePage;
655  }
656  if ($storagePage === null) {
657  return;
658  }
659  if (!isset($this->pageTSConfigCache[$storagePage])) {
660  $this->pageTSConfigCache[$storagePage] = BackendUtility::getPagesTSconfig($storagePage);
661  }
662  if (isset($this->pageTSConfigCache[$storagePage]['TCEMAIN.']['clearCacheCmd'])) {
663  $clearCacheCommands = GeneralUtility::trimExplode(',', strtolower($this->pageTSConfigCache[$storagePage]['TCEMAIN.']['clearCacheCmd']), true);
664  $clearCacheCommands = array_unique($clearCacheCommands);
665  foreach ($clearCacheCommands as $clearCacheCommand) {
666  if (MathUtility::canBeInterpretedAsInteger($clearCacheCommand)) {
667  $pageIdsToClear[] = $clearCacheCommand;
668  }
669  }
670  }
671 
672  foreach ($pageIdsToClear as $pageIdToClear) {
673  $this->cacheService->getPageIdStack()->push($pageIdToClear);
674  }
675  }
676 }
removeRow($tableName, array $where, $isRelation=false)
static trimExplode($delim, $string, $removeEmptyValues=false, $limit=0)
addRow($tableName, array $fieldValues, $isRelation=false)
injectCacheService(\TYPO3\CMS\Extbase\Service\CacheService $cacheService)
getMaxValueFromTable($tableName, array $where, $columnName)
doLanguageAndWorkspaceOverlay(Qom\SourceInterface $source, array $rows,\TYPO3\CMS\Extbase\Persistence\Generic\QuerySettingsInterface $querySettings, $workspaceUid=null)
static getPagesTSconfig($id, $rootLine=null, $returnPartArray=false)
if(TYPO3_MODE=== 'BE') $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tsfebeuserauth.php']['frontendEditingController']['default']
static makeInstance($className,...$constructorArguments)
injectEnvironmentService(\TYPO3\CMS\Extbase\Service\EnvironmentService $environmentService)
injectDataMapper(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper $dataMapper)
injectConfigurationManager(ConfigurationManagerInterface $configurationManager)
injectObjectManager(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface $objectManager)
updateRow($tableName, array $fieldValues, $isRelation=false)