SqlContentEntityStorage.php 65 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758
  1. <?php
  2. namespace Drupal\Core\Entity\Sql;
  3. use Drupal\Core\Cache\CacheBackendInterface;
  4. use Drupal\Core\Database\Connection;
  5. use Drupal\Core\Database\Database;
  6. use Drupal\Core\Database\DatabaseExceptionWrapper;
  7. use Drupal\Core\Database\SchemaException;
  8. use Drupal\Core\Entity\ContentEntityInterface;
  9. use Drupal\Core\Entity\ContentEntityStorageBase;
  10. use Drupal\Core\Entity\EntityBundleListenerInterface;
  11. use Drupal\Core\Entity\EntityInterface;
  12. use Drupal\Core\Entity\EntityManagerInterface;
  13. use Drupal\Core\Entity\EntityStorageException;
  14. use Drupal\Core\Entity\EntityTypeInterface;
  15. use Drupal\Core\Entity\Query\QueryInterface;
  16. use Drupal\Core\Entity\Schema\DynamicallyFieldableEntityStorageSchemaInterface;
  17. use Drupal\Core\Field\FieldDefinitionInterface;
  18. use Drupal\Core\Field\FieldStorageDefinitionInterface;
  19. use Drupal\Core\Language\LanguageInterface;
  20. use Drupal\Core\Language\LanguageManagerInterface;
  21. use Symfony\Component\DependencyInjection\ContainerInterface;
  22. /**
  23. * A content entity database storage implementation.
  24. *
  25. * This class can be used as-is by most content entity types. Entity types
  26. * requiring special handling can extend the class.
  27. *
  28. * The class uses \Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema
  29. * internally in order to automatically generate the database schema based on
  30. * the defined base fields. Entity types can override the schema handler to
  31. * customize the generated schema; e.g., to add additional indexes.
  32. *
  33. * @ingroup entity_api
  34. */
  35. class SqlContentEntityStorage extends ContentEntityStorageBase implements SqlEntityStorageInterface, DynamicallyFieldableEntityStorageSchemaInterface, EntityBundleListenerInterface {
  36. /**
  37. * The mapping of field columns to SQL tables.
  38. *
  39. * @var \Drupal\Core\Entity\Sql\TableMappingInterface
  40. */
  41. protected $tableMapping;
  42. /**
  43. * Name of entity's revision database table field, if it supports revisions.
  44. *
  45. * Has the value FALSE if this entity does not use revisions.
  46. *
  47. * @var string
  48. */
  49. protected $revisionKey = FALSE;
  50. /**
  51. * The entity langcode key.
  52. *
  53. * @var string|bool
  54. */
  55. protected $langcodeKey = FALSE;
  56. /**
  57. * The default language entity key.
  58. *
  59. * @var string
  60. */
  61. protected $defaultLangcodeKey = FALSE;
  62. /**
  63. * The base table of the entity.
  64. *
  65. * @var string
  66. */
  67. protected $baseTable;
  68. /**
  69. * The table that stores revisions, if the entity supports revisions.
  70. *
  71. * @var string
  72. */
  73. protected $revisionTable;
  74. /**
  75. * The table that stores properties, if the entity has multilingual support.
  76. *
  77. * @var string
  78. */
  79. protected $dataTable;
  80. /**
  81. * The table that stores revision field data if the entity supports revisions.
  82. *
  83. * @var string
  84. */
  85. protected $revisionDataTable;
  86. /**
  87. * Active database connection.
  88. *
  89. * @var \Drupal\Core\Database\Connection
  90. */
  91. protected $database;
  92. /**
  93. * The entity type's storage schema object.
  94. *
  95. * @var \Drupal\Core\Entity\Schema\EntityStorageSchemaInterface
  96. */
  97. protected $storageSchema;
  98. /**
  99. * The language manager.
  100. *
  101. * @var \Drupal\Core\Language\LanguageManagerInterface
  102. */
  103. protected $languageManager;
  104. /**
  105. * Whether this storage should use the temporary table mapping.
  106. *
  107. * @var bool
  108. */
  109. protected $temporary = FALSE;
  110. /**
  111. * {@inheritdoc}
  112. */
  113. public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
  114. return new static(
  115. $entity_type,
  116. $container->get('database'),
  117. $container->get('entity.manager'),
  118. $container->get('cache.entity'),
  119. $container->get('language_manager')
  120. );
  121. }
  122. /**
  123. * Gets the base field definitions for a content entity type.
  124. *
  125. * @return \Drupal\Core\Field\FieldDefinitionInterface[]
  126. * The array of base field definitions for the entity type, keyed by field
  127. * name.
  128. */
  129. public function getFieldStorageDefinitions() {
  130. return $this->entityManager->getBaseFieldDefinitions($this->entityTypeId);
  131. }
  132. /**
  133. * Constructs a SqlContentEntityStorage object.
  134. *
  135. * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
  136. * The entity type definition.
  137. * @param \Drupal\Core\Database\Connection $database
  138. * The database connection to be used.
  139. * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
  140. * The entity manager.
  141. * @param \Drupal\Core\Cache\CacheBackendInterface $cache
  142. * The cache backend to be used.
  143. * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
  144. * The language manager.
  145. */
  146. public function __construct(EntityTypeInterface $entity_type, Connection $database, EntityManagerInterface $entity_manager, CacheBackendInterface $cache, LanguageManagerInterface $language_manager) {
  147. parent::__construct($entity_type, $entity_manager, $cache);
  148. $this->database = $database;
  149. $this->languageManager = $language_manager;
  150. $this->initTableLayout();
  151. }
  152. /**
  153. * Initializes table name variables.
  154. */
  155. protected function initTableLayout() {
  156. // Reset table field values to ensure changes in the entity type definition
  157. // are correctly reflected in the table layout.
  158. $this->tableMapping = NULL;
  159. $this->revisionKey = NULL;
  160. $this->revisionTable = NULL;
  161. $this->dataTable = NULL;
  162. $this->revisionDataTable = NULL;
  163. // @todo Remove table names from the entity type definition in
  164. // https://www.drupal.org/node/2232465.
  165. $this->baseTable = $this->entityType->getBaseTable() ?: $this->entityTypeId;
  166. $revisionable = $this->entityType->isRevisionable();
  167. if ($revisionable) {
  168. $this->revisionKey = $this->entityType->getKey('revision') ?: 'revision_id';
  169. $this->revisionTable = $this->entityType->getRevisionTable() ?: $this->entityTypeId . '_revision';
  170. }
  171. $translatable = $this->entityType->isTranslatable();
  172. if ($translatable) {
  173. $this->dataTable = $this->entityType->getDataTable() ?: $this->entityTypeId . '_field_data';
  174. $this->langcodeKey = $this->entityType->getKey('langcode');
  175. $this->defaultLangcodeKey = $this->entityType->getKey('default_langcode');
  176. }
  177. if ($revisionable && $translatable) {
  178. $this->revisionDataTable = $this->entityType->getRevisionDataTable() ?: $this->entityTypeId . '_field_revision';
  179. }
  180. }
  181. /**
  182. * Gets the base table name.
  183. *
  184. * @return string
  185. * The table name.
  186. */
  187. public function getBaseTable() {
  188. return $this->baseTable;
  189. }
  190. /**
  191. * Gets the revision table name.
  192. *
  193. * @return string|false
  194. * The table name or FALSE if it is not available.
  195. */
  196. public function getRevisionTable() {
  197. return $this->revisionTable;
  198. }
  199. /**
  200. * Gets the data table name.
  201. *
  202. * @return string|false
  203. * The table name or FALSE if it is not available.
  204. */
  205. public function getDataTable() {
  206. return $this->dataTable;
  207. }
  208. /**
  209. * Gets the revision data table name.
  210. *
  211. * @return string|false
  212. * The table name or FALSE if it is not available.
  213. */
  214. public function getRevisionDataTable() {
  215. return $this->revisionDataTable;
  216. }
  217. /**
  218. * Gets the entity type's storage schema object.
  219. *
  220. * @return \Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema
  221. * The schema object.
  222. */
  223. protected function getStorageSchema() {
  224. if (!isset($this->storageSchema)) {
  225. $class = $this->entityType->getHandlerClass('storage_schema') ?: 'Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema';
  226. $this->storageSchema = new $class($this->entityManager, $this->entityType, $this, $this->database);
  227. }
  228. return $this->storageSchema;
  229. }
  230. /**
  231. * Updates the wrapped entity type definition.
  232. *
  233. * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
  234. * The update entity type.
  235. *
  236. * @internal Only to be used internally by Entity API. Expected to be
  237. * removed by https://www.drupal.org/node/2274017.
  238. */
  239. public function setEntityType(EntityTypeInterface $entity_type) {
  240. if ($this->entityType->id() == $entity_type->id()) {
  241. $this->entityType = $entity_type;
  242. $this->initTableLayout();
  243. }
  244. else {
  245. throw new EntityStorageException("Unsupported entity type {$entity_type->id()}");
  246. }
  247. }
  248. /**
  249. * Sets the wrapped table mapping definition.
  250. *
  251. * @param \Drupal\Core\Entity\Sql\TableMappingInterface $table_mapping
  252. * The table mapping.
  253. *
  254. * @internal Only to be used internally by Entity API. Expected to be removed
  255. * by https://www.drupal.org/node/2554235.
  256. */
  257. public function setTableMapping(TableMappingInterface $table_mapping) {
  258. $this->tableMapping = $table_mapping;
  259. }
  260. /**
  261. * Changes the temporary state of the storage.
  262. *
  263. * @param bool $temporary
  264. * Whether to use a temporary table mapping or not.
  265. *
  266. * @internal Only to be used internally by Entity API.
  267. */
  268. public function setTemporary($temporary) {
  269. $this->temporary = $temporary;
  270. }
  271. /**
  272. * {@inheritdoc}
  273. */
  274. public function getTableMapping(array $storage_definitions = NULL) {
  275. $table_mapping = $this->tableMapping;
  276. // If we are using our internal storage definitions, which is our main use
  277. // case, we can statically cache the computed table mapping. If a new set
  278. // of field storage definitions is passed, for instance when comparing old
  279. // and new storage schema, we compute the table mapping without caching.
  280. // @todo Clean-up this in https://www.drupal.org/node/2274017 so we can
  281. // easily instantiate a new table mapping whenever needed.
  282. if (!isset($this->tableMapping) || $storage_definitions) {
  283. $table_mapping_class = $this->temporary ? TemporaryTableMapping::class : DefaultTableMapping::class;
  284. $definitions = $storage_definitions ?: $this->entityManager->getFieldStorageDefinitions($this->entityTypeId);
  285. /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping|\Drupal\Core\Entity\Sql\TemporaryTableMapping $table_mapping */
  286. $table_mapping = new $table_mapping_class($this->entityType, $definitions);
  287. $shared_table_definitions = array_filter($definitions, function (FieldStorageDefinitionInterface $definition) use ($table_mapping) {
  288. return $table_mapping->allowsSharedTableStorage($definition);
  289. });
  290. $key_fields = array_values(array_filter([$this->idKey, $this->revisionKey, $this->bundleKey, $this->uuidKey, $this->langcodeKey]));
  291. $all_fields = array_keys($shared_table_definitions);
  292. $revisionable_fields = array_keys(array_filter($shared_table_definitions, function (FieldStorageDefinitionInterface $definition) {
  293. return $definition->isRevisionable();
  294. }));
  295. // Make sure the key fields come first in the list of fields.
  296. $all_fields = array_merge($key_fields, array_diff($all_fields, $key_fields));
  297. // If the entity is revisionable, gather the fields that need to be put
  298. // in the revision table.
  299. $revisionable = $this->entityType->isRevisionable();
  300. $revision_metadata_fields = $revisionable ? array_values($this->entityType->getRevisionMetadataKeys()) : [];
  301. $translatable = $this->entityType->isTranslatable();
  302. if (!$revisionable && !$translatable) {
  303. // The base layout stores all the base field values in the base table.
  304. $table_mapping->setFieldNames($this->baseTable, $all_fields);
  305. }
  306. elseif ($revisionable && !$translatable) {
  307. // The revisionable layout stores all the base field values in the base
  308. // table, except for revision metadata fields. Revisionable fields
  309. // denormalized in the base table but also stored in the revision table
  310. // together with the entity ID and the revision ID as identifiers.
  311. $table_mapping->setFieldNames($this->baseTable, array_diff($all_fields, $revision_metadata_fields));
  312. $revision_key_fields = [$this->idKey, $this->revisionKey];
  313. $table_mapping->setFieldNames($this->revisionTable, array_merge($revision_key_fields, $revisionable_fields));
  314. }
  315. elseif (!$revisionable && $translatable) {
  316. // Multilingual layouts store key field values in the base table. The
  317. // other base field values are stored in the data table, no matter
  318. // whether they are translatable or not. The data table holds also a
  319. // denormalized copy of the bundle field value to allow for more
  320. // performant queries. This means that only the UUID is not stored on
  321. // the data table.
  322. $table_mapping
  323. ->setFieldNames($this->baseTable, $key_fields)
  324. ->setFieldNames($this->dataTable, array_values(array_diff($all_fields, [$this->uuidKey])));
  325. }
  326. elseif ($revisionable && $translatable) {
  327. // The revisionable multilingual layout stores key field values in the
  328. // base table, except for language, which is stored in the revision
  329. // table along with revision metadata. The revision data table holds
  330. // data field values for all the revisionable fields and the data table
  331. // holds the data field values for all non-revisionable fields. The data
  332. // field values of revisionable fields are denormalized in the data
  333. // table, as well.
  334. $table_mapping->setFieldNames($this->baseTable, array_values($key_fields));
  335. // Like in the multilingual, non-revisionable case the UUID is not
  336. // in the data table. Additionally, do not store revision metadata
  337. // fields in the data table.
  338. $data_fields = array_values(array_diff($all_fields, [$this->uuidKey], $revision_metadata_fields));
  339. $table_mapping->setFieldNames($this->dataTable, $data_fields);
  340. $revision_base_fields = array_merge([$this->idKey, $this->revisionKey, $this->langcodeKey], $revision_metadata_fields);
  341. $table_mapping->setFieldNames($this->revisionTable, $revision_base_fields);
  342. $revision_data_key_fields = [$this->idKey, $this->revisionKey, $this->langcodeKey];
  343. $revision_data_fields = array_diff($revisionable_fields, $revision_metadata_fields, [$this->langcodeKey]);
  344. $table_mapping->setFieldNames($this->revisionDataTable, array_merge($revision_data_key_fields, $revision_data_fields));
  345. }
  346. // Add dedicated tables.
  347. $dedicated_table_definitions = array_filter($definitions, function (FieldStorageDefinitionInterface $definition) use ($table_mapping) {
  348. return $table_mapping->requiresDedicatedTableStorage($definition);
  349. });
  350. $extra_columns = [
  351. 'bundle',
  352. 'deleted',
  353. 'entity_id',
  354. 'revision_id',
  355. 'langcode',
  356. 'delta',
  357. ];
  358. foreach ($dedicated_table_definitions as $field_name => $definition) {
  359. $tables = [$table_mapping->getDedicatedDataTableName($definition)];
  360. if ($revisionable && $definition->isRevisionable()) {
  361. $tables[] = $table_mapping->getDedicatedRevisionTableName($definition);
  362. }
  363. foreach ($tables as $table_name) {
  364. $table_mapping->setFieldNames($table_name, [$field_name]);
  365. $table_mapping->setExtraColumns($table_name, $extra_columns);
  366. }
  367. }
  368. // Cache the computed table mapping only if we are using our internal
  369. // storage definitions.
  370. if (!$storage_definitions) {
  371. $this->tableMapping = $table_mapping;
  372. }
  373. }
  374. return $table_mapping;
  375. }
  376. /**
  377. * {@inheritdoc}
  378. */
  379. protected function doLoadMultiple(array $ids = NULL) {
  380. // Attempt to load entities from the persistent cache. This will remove IDs
  381. // that were loaded from $ids.
  382. $entities_from_cache = $this->getFromPersistentCache($ids);
  383. // Load any remaining entities from the database.
  384. if ($entities_from_storage = $this->getFromStorage($ids)) {
  385. $this->invokeStorageLoadHook($entities_from_storage);
  386. $this->setPersistentCache($entities_from_storage);
  387. }
  388. return $entities_from_cache + $entities_from_storage;
  389. }
  390. /**
  391. * Gets entities from the storage.
  392. *
  393. * @param array|null $ids
  394. * If not empty, return entities that match these IDs. Return all entities
  395. * when NULL.
  396. *
  397. * @return \Drupal\Core\Entity\ContentEntityInterface[]
  398. * Array of entities from the storage.
  399. */
  400. protected function getFromStorage(array $ids = NULL) {
  401. $entities = [];
  402. if (!empty($ids)) {
  403. // Sanitize IDs. Before feeding ID array into buildQuery, check whether
  404. // it is empty as this would load all entities.
  405. $ids = $this->cleanIds($ids);
  406. }
  407. if ($ids === NULL || $ids) {
  408. // Build and execute the query.
  409. $query_result = $this->buildQuery($ids)->execute();
  410. $records = $query_result->fetchAllAssoc($this->idKey);
  411. // Map the loaded records into entity objects and according fields.
  412. if ($records) {
  413. $entities = $this->mapFromStorageRecords($records);
  414. }
  415. }
  416. return $entities;
  417. }
  418. /**
  419. * Maps from storage records to entity objects, and attaches fields.
  420. *
  421. * @param array $records
  422. * Associative array of query results, keyed on the entity ID or revision
  423. * ID.
  424. * @param bool $load_from_revision
  425. * (optional) Flag to indicate whether revisions should be loaded or not.
  426. * Defaults to FALSE.
  427. *
  428. * @return array
  429. * An array of entity objects implementing the EntityInterface.
  430. */
  431. protected function mapFromStorageRecords(array $records, $load_from_revision = FALSE) {
  432. if (!$records) {
  433. return [];
  434. }
  435. $values = [];
  436. foreach ($records as $id => $record) {
  437. $values[$id] = [];
  438. // Skip the item delta and item value levels (if possible) but let the
  439. // field assign the value as suiting. This avoids unnecessary array
  440. // hierarchies and saves memory here.
  441. foreach ($record as $name => $value) {
  442. // Handle columns named [field_name]__[column_name] (e.g for field types
  443. // that store several properties).
  444. if ($field_name = strstr($name, '__', TRUE)) {
  445. $property_name = substr($name, strpos($name, '__') + 2);
  446. $values[$id][$field_name][LanguageInterface::LANGCODE_DEFAULT][$property_name] = $value;
  447. }
  448. else {
  449. // Handle columns named directly after the field (e.g if the field
  450. // type only stores one property).
  451. $values[$id][$name][LanguageInterface::LANGCODE_DEFAULT] = $value;
  452. }
  453. }
  454. }
  455. // Initialize translations array.
  456. $translations = array_fill_keys(array_keys($values), []);
  457. // Load values from shared and dedicated tables.
  458. $this->loadFromSharedTables($values, $translations, $load_from_revision);
  459. $this->loadFromDedicatedTables($values, $load_from_revision);
  460. $entities = [];
  461. foreach ($values as $id => $entity_values) {
  462. $bundle = $this->bundleKey ? $entity_values[$this->bundleKey][LanguageInterface::LANGCODE_DEFAULT] : FALSE;
  463. // Turn the record into an entity class.
  464. $entities[$id] = new $this->entityClass($entity_values, $this->entityTypeId, $bundle, array_keys($translations[$id]));
  465. }
  466. return $entities;
  467. }
  468. /**
  469. * Loads values for fields stored in the shared data tables.
  470. *
  471. * @param array &$values
  472. * Associative array of entities values, keyed on the entity ID or the
  473. * revision ID.
  474. * @param array &$translations
  475. * List of translations, keyed on the entity ID.
  476. * @param bool $load_from_revision
  477. * Flag to indicate whether revisions should be loaded or not.
  478. */
  479. protected function loadFromSharedTables(array &$values, array &$translations, $load_from_revision) {
  480. $record_key = !$load_from_revision ? $this->idKey : $this->revisionKey;
  481. if ($this->dataTable) {
  482. // If a revision table is available, we need all the properties of the
  483. // latest revision. Otherwise we fall back to the data table.
  484. $table = $this->revisionDataTable ?: $this->dataTable;
  485. $alias = $this->revisionDataTable ? 'revision' : 'data';
  486. $query = $this->database->select($table, $alias, ['fetch' => \PDO::FETCH_ASSOC])
  487. ->fields($alias)
  488. ->condition($alias . '.' . $record_key, array_keys($values), 'IN')
  489. ->orderBy($alias . '.' . $record_key);
  490. $table_mapping = $this->getTableMapping();
  491. if ($this->revisionDataTable) {
  492. // Find revisioned fields that are not entity keys. Exclude the langcode
  493. // key as the base table holds only the default language.
  494. $base_fields = array_diff($table_mapping->getFieldNames($this->baseTable), [$this->langcodeKey]);
  495. $revisioned_fields = array_diff($table_mapping->getFieldNames($this->revisionDataTable), $base_fields);
  496. // Find fields that are not revisioned or entity keys. Data fields have
  497. // the same value regardless of entity revision.
  498. $data_fields = array_diff($table_mapping->getFieldNames($this->dataTable), $revisioned_fields, $base_fields);
  499. // If there are no data fields then only revisioned fields are needed
  500. // else both data fields and revisioned fields are needed to map the
  501. // entity values.
  502. $all_fields = $revisioned_fields;
  503. if ($data_fields) {
  504. $all_fields = array_merge($revisioned_fields, $data_fields);
  505. $query->leftJoin($this->dataTable, 'data', "(revision.$this->idKey = data.$this->idKey and revision.$this->langcodeKey = data.$this->langcodeKey)");
  506. $column_names = [];
  507. // Some fields can have more then one columns in the data table so
  508. // column names are needed.
  509. foreach ($data_fields as $data_field) {
  510. // \Drupal\Core\Entity\Sql\TableMappingInterface:: getColumNames()
  511. // returns an array keyed by property names so remove the keys
  512. // before array_merge() to avoid losing data with fields having the
  513. // same columns i.e. value.
  514. $column_names = array_merge($column_names, array_values($table_mapping->getColumnNames($data_field)));
  515. }
  516. $query->fields('data', $column_names);
  517. }
  518. // Get the revision IDs.
  519. $revision_ids = [];
  520. foreach ($values as $entity_values) {
  521. $revision_ids[] = $entity_values[$this->revisionKey][LanguageInterface::LANGCODE_DEFAULT];
  522. }
  523. $query->condition('revision.' . $this->revisionKey, $revision_ids, 'IN');
  524. }
  525. else {
  526. $all_fields = $table_mapping->getFieldNames($this->dataTable);
  527. }
  528. $result = $query->execute();
  529. foreach ($result as $row) {
  530. $id = $row[$record_key];
  531. // Field values in default language are stored with
  532. // LanguageInterface::LANGCODE_DEFAULT as key.
  533. $langcode = empty($row[$this->defaultLangcodeKey]) ? $row[$this->langcodeKey] : LanguageInterface::LANGCODE_DEFAULT;
  534. $translations[$id][$langcode] = TRUE;
  535. foreach ($all_fields as $field_name) {
  536. $columns = $table_mapping->getColumnNames($field_name);
  537. // Do not key single-column fields by property name.
  538. if (count($columns) == 1) {
  539. $values[$id][$field_name][$langcode] = $row[reset($columns)];
  540. }
  541. else {
  542. foreach ($columns as $property_name => $column_name) {
  543. $values[$id][$field_name][$langcode][$property_name] = $row[$column_name];
  544. }
  545. }
  546. }
  547. }
  548. }
  549. }
  550. /**
  551. * {@inheritdoc}
  552. */
  553. protected function doLoadRevisionFieldItems($revision_id) {
  554. @trigger_error('"\Drupal\Core\Entity\ContentEntityStorageBase::doLoadRevisionFieldItems()" is deprecated in Drupal 8.5.x and will be removed before Drupal 9.0.0. "\Drupal\Core\Entity\ContentEntityStorageBase::doLoadMultipleRevisionsFieldItems()" should be implemented instead. See https://www.drupal.org/node/2924915.', E_USER_DEPRECATED);
  555. $revisions = $this->doLoadMultipleRevisionsFieldItems([$revision_id]);
  556. return !empty($revisions) ? reset($revisions) : NULL;
  557. }
  558. /**
  559. * {@inheritdoc}
  560. */
  561. protected function doLoadMultipleRevisionsFieldItems($revision_ids) {
  562. $revisions = [];
  563. // Sanitize IDs. Before feeding ID array into buildQuery, check whether
  564. // it is empty as this would load all entity revisions.
  565. $revision_ids = $this->cleanIds($revision_ids, 'revision');
  566. if (!empty($revision_ids)) {
  567. // Build and execute the query.
  568. $query_result = $this->buildQuery(NULL, $revision_ids)->execute();
  569. $records = $query_result->fetchAllAssoc($this->revisionKey);
  570. // Map the loaded records into entity objects and according fields.
  571. if ($records) {
  572. $revisions = $this->mapFromStorageRecords($records, TRUE);
  573. }
  574. }
  575. return $revisions;
  576. }
  577. /**
  578. * {@inheritdoc}
  579. */
  580. protected function doDeleteRevisionFieldItems(ContentEntityInterface $revision) {
  581. $this->database->delete($this->revisionTable)
  582. ->condition($this->revisionKey, $revision->getRevisionId())
  583. ->execute();
  584. if ($this->revisionDataTable) {
  585. $this->database->delete($this->revisionDataTable)
  586. ->condition($this->revisionKey, $revision->getRevisionId())
  587. ->execute();
  588. }
  589. $this->deleteRevisionFromDedicatedTables($revision);
  590. }
  591. /**
  592. * {@inheritdoc}
  593. */
  594. protected function buildPropertyQuery(QueryInterface $entity_query, array $values) {
  595. if ($this->dataTable) {
  596. // @todo We should not be using a condition to specify whether conditions
  597. // apply to the default language. See
  598. // https://www.drupal.org/node/1866330.
  599. // Default to the original entity language if not explicitly specified
  600. // otherwise.
  601. if (!array_key_exists($this->defaultLangcodeKey, $values)) {
  602. $values[$this->defaultLangcodeKey] = 1;
  603. }
  604. // If the 'default_langcode' flag is explicitly not set, we do not care
  605. // whether the queried values are in the original entity language or not.
  606. elseif ($values[$this->defaultLangcodeKey] === NULL) {
  607. unset($values[$this->defaultLangcodeKey]);
  608. }
  609. }
  610. parent::buildPropertyQuery($entity_query, $values);
  611. }
  612. /**
  613. * Builds the query to load the entity.
  614. *
  615. * This has full revision support. For entities requiring special queries,
  616. * the class can be extended, and the default query can be constructed by
  617. * calling parent::buildQuery(). This is usually necessary when the object
  618. * being loaded needs to be augmented with additional data from another
  619. * table, such as loading node type into comments or vocabulary machine name
  620. * into terms, however it can also support $conditions on different tables.
  621. * See Drupal\comment\CommentStorage::buildQuery() for an example.
  622. *
  623. * @param array|null $ids
  624. * An array of entity IDs, or NULL to load all entities.
  625. * @param array|bool $revision_ids
  626. * The IDs of the revisions to load, or FALSE if this query is asking for
  627. * the default revisions. Defaults to FALSE.
  628. *
  629. * @return \Drupal\Core\Database\Query\Select
  630. * A SelectQuery object for loading the entity.
  631. */
  632. protected function buildQuery($ids, $revision_ids = FALSE) {
  633. $query = $this->database->select($this->entityType->getBaseTable(), 'base');
  634. $query->addTag($this->entityTypeId . '_load_multiple');
  635. if ($revision_ids) {
  636. if (!is_array($revision_ids)) {
  637. @trigger_error('Passing a single revision ID to "\Drupal\Core\Entity\Sql\SqlContentEntityStorage::buildQuery()" is deprecated in Drupal 8.5.x and will be removed before Drupal 9.0.0. An array of revision IDs should be given instead. See https://www.drupal.org/node/2924915.', E_USER_DEPRECATED);
  638. }
  639. $query->join($this->revisionTable, 'revision', "revision.{$this->idKey} = base.{$this->idKey} AND revision.{$this->revisionKey} IN (:revisionIds[])", [':revisionIds[]' => (array) $revision_ids]);
  640. }
  641. elseif ($this->revisionTable) {
  642. $query->join($this->revisionTable, 'revision', "revision.{$this->revisionKey} = base.{$this->revisionKey}");
  643. }
  644. // Add fields from the {entity} table.
  645. $table_mapping = $this->getTableMapping();
  646. $entity_fields = $table_mapping->getAllColumns($this->baseTable);
  647. if ($this->revisionTable) {
  648. // Add all fields from the {entity_revision} table.
  649. $entity_revision_fields = $table_mapping->getAllColumns($this->revisionTable);
  650. $entity_revision_fields = array_combine($entity_revision_fields, $entity_revision_fields);
  651. // The ID field is provided by entity, so remove it.
  652. unset($entity_revision_fields[$this->idKey]);
  653. // Remove all fields from the base table that are also fields by the same
  654. // name in the revision table.
  655. $entity_field_keys = array_flip($entity_fields);
  656. foreach ($entity_revision_fields as $name) {
  657. if (isset($entity_field_keys[$name])) {
  658. unset($entity_fields[$entity_field_keys[$name]]);
  659. }
  660. }
  661. $query->fields('revision', $entity_revision_fields);
  662. // Compare revision ID of the base and revision table, if equal then this
  663. // is the default revision.
  664. $query->addExpression('CASE base.' . $this->revisionKey . ' WHEN revision.' . $this->revisionKey . ' THEN 1 ELSE 0 END', 'isDefaultRevision');
  665. }
  666. $query->fields('base', $entity_fields);
  667. if ($ids) {
  668. $query->condition("base.{$this->idKey}", $ids, 'IN');
  669. }
  670. return $query;
  671. }
  672. /**
  673. * {@inheritdoc}
  674. */
  675. public function delete(array $entities) {
  676. if (!$entities) {
  677. // If no IDs or invalid IDs were passed, do nothing.
  678. return;
  679. }
  680. $transaction = $this->database->startTransaction();
  681. try {
  682. parent::delete($entities);
  683. // Ignore replica server temporarily.
  684. db_ignore_replica();
  685. }
  686. catch (\Exception $e) {
  687. $transaction->rollBack();
  688. watchdog_exception($this->entityTypeId, $e);
  689. throw new EntityStorageException($e->getMessage(), $e->getCode(), $e);
  690. }
  691. }
  692. /**
  693. * {@inheritdoc}
  694. */
  695. protected function doDeleteFieldItems($entities) {
  696. $ids = array_keys($entities);
  697. $this->database->delete($this->entityType->getBaseTable())
  698. ->condition($this->idKey, $ids, 'IN')
  699. ->execute();
  700. if ($this->revisionTable) {
  701. $this->database->delete($this->revisionTable)
  702. ->condition($this->idKey, $ids, 'IN')
  703. ->execute();
  704. }
  705. if ($this->dataTable) {
  706. $this->database->delete($this->dataTable)
  707. ->condition($this->idKey, $ids, 'IN')
  708. ->execute();
  709. }
  710. if ($this->revisionDataTable) {
  711. $this->database->delete($this->revisionDataTable)
  712. ->condition($this->idKey, $ids, 'IN')
  713. ->execute();
  714. }
  715. foreach ($entities as $entity) {
  716. $this->deleteFromDedicatedTables($entity);
  717. }
  718. }
  719. /**
  720. * {@inheritdoc}
  721. */
  722. public function save(EntityInterface $entity) {
  723. $transaction = $this->database->startTransaction();
  724. try {
  725. $return = parent::save($entity);
  726. // Ignore replica server temporarily.
  727. db_ignore_replica();
  728. return $return;
  729. }
  730. catch (\Exception $e) {
  731. $transaction->rollBack();
  732. watchdog_exception($this->entityTypeId, $e);
  733. throw new EntityStorageException($e->getMessage(), $e->getCode(), $e);
  734. }
  735. }
  736. /**
  737. * {@inheritdoc}
  738. */
  739. protected function doSaveFieldItems(ContentEntityInterface $entity, array $names = []) {
  740. $full_save = empty($names);
  741. $update = !$full_save || !$entity->isNew();
  742. if ($full_save) {
  743. $shared_table_fields = TRUE;
  744. $dedicated_table_fields = TRUE;
  745. }
  746. else {
  747. $table_mapping = $this->getTableMapping();
  748. $storage_definitions = $this->entityManager->getFieldStorageDefinitions($this->entityTypeId);
  749. $shared_table_fields = FALSE;
  750. $dedicated_table_fields = [];
  751. // Collect the name of fields to be written in dedicated tables and check
  752. // whether shared table records need to be updated.
  753. foreach ($names as $name) {
  754. $storage_definition = $storage_definitions[$name];
  755. if ($table_mapping->allowsSharedTableStorage($storage_definition)) {
  756. $shared_table_fields = TRUE;
  757. }
  758. elseif ($table_mapping->requiresDedicatedTableStorage($storage_definition)) {
  759. $dedicated_table_fields[] = $name;
  760. }
  761. }
  762. }
  763. // Update shared table records if necessary.
  764. if ($shared_table_fields) {
  765. $record = $this->mapToStorageRecord($entity->getUntranslated(), $this->baseTable);
  766. // Create the storage record to be saved.
  767. if ($update) {
  768. $default_revision = $entity->isDefaultRevision();
  769. if ($default_revision) {
  770. $this->database
  771. ->update($this->baseTable)
  772. ->fields((array) $record)
  773. ->condition($this->idKey, $record->{$this->idKey})
  774. ->execute();
  775. }
  776. if ($this->revisionTable) {
  777. if ($full_save) {
  778. $entity->{$this->revisionKey} = $this->saveRevision($entity);
  779. }
  780. else {
  781. $record = $this->mapToStorageRecord($entity->getUntranslated(), $this->revisionTable);
  782. $entity->preSaveRevision($this, $record);
  783. $this->database
  784. ->update($this->revisionTable)
  785. ->fields((array) $record)
  786. ->condition($this->revisionKey, $record->{$this->revisionKey})
  787. ->execute();
  788. }
  789. }
  790. if ($default_revision && $this->dataTable) {
  791. $this->saveToSharedTables($entity);
  792. }
  793. if ($this->revisionDataTable) {
  794. $new_revision = $full_save && $entity->isNewRevision();
  795. $this->saveToSharedTables($entity, $this->revisionDataTable, $new_revision);
  796. }
  797. }
  798. else {
  799. $insert_id = $this->database
  800. ->insert($this->baseTable, ['return' => Database::RETURN_INSERT_ID])
  801. ->fields((array) $record)
  802. ->execute();
  803. // Even if this is a new entity the ID key might have been set, in which
  804. // case we should not override the provided ID. An ID key that is not set
  805. // to any value is interpreted as NULL (or DEFAULT) and thus overridden.
  806. if (!isset($record->{$this->idKey})) {
  807. $record->{$this->idKey} = $insert_id;
  808. }
  809. $entity->{$this->idKey} = (string) $record->{$this->idKey};
  810. if ($this->revisionTable) {
  811. $record->{$this->revisionKey} = $this->saveRevision($entity);
  812. }
  813. if ($this->dataTable) {
  814. $this->saveToSharedTables($entity);
  815. }
  816. if ($this->revisionDataTable) {
  817. $this->saveToSharedTables($entity, $this->revisionDataTable);
  818. }
  819. }
  820. }
  821. // Update dedicated table records if necessary.
  822. if ($dedicated_table_fields) {
  823. $names = is_array($dedicated_table_fields) ? $dedicated_table_fields : [];
  824. $this->saveToDedicatedTables($entity, $update, $names);
  825. }
  826. }
  827. /**
  828. * {@inheritdoc}
  829. */
  830. protected function has($id, EntityInterface $entity) {
  831. return !$entity->isNew();
  832. }
  833. /**
  834. * Saves fields that use the shared tables.
  835. *
  836. * @param \Drupal\Core\Entity\ContentEntityInterface $entity
  837. * The entity object.
  838. * @param string $table_name
  839. * (optional) The table name to save to. Defaults to the data table.
  840. * @param bool $new_revision
  841. * (optional) Whether we are dealing with a new revision. By default fetches
  842. * the information from the entity object.
  843. */
  844. protected function saveToSharedTables(ContentEntityInterface $entity, $table_name = NULL, $new_revision = NULL) {
  845. if (!isset($table_name)) {
  846. $table_name = $this->dataTable;
  847. }
  848. if (!isset($new_revision)) {
  849. $new_revision = $entity->isNewRevision();
  850. }
  851. $revision = $table_name != $this->dataTable;
  852. if (!$revision || !$new_revision) {
  853. $key = $revision ? $this->revisionKey : $this->idKey;
  854. $value = $revision ? $entity->getRevisionId() : $entity->id();
  855. // Delete and insert to handle removed values.
  856. $this->database->delete($table_name)
  857. ->condition($key, $value)
  858. ->execute();
  859. }
  860. $query = $this->database->insert($table_name);
  861. foreach ($entity->getTranslationLanguages() as $langcode => $language) {
  862. $translation = $entity->getTranslation($langcode);
  863. $record = $this->mapToDataStorageRecord($translation, $table_name);
  864. $values = (array) $record;
  865. $query
  866. ->fields(array_keys($values))
  867. ->values($values);
  868. }
  869. $query->execute();
  870. }
  871. /**
  872. * Maps from an entity object to the storage record.
  873. *
  874. * @param \Drupal\Core\Entity\ContentEntityInterface $entity
  875. * The entity object.
  876. * @param string $table_name
  877. * (optional) The table name to map records to. Defaults to the base table.
  878. *
  879. * @return \stdClass
  880. * The record to store.
  881. */
  882. protected function mapToStorageRecord(ContentEntityInterface $entity, $table_name = NULL) {
  883. if (!isset($table_name)) {
  884. $table_name = $this->baseTable;
  885. }
  886. $record = new \stdClass();
  887. $table_mapping = $this->getTableMapping();
  888. foreach ($table_mapping->getFieldNames($table_name) as $field_name) {
  889. if (empty($this->getFieldStorageDefinitions()[$field_name])) {
  890. throw new EntityStorageException("Table mapping contains invalid field $field_name.");
  891. }
  892. $definition = $this->getFieldStorageDefinitions()[$field_name];
  893. $columns = $table_mapping->getColumnNames($field_name);
  894. foreach ($columns as $column_name => $schema_name) {
  895. // If there is no main property and only a single column, get all
  896. // properties from the first field item and assume that they will be
  897. // stored serialized.
  898. // @todo Give field types more control over this behavior in
  899. // https://www.drupal.org/node/2232427.
  900. if (!$definition->getMainPropertyName() && count($columns) == 1) {
  901. $value = ($item = $entity->$field_name->first()) ? $item->getValue() : [];
  902. }
  903. else {
  904. $value = isset($entity->$field_name->$column_name) ? $entity->$field_name->$column_name : NULL;
  905. }
  906. if (!empty($definition->getSchema()['columns'][$column_name]['serialize'])) {
  907. $value = serialize($value);
  908. }
  909. // Do not set serial fields if we do not have a value. This supports all
  910. // SQL database drivers.
  911. // @see https://www.drupal.org/node/2279395
  912. $value = drupal_schema_get_field_value($definition->getSchema()['columns'][$column_name], $value);
  913. if (!(empty($value) && $this->isColumnSerial($table_name, $schema_name))) {
  914. $record->$schema_name = $value;
  915. }
  916. }
  917. }
  918. return $record;
  919. }
  920. /**
  921. * Checks whether a field column should be treated as serial.
  922. *
  923. * @param $table_name
  924. * The name of the table the field column belongs to.
  925. * @param $schema_name
  926. * The schema name of the field column.
  927. *
  928. * @return bool
  929. * TRUE if the column is serial, FALSE otherwise.
  930. *
  931. * @see \Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema::processBaseTable()
  932. * @see \Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema::processRevisionTable()
  933. */
  934. protected function isColumnSerial($table_name, $schema_name) {
  935. $result = FALSE;
  936. switch ($table_name) {
  937. case $this->baseTable:
  938. $result = $schema_name == $this->idKey;
  939. break;
  940. case $this->revisionTable:
  941. $result = $schema_name == $this->revisionKey;
  942. break;
  943. }
  944. return $result;
  945. }
  946. /**
  947. * Maps from an entity object to the storage record of the field data.
  948. *
  949. * @param \Drupal\Core\Entity\EntityInterface $entity
  950. * The entity object.
  951. * @param string $table_name
  952. * (optional) The table name to map records to. Defaults to the data table.
  953. *
  954. * @return \stdClass
  955. * The record to store.
  956. */
  957. protected function mapToDataStorageRecord(EntityInterface $entity, $table_name = NULL) {
  958. if (!isset($table_name)) {
  959. $table_name = $this->dataTable;
  960. }
  961. $record = $this->mapToStorageRecord($entity, $table_name);
  962. return $record;
  963. }
  964. /**
  965. * Saves an entity revision.
  966. *
  967. * @param \Drupal\Core\Entity\ContentEntityInterface $entity
  968. * The entity object.
  969. *
  970. * @return int
  971. * The revision id.
  972. */
  973. protected function saveRevision(ContentEntityInterface $entity) {
  974. $record = $this->mapToStorageRecord($entity->getUntranslated(), $this->revisionTable);
  975. $entity->preSaveRevision($this, $record);
  976. if ($entity->isNewRevision()) {
  977. $insert_id = $this->database
  978. ->insert($this->revisionTable, ['return' => Database::RETURN_INSERT_ID])
  979. ->fields((array) $record)
  980. ->execute();
  981. // Even if this is a new revision, the revision ID key might have been
  982. // set in which case we should not override the provided revision ID.
  983. if (!isset($record->{$this->revisionKey})) {
  984. $record->{$this->revisionKey} = $insert_id;
  985. }
  986. if ($entity->isDefaultRevision()) {
  987. $this->database->update($this->entityType->getBaseTable())
  988. ->fields([$this->revisionKey => $record->{$this->revisionKey}])
  989. ->condition($this->idKey, $record->{$this->idKey})
  990. ->execute();
  991. }
  992. }
  993. else {
  994. $this->database
  995. ->update($this->revisionTable)
  996. ->fields((array) $record)
  997. ->condition($this->revisionKey, $record->{$this->revisionKey})
  998. ->execute();
  999. }
  1000. // Make sure to update the new revision key for the entity.
  1001. $entity->{$this->revisionKey}->value = $record->{$this->revisionKey};
  1002. return $record->{$this->revisionKey};
  1003. }
  1004. /**
  1005. * {@inheritdoc}
  1006. */
  1007. protected function getQueryServiceName() {
  1008. return 'entity.query.sql';
  1009. }
  1010. /**
  1011. * Loads values of fields stored in dedicated tables for a group of entities.
  1012. *
  1013. * @param array &$values
  1014. * An array of values keyed by entity ID.
  1015. * @param bool $load_from_revision
  1016. * Flag to indicate whether revisions should be loaded or not.
  1017. */
  1018. protected function loadFromDedicatedTables(array &$values, $load_from_revision) {
  1019. if (empty($values)) {
  1020. return;
  1021. }
  1022. // Collect entities ids, bundles and languages.
  1023. $bundles = [];
  1024. $ids = [];
  1025. $default_langcodes = [];
  1026. foreach ($values as $key => $entity_values) {
  1027. $bundles[$this->bundleKey ? $entity_values[$this->bundleKey][LanguageInterface::LANGCODE_DEFAULT] : $this->entityTypeId] = TRUE;
  1028. $ids[] = !$load_from_revision ? $key : $entity_values[$this->revisionKey][LanguageInterface::LANGCODE_DEFAULT];
  1029. if ($this->langcodeKey && isset($entity_values[$this->langcodeKey][LanguageInterface::LANGCODE_DEFAULT])) {
  1030. $default_langcodes[$key] = $entity_values[$this->langcodeKey][LanguageInterface::LANGCODE_DEFAULT];
  1031. }
  1032. }
  1033. // Collect impacted fields.
  1034. $storage_definitions = [];
  1035. $definitions = [];
  1036. $table_mapping = $this->getTableMapping();
  1037. foreach ($bundles as $bundle => $v) {
  1038. $definitions[$bundle] = $this->entityManager->getFieldDefinitions($this->entityTypeId, $bundle);
  1039. foreach ($definitions[$bundle] as $field_name => $field_definition) {
  1040. $storage_definition = $field_definition->getFieldStorageDefinition();
  1041. if ($table_mapping->requiresDedicatedTableStorage($storage_definition)) {
  1042. $storage_definitions[$field_name] = $storage_definition;
  1043. }
  1044. }
  1045. }
  1046. // Load field data.
  1047. $langcodes = array_keys($this->languageManager->getLanguages(LanguageInterface::STATE_ALL));
  1048. foreach ($storage_definitions as $field_name => $storage_definition) {
  1049. $table = !$load_from_revision ? $table_mapping->getDedicatedDataTableName($storage_definition) : $table_mapping->getDedicatedRevisionTableName($storage_definition);
  1050. // Ensure that only values having valid languages are retrieved. Since we
  1051. // are loading values for multiple entities, we cannot limit the query to
  1052. // the available translations.
  1053. $results = $this->database->select($table, 't')
  1054. ->fields('t')
  1055. ->condition(!$load_from_revision ? 'entity_id' : 'revision_id', $ids, 'IN')
  1056. ->condition('deleted', 0)
  1057. ->condition('langcode', $langcodes, 'IN')
  1058. ->orderBy('delta')
  1059. ->execute();
  1060. foreach ($results as $row) {
  1061. $bundle = $row->bundle;
  1062. $value_key = !$load_from_revision ? $row->entity_id : $row->revision_id;
  1063. // Field values in default language are stored with
  1064. // LanguageInterface::LANGCODE_DEFAULT as key.
  1065. $langcode = LanguageInterface::LANGCODE_DEFAULT;
  1066. if ($this->langcodeKey && isset($default_langcodes[$value_key]) && $row->langcode != $default_langcodes[$value_key]) {
  1067. $langcode = $row->langcode;
  1068. }
  1069. if (!isset($values[$value_key][$field_name][$langcode])) {
  1070. $values[$value_key][$field_name][$langcode] = [];
  1071. }
  1072. // Ensure that records for non-translatable fields having invalid
  1073. // languages are skipped.
  1074. if ($langcode == LanguageInterface::LANGCODE_DEFAULT || $definitions[$bundle][$field_name]->isTranslatable()) {
  1075. if ($storage_definition->getCardinality() == FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED || count($values[$value_key][$field_name][$langcode]) < $storage_definition->getCardinality()) {
  1076. $item = [];
  1077. // For each column declared by the field, populate the item from the
  1078. // prefixed database column.
  1079. foreach ($storage_definition->getColumns() as $column => $attributes) {
  1080. $column_name = $table_mapping->getFieldColumnName($storage_definition, $column);
  1081. // Unserialize the value if specified in the column schema.
  1082. $item[$column] = (!empty($attributes['serialize'])) ? unserialize($row->$column_name) : $row->$column_name;
  1083. }
  1084. // Add the item to the field values for the entity.
  1085. $values[$value_key][$field_name][$langcode][] = $item;
  1086. }
  1087. }
  1088. }
  1089. }
  1090. }
  1091. /**
  1092. * Saves values of fields that use dedicated tables.
  1093. *
  1094. * @param \Drupal\Core\Entity\ContentEntityInterface $entity
  1095. * The entity.
  1096. * @param bool $update
  1097. * TRUE if the entity is being updated, FALSE if it is being inserted.
  1098. * @param string[] $names
  1099. * (optional) The names of the fields to be stored. Defaults to all the
  1100. * available fields.
  1101. */
  1102. protected function saveToDedicatedTables(ContentEntityInterface $entity, $update = TRUE, $names = []) {
  1103. $vid = $entity->getRevisionId();
  1104. $id = $entity->id();
  1105. $bundle = $entity->bundle();
  1106. $entity_type = $entity->getEntityTypeId();
  1107. $default_langcode = $entity->getUntranslated()->language()->getId();
  1108. $translation_langcodes = array_keys($entity->getTranslationLanguages());
  1109. $table_mapping = $this->getTableMapping();
  1110. if (!isset($vid)) {
  1111. $vid = $id;
  1112. }
  1113. $original = !empty($entity->original) ? $entity->original : NULL;
  1114. // Determine which fields should be actually stored.
  1115. $definitions = $this->entityManager->getFieldDefinitions($entity_type, $bundle);
  1116. if ($names) {
  1117. $definitions = array_intersect_key($definitions, array_flip($names));
  1118. }
  1119. foreach ($definitions as $field_name => $field_definition) {
  1120. $storage_definition = $field_definition->getFieldStorageDefinition();
  1121. if (!$table_mapping->requiresDedicatedTableStorage($storage_definition)) {
  1122. continue;
  1123. }
  1124. // When updating an existing revision, keep the existing records if the
  1125. // field values did not change.
  1126. if (!$entity->isNewRevision() && $original && !$this->hasFieldValueChanged($field_definition, $entity, $original)) {
  1127. continue;
  1128. }
  1129. $table_name = $table_mapping->getDedicatedDataTableName($storage_definition);
  1130. $revision_name = $table_mapping->getDedicatedRevisionTableName($storage_definition);
  1131. // Delete and insert, rather than update, in case a value was added.
  1132. if ($update) {
  1133. // Only overwrite the field's base table if saving the default revision
  1134. // of an entity.
  1135. if ($entity->isDefaultRevision()) {
  1136. $this->database->delete($table_name)
  1137. ->condition('entity_id', $id)
  1138. ->execute();
  1139. }
  1140. if ($this->entityType->isRevisionable()) {
  1141. $this->database->delete($revision_name)
  1142. ->condition('entity_id', $id)
  1143. ->condition('revision_id', $vid)
  1144. ->execute();
  1145. }
  1146. }
  1147. // Prepare the multi-insert query.
  1148. $do_insert = FALSE;
  1149. $columns = ['entity_id', 'revision_id', 'bundle', 'delta', 'langcode'];
  1150. foreach ($storage_definition->getColumns() as $column => $attributes) {
  1151. $columns[] = $table_mapping->getFieldColumnName($storage_definition, $column);
  1152. }
  1153. $query = $this->database->insert($table_name)->fields($columns);
  1154. if ($this->entityType->isRevisionable()) {
  1155. $revision_query = $this->database->insert($revision_name)->fields($columns);
  1156. }
  1157. $langcodes = $field_definition->isTranslatable() ? $translation_langcodes : [$default_langcode];
  1158. foreach ($langcodes as $langcode) {
  1159. $delta_count = 0;
  1160. $items = $entity->getTranslation($langcode)->get($field_name);
  1161. $items->filterEmptyItems();
  1162. foreach ($items as $delta => $item) {
  1163. // We now know we have something to insert.
  1164. $do_insert = TRUE;
  1165. $record = [
  1166. 'entity_id' => $id,
  1167. 'revision_id' => $vid,
  1168. 'bundle' => $bundle,
  1169. 'delta' => $delta,
  1170. 'langcode' => $langcode,
  1171. ];
  1172. foreach ($storage_definition->getColumns() as $column => $attributes) {
  1173. $column_name = $table_mapping->getFieldColumnName($storage_definition, $column);
  1174. // Serialize the value if specified in the column schema.
  1175. $value = $item->$column;
  1176. if (!empty($attributes['serialize'])) {
  1177. $value = serialize($value);
  1178. }
  1179. $record[$column_name] = drupal_schema_get_field_value($attributes, $value);
  1180. }
  1181. $query->values($record);
  1182. if ($this->entityType->isRevisionable()) {
  1183. $revision_query->values($record);
  1184. }
  1185. if ($storage_definition->getCardinality() != FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED && ++$delta_count == $storage_definition->getCardinality()) {
  1186. break;
  1187. }
  1188. }
  1189. }
  1190. // Execute the query if we have values to insert.
  1191. if ($do_insert) {
  1192. // Only overwrite the field's base table if saving the default revision
  1193. // of an entity.
  1194. if ($entity->isDefaultRevision()) {
  1195. $query->execute();
  1196. }
  1197. if ($this->entityType->isRevisionable()) {
  1198. $revision_query->execute();
  1199. }
  1200. }
  1201. }
  1202. }
  1203. /**
  1204. * Deletes values of fields in dedicated tables for all revisions.
  1205. *
  1206. * @param \Drupal\Core\Entity\ContentEntityInterface $entity
  1207. * The entity.
  1208. */
  1209. protected function deleteFromDedicatedTables(ContentEntityInterface $entity) {
  1210. $table_mapping = $this->getTableMapping();
  1211. foreach ($this->entityManager->getFieldDefinitions($entity->getEntityTypeId(), $entity->bundle()) as $field_definition) {
  1212. $storage_definition = $field_definition->getFieldStorageDefinition();
  1213. if (!$table_mapping->requiresDedicatedTableStorage($storage_definition)) {
  1214. continue;
  1215. }
  1216. $table_name = $table_mapping->getDedicatedDataTableName($storage_definition);
  1217. $revision_name = $table_mapping->getDedicatedRevisionTableName($storage_definition);
  1218. $this->database->delete($table_name)
  1219. ->condition('entity_id', $entity->id())
  1220. ->execute();
  1221. if ($this->entityType->isRevisionable()) {
  1222. $this->database->delete($revision_name)
  1223. ->condition('entity_id', $entity->id())
  1224. ->execute();
  1225. }
  1226. }
  1227. }
  1228. /**
  1229. * Deletes values of fields in dedicated tables for all revisions.
  1230. *
  1231. * @param \Drupal\Core\Entity\ContentEntityInterface $entity
  1232. * The entity. It must have a revision ID.
  1233. */
  1234. protected function deleteRevisionFromDedicatedTables(ContentEntityInterface $entity) {
  1235. $vid = $entity->getRevisionId();
  1236. if (isset($vid)) {
  1237. $table_mapping = $this->getTableMapping();
  1238. foreach ($this->entityManager->getFieldDefinitions($entity->getEntityTypeId(), $entity->bundle()) as $field_definition) {
  1239. $storage_definition = $field_definition->getFieldStorageDefinition();
  1240. if (!$table_mapping->requiresDedicatedTableStorage($storage_definition)) {
  1241. continue;
  1242. }
  1243. $revision_name = $table_mapping->getDedicatedRevisionTableName($storage_definition);
  1244. $this->database->delete($revision_name)
  1245. ->condition('entity_id', $entity->id())
  1246. ->condition('revision_id', $vid)
  1247. ->execute();
  1248. }
  1249. }
  1250. }
  1251. /**
  1252. * {@inheritdoc}
  1253. */
  1254. public function requiresEntityStorageSchemaChanges(EntityTypeInterface $entity_type, EntityTypeInterface $original) {
  1255. return $this->getStorageSchema()->requiresEntityStorageSchemaChanges($entity_type, $original);
  1256. }
  1257. /**
  1258. * {@inheritdoc}
  1259. */
  1260. public function requiresFieldStorageSchemaChanges(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) {
  1261. return $this->getStorageSchema()->requiresFieldStorageSchemaChanges($storage_definition, $original);
  1262. }
  1263. /**
  1264. * {@inheritdoc}
  1265. */
  1266. public function requiresEntityDataMigration(EntityTypeInterface $entity_type, EntityTypeInterface $original) {
  1267. return $this->getStorageSchema()->requiresEntityDataMigration($entity_type, $original);
  1268. }
  1269. /**
  1270. * {@inheritdoc}
  1271. */
  1272. public function requiresFieldDataMigration(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) {
  1273. return $this->getStorageSchema()->requiresFieldDataMigration($storage_definition, $original);
  1274. }
  1275. /**
  1276. * {@inheritdoc}
  1277. */
  1278. public function onEntityTypeCreate(EntityTypeInterface $entity_type) {
  1279. $this->wrapSchemaException(function () use ($entity_type) {
  1280. $this->getStorageSchema()->onEntityTypeCreate($entity_type);
  1281. });
  1282. }
  1283. /**
  1284. * {@inheritdoc}
  1285. */
  1286. public function onEntityTypeUpdate(EntityTypeInterface $entity_type, EntityTypeInterface $original) {
  1287. // Ensure we have an updated entity type definition.
  1288. $this->entityType = $entity_type;
  1289. // The table layout may have changed depending on the new entity type
  1290. // definition.
  1291. $this->initTableLayout();
  1292. // Let the schema handler adapt to possible table layout changes.
  1293. $this->wrapSchemaException(function () use ($entity_type, $original) {
  1294. $this->getStorageSchema()->onEntityTypeUpdate($entity_type, $original);
  1295. });
  1296. }
  1297. /**
  1298. * {@inheritdoc}
  1299. */
  1300. public function onEntityTypeDelete(EntityTypeInterface $entity_type) {
  1301. $this->wrapSchemaException(function () use ($entity_type) {
  1302. $this->getStorageSchema()->onEntityTypeDelete($entity_type);
  1303. });
  1304. }
  1305. /**
  1306. * {@inheritdoc}
  1307. */
  1308. public function onFieldStorageDefinitionCreate(FieldStorageDefinitionInterface $storage_definition) {
  1309. // If we are adding a field stored in a shared table we need to recompute
  1310. // the table mapping.
  1311. // @todo This does not belong here. Remove it once we are able to generate a
  1312. // fresh table mapping in the schema handler. See
  1313. // https://www.drupal.org/node/2274017.
  1314. if ($this->getTableMapping()->allowsSharedTableStorage($storage_definition)) {
  1315. $this->tableMapping = NULL;
  1316. }
  1317. $this->wrapSchemaException(function () use ($storage_definition) {
  1318. $this->getStorageSchema()->onFieldStorageDefinitionCreate($storage_definition);
  1319. });
  1320. }
  1321. /**
  1322. * {@inheritdoc}
  1323. */
  1324. public function onFieldStorageDefinitionUpdate(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) {
  1325. $this->wrapSchemaException(function () use ($storage_definition, $original) {
  1326. $this->getStorageSchema()->onFieldStorageDefinitionUpdate($storage_definition, $original);
  1327. });
  1328. }
  1329. /**
  1330. * {@inheritdoc}
  1331. */
  1332. public function onFieldStorageDefinitionDelete(FieldStorageDefinitionInterface $storage_definition) {
  1333. $table_mapping = $this->getTableMapping(
  1334. $this->entityManager->getLastInstalledFieldStorageDefinitions($this->entityType->id())
  1335. );
  1336. if ($table_mapping->requiresDedicatedTableStorage($storage_definition)) {
  1337. // Mark all data associated with the field for deletion.
  1338. $table = $table_mapping->getDedicatedDataTableName($storage_definition);
  1339. $revision_table = $table_mapping->getDedicatedRevisionTableName($storage_definition);
  1340. $this->database->update($table)
  1341. ->fields(['deleted' => 1])
  1342. ->execute();
  1343. if ($this->entityType->isRevisionable()) {
  1344. $this->database->update($revision_table)
  1345. ->fields(['deleted' => 1])
  1346. ->execute();
  1347. }
  1348. }
  1349. // Update the field schema.
  1350. $this->wrapSchemaException(function () use ($storage_definition) {
  1351. $this->getStorageSchema()->onFieldStorageDefinitionDelete($storage_definition);
  1352. });
  1353. }
  1354. /**
  1355. * Wraps a database schema exception into an entity storage exception.
  1356. *
  1357. * @param callable $callback
  1358. * The callback to be executed.
  1359. *
  1360. * @throws \Drupal\Core\Entity\EntityStorageException
  1361. * When a database schema exception is thrown.
  1362. */
  1363. protected function wrapSchemaException(callable $callback) {
  1364. $message = 'Exception thrown while performing a schema update.';
  1365. try {
  1366. $callback();
  1367. }
  1368. catch (SchemaException $e) {
  1369. $message .= ' ' . $e->getMessage();
  1370. throw new EntityStorageException($message, 0, $e);
  1371. }
  1372. catch (DatabaseExceptionWrapper $e) {
  1373. $message .= ' ' . $e->getMessage();
  1374. throw new EntityStorageException($message, 0, $e);
  1375. }
  1376. }
  1377. /**
  1378. * {@inheritdoc}
  1379. */
  1380. public function onFieldDefinitionDelete(FieldDefinitionInterface $field_definition) {
  1381. $table_mapping = $this->getTableMapping();
  1382. $storage_definition = $field_definition->getFieldStorageDefinition();
  1383. // Mark field data as deleted.
  1384. if ($table_mapping->requiresDedicatedTableStorage($storage_definition)) {
  1385. $table_name = $table_mapping->getDedicatedDataTableName($storage_definition);
  1386. $revision_name = $table_mapping->getDedicatedRevisionTableName($storage_definition);
  1387. $this->database->update($table_name)
  1388. ->fields(['deleted' => 1])
  1389. ->condition('bundle', $field_definition->getTargetBundle())
  1390. ->execute();
  1391. if ($this->entityType->isRevisionable()) {
  1392. $this->database->update($revision_name)
  1393. ->fields(['deleted' => 1])
  1394. ->condition('bundle', $field_definition->getTargetBundle())
  1395. ->execute();
  1396. }
  1397. }
  1398. }
  1399. /**
  1400. * {@inheritdoc}
  1401. */
  1402. public function onBundleCreate($bundle, $entity_type_id) {}
  1403. /**
  1404. * {@inheritdoc}
  1405. */
  1406. public function onBundleDelete($bundle, $entity_type_id) {}
  1407. /**
  1408. * {@inheritdoc}
  1409. */
  1410. protected function readFieldItemsToPurge(FieldDefinitionInterface $field_definition, $batch_size) {
  1411. // Check whether the whole field storage definition is gone, or just some
  1412. // bundle fields.
  1413. $storage_definition = $field_definition->getFieldStorageDefinition();
  1414. $table_mapping = $this->getTableMapping();
  1415. $table_name = $table_mapping->getDedicatedDataTableName($storage_definition, $storage_definition->isDeleted());
  1416. // Get the entities which we want to purge first.
  1417. $entity_query = $this->database->select($table_name, 't', ['fetch' => \PDO::FETCH_ASSOC]);
  1418. $or = $entity_query->orConditionGroup();
  1419. foreach ($storage_definition->getColumns() as $column_name => $data) {
  1420. $or->isNotNull($table_mapping->getFieldColumnName($storage_definition, $column_name));
  1421. }
  1422. $entity_query
  1423. ->distinct(TRUE)
  1424. ->fields('t', ['entity_id'])
  1425. ->condition('bundle', $field_definition->getTargetBundle())
  1426. ->range(0, $batch_size);
  1427. // Create a map of field data table column names to field column names.
  1428. $column_map = [];
  1429. foreach ($storage_definition->getColumns() as $column_name => $data) {
  1430. $column_map[$table_mapping->getFieldColumnName($storage_definition, $column_name)] = $column_name;
  1431. }
  1432. $entities = [];
  1433. $items_by_entity = [];
  1434. foreach ($entity_query->execute() as $row) {
  1435. $item_query = $this->database->select($table_name, 't', ['fetch' => \PDO::FETCH_ASSOC])
  1436. ->fields('t')
  1437. ->condition('entity_id', $row['entity_id'])
  1438. ->condition('deleted', 1)
  1439. ->orderBy('delta');
  1440. foreach ($item_query->execute() as $item_row) {
  1441. if (!isset($entities[$item_row['revision_id']])) {
  1442. // Create entity with the right revision id and entity id combination.
  1443. $item_row['entity_type'] = $this->entityTypeId;
  1444. // @todo: Replace this by an entity object created via an entity
  1445. // factory, see https://www.drupal.org/node/1867228.
  1446. $entities[$item_row['revision_id']] = _field_create_entity_from_ids((object) $item_row);
  1447. }
  1448. $item = [];
  1449. foreach ($column_map as $db_column => $field_column) {
  1450. $item[$field_column] = $item_row[$db_column];
  1451. }
  1452. $items_by_entity[$item_row['revision_id']][] = $item;
  1453. }
  1454. }
  1455. // Create field item objects and return.
  1456. foreach ($items_by_entity as $revision_id => $values) {
  1457. $entity_adapter = $entities[$revision_id]->getTypedData();
  1458. $items_by_entity[$revision_id] = \Drupal::typedDataManager()->create($field_definition, $values, $field_definition->getName(), $entity_adapter);
  1459. }
  1460. return $items_by_entity;
  1461. }
  1462. /**
  1463. * {@inheritdoc}
  1464. */
  1465. protected function purgeFieldItems(ContentEntityInterface $entity, FieldDefinitionInterface $field_definition) {
  1466. $storage_definition = $field_definition->getFieldStorageDefinition();
  1467. $is_deleted = $storage_definition->isDeleted();
  1468. $table_mapping = $this->getTableMapping();
  1469. $table_name = $table_mapping->getDedicatedDataTableName($storage_definition, $is_deleted);
  1470. $revision_name = $table_mapping->getDedicatedRevisionTableName($storage_definition, $is_deleted);
  1471. $revision_id = $this->entityType->isRevisionable() ? $entity->getRevisionId() : $entity->id();
  1472. $this->database->delete($table_name)
  1473. ->condition('revision_id', $revision_id)
  1474. ->condition('deleted', 1)
  1475. ->execute();
  1476. if ($this->entityType->isRevisionable()) {
  1477. $this->database->delete($revision_name)
  1478. ->condition('revision_id', $revision_id)
  1479. ->condition('deleted', 1)
  1480. ->execute();
  1481. }
  1482. }
  1483. /**
  1484. * {@inheritdoc}
  1485. */
  1486. public function finalizePurge(FieldStorageDefinitionInterface $storage_definition) {
  1487. $this->getStorageSchema()->finalizePurge($storage_definition);
  1488. }
  1489. /**
  1490. * {@inheritdoc}
  1491. */
  1492. public function countFieldData($storage_definition, $as_bool = FALSE) {
  1493. // The table mapping contains stale data during a request when a field
  1494. // storage definition is added, so bypass the internal storage definitions
  1495. // and fetch the table mapping using the passed in storage definition.
  1496. // @todo Fix this in https://www.drupal.org/node/2705205.
  1497. $storage_definitions = $this->entityManager->getFieldStorageDefinitions($this->entityTypeId);
  1498. $storage_definitions[$storage_definition->getName()] = $storage_definition;
  1499. $table_mapping = $this->getTableMapping($storage_definitions);
  1500. if ($table_mapping->requiresDedicatedTableStorage($storage_definition)) {
  1501. $is_deleted = $storage_definition->isDeleted();
  1502. if ($this->entityType->isRevisionable()) {
  1503. $table_name = $table_mapping->getDedicatedRevisionTableName($storage_definition, $is_deleted);
  1504. }
  1505. else {
  1506. $table_name = $table_mapping->getDedicatedDataTableName($storage_definition, $is_deleted);
  1507. }
  1508. $query = $this->database->select($table_name, 't');
  1509. $or = $query->orConditionGroup();
  1510. foreach ($storage_definition->getColumns() as $column_name => $data) {
  1511. $or->isNotNull($table_mapping->getFieldColumnName($storage_definition, $column_name));
  1512. }
  1513. $query->condition($or);
  1514. if (!$as_bool) {
  1515. $query
  1516. ->fields('t', ['entity_id'])
  1517. ->distinct(TRUE);
  1518. }
  1519. }
  1520. elseif ($table_mapping->allowsSharedTableStorage($storage_definition)) {
  1521. // Ascertain the table this field is mapped too.
  1522. $field_name = $storage_definition->getName();
  1523. try {
  1524. $table_name = $table_mapping->getFieldTableName($field_name);
  1525. }
  1526. catch (SqlContentEntityStorageException $e) {
  1527. // This may happen when changing field storage schema, since we are not
  1528. // able to use a table mapping matching the passed storage definition.
  1529. // @todo Revisit this once we are able to instantiate the table mapping
  1530. // properly. See https://www.drupal.org/node/2274017.
  1531. $table_name = $this->dataTable ?: $this->baseTable;
  1532. }
  1533. $query = $this->database->select($table_name, 't');
  1534. $or = $query->orConditionGroup();
  1535. foreach (array_keys($storage_definition->getColumns()) as $property_name) {
  1536. $or->isNotNull($table_mapping->getFieldColumnName($storage_definition, $property_name));
  1537. }
  1538. $query->condition($or);
  1539. if (!$as_bool) {
  1540. $query
  1541. ->fields('t', [$this->idKey])
  1542. ->distinct(TRUE);
  1543. }
  1544. }
  1545. // @todo Find a way to count field data also for fields having custom
  1546. // storage. See https://www.drupal.org/node/2337753.
  1547. $count = 0;
  1548. if (isset($query)) {
  1549. // If we are performing the query just to check if the field has data
  1550. // limit the number of rows.
  1551. if ($as_bool) {
  1552. $query
  1553. ->range(0, 1)
  1554. ->addExpression('1');
  1555. }
  1556. else {
  1557. // Otherwise count the number of rows.
  1558. $query = $query->countQuery();
  1559. }
  1560. $count = $query->execute()->fetchField();
  1561. }
  1562. return $as_bool ? (bool) $count : (int) $count;
  1563. }
  1564. /**
  1565. * Determines whether the passed field has been already deleted.
  1566. *
  1567. * @param \Drupal\Core\Field\FieldStorageDefinitionInterface $storage_definition
  1568. * The field storage definition.
  1569. *
  1570. * @return bool
  1571. * Whether the field has been already deleted.
  1572. *
  1573. * @deprecated in Drupal 8.5.x, will be removed before Drupal 9.0.0. Use
  1574. * \Drupal\Core\Field\FieldStorageDefinitionInterface::isDeleted() instead.
  1575. *
  1576. * @see https://www.drupal.org/node/2907785
  1577. */
  1578. protected function storageDefinitionIsDeleted(FieldStorageDefinitionInterface $storage_definition) {
  1579. return $storage_definition->isDeleted();
  1580. }
  1581. }