PluginProbe
seQura / 3.2.2
seQura v3.2.2
4.3.4 4.3.3 4.3.2 4.3.1 trunk 2.0.0 2.0.10 2.0.11 2.0.12 2.0.5 2.0.6 2.0.7 2.0.8 2.0.9 3.0.0 3.0.2 3.0.5 3.0.6 3.0.7 3.1.0 3.1.1 3.2.0 3.2.1 3.2.2 4.0.0 All 30 releases
sequra / src / Repositories / class-repository.php

class-repository.php in seQura 3.2.2, at src/Repositories/class-repository.php

754 lines 21.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Shared repository functionality.
4 *
5 * @package SeQura/WC
6 * @subpackage SeQura/WC/Repositories
7 */
8
9 namespace SeQura\WC\Repositories;
10
11 use Exception;
12 use SeQura\Core\Infrastructure\ORM\Entity;
13 use SeQura\Core\Infrastructure\ORM\Exceptions\QueryFilterInvalidParamException;
14 use SeQura\Core\Infrastructure\ORM\Interfaces\RepositoryInterface;
15 use SeQura\Core\Infrastructure\ORM\QueryFilter\QueryCondition;
16 use SeQura\Core\Infrastructure\ORM\QueryFilter\QueryFilter;
17 use SeQura\Core\Infrastructure\ORM\Utility\IndexHelper;
18 use SeQura\Core\Infrastructure\ServiceRegister;
19 use SeQura\WC\Dto\Table_Index;
20 use SeQura\WC\Dto\Table_Index_Column;
21 use wpdb;
22
23 /**
24 * Shared repository functionality.
25 */
26 abstract class Repository implements RepositoryInterface, Interface_Deletable_Repository, Interface_Table_Migration_Repository {
27
28 /**
29 * Entity class FQN.
30 *
31 * @var string
32 */
33 protected $entity_class;
34
35 /**
36 * Database session object.
37 *
38 * @var \wpdb
39 */
40 protected $db;
41
42 /**
43 * Returns unprefixed table name.
44 */
45 abstract protected function get_unprefixed_table_name(): string;
46
47 /**
48 * Returns full table name.
49 */
50 public function get_table_name(): string {
51 return $this->db->prefix . $this->get_unprefixed_table_name();
52 }
53
54 /**
55 * Get the name that is set to the original table during the migration.
56 *
57 * @return string The name of the old table.
58 */
59 public function get_legacy_table_name() {
60 return $this->get_table_name() . '_legacy';
61 }
62
63 /**
64 * Constructor.
65 *
66 * @throws \RuntimeException If database service not found.
67 */
68 public function __construct() {
69 $db = ServiceRegister::getService( \wpdb::class );
70 if ( ! $db instanceof \wpdb ) {
71 throw new \RuntimeException( 'Database service not found.' );
72 }
73 $this->db = $db;
74 }
75
76 /**
77 * Returns full class name.
78 *
79 * @return string Full class name.
80 */
81 public static function getClassName() {
82 return __CLASS__;
83 }
84
85 /**
86 * Sets repository entity
87 *
88 * @noinspection PhpDocMissingThrowsInspection
89 *
90 * @param string $entity_class Entity class.
91 * @return void
92 */
93 public function setEntityClass( $entity_class ): void {
94 $this->entity_class = $entity_class;
95 }
96
97 /**
98 * Executes select query.
99 *
100 * @param QueryFilter $filter Filter for query.
101 *
102 * @return Entity[] A list of found entities ot empty array.
103 * @throws QueryFilterInvalidParamException If filter condition is invalid.
104 */
105 public function select( QueryFilter $filter = null ) {
106 /**
107 * Entity object.
108 *
109 * @var Entity $entity
110 */
111 $entity = new $this->entity_class();
112 $type = $entity->getConfig()->getType();
113
114 $query = "SELECT * FROM {$this->get_table_name()} WHERE type = '$type' ";
115 if ( $filter ) {
116 $query .= $this->apply_query_filter( $filter, IndexHelper::mapFieldsToIndexes( $entity ) );
117 }
118
119 $raw_results = array();
120 if ( $this->table_exists() ) {
121 $raw_results = $this->db->get_results( $query, ARRAY_A );
122 if ( ! is_array( $raw_results ) ) {
123 $raw_results = array();
124 }
125 }
126 if ( $this->table_exists( true ) ) {
127 // If the legacy table exists the data may be there.
128 $query = str_replace( $this->get_table_name(), $this->get_legacy_table_name(), $query );
129 $legacy_raw_results = $this->db->get_results( $query, ARRAY_A );
130 if ( ! is_array( $legacy_raw_results ) ) {
131 $legacy_raw_results = array();
132 }
133 $raw_results = array_merge( $raw_results, $legacy_raw_results );
134 }
135
136 return $this->translateToEntities( $raw_results );
137 }
138
139 /**
140 * Executes select query and returns first result.
141 *
142 * @param QueryFilter $filter Filter for query.
143 *
144 * @return Entity|null First found entity or NULL.
145 * @throws QueryFilterInvalidParamException If filter condition is invalid.
146 */
147 public function selectOne( QueryFilter $filter = null ) {
148 if ( ! $filter ) {
149 $filter = new QueryFilter();
150 }
151
152 $filter->setLimit( 1 );
153 $results = $this->select( $filter );
154
155 return ! empty( $results ) ? $results[0] : null;
156 }
157
158 /**
159 * Executes insert query and returns ID of created entity. Entity will be updated with new ID.
160 *
161 * @param Entity $entity Entity to be saved.
162 *
163 * @return int Identifier of saved entity.
164 */
165 public function save( Entity $entity ) {
166 if ( ! $this->table_exists() ) {
167 return -1;
168 }
169
170 if ( $entity->getId() ) {
171 $this->update( $entity );
172
173 return $entity->getId();
174 }
175
176 return $this->save_entity_to_storage( $entity );
177 }
178
179 /**
180 * Executes update query and returns success flag.
181 *
182 * @param Entity $entity Entity to be updated.
183 *
184 * @return bool TRUE if operation succeeded; otherwise, FALSE.
185 */
186 public function update( Entity $entity ) {
187 if ( ! $this->table_exists() ) {
188 return false;
189 }
190 $item = $this->prepare_entity_for_storage( $entity );
191 $where = array( 'id' => $entity->getId() );
192
193 // Check if entity wasn't already migrated and migrate it including the new data.
194 if ( $this->table_exists( true ) && $this->entity_exists( $entity->getId(), true ) ) {
195 if ( 1 !== $this->db->update( $this->get_legacy_table_name(), $item, $where ) ) {
196 return false;
197 }
198 // Read from the legacy table.
199 $raw_results = $this->db->get_results( "SELECT * FROM {$this->get_legacy_table_name()} WHERE id = {$entity->getId()} LIMIT 1;", ARRAY_A );
200 if ( empty( $raw_results ) ) {
201 return false;
202 }
203 $entity = $this->translateToEntities( $raw_results )[0] ?? null;
204 if ( ! $entity ) {
205 return false;
206 }
207 // Insert into the new table.
208 $item = $this->prepare_entity_for_storage( $entity );
209 if ( false !== $this->db->insert( $this->get_table_name(), $item ) ) {
210 return false;
211 }
212 // Delete the row from the legacy table.
213 $this->db->delete( $this->get_legacy_table_name(), $where );
214 return true;
215 }
216 // Only one record should be updated.
217 return 1 === $this->db->update( $this->get_table_name(), $item, $where );
218 }
219
220 /**
221 * Executes delete query and returns success flag.
222 *
223 * @param Entity $entity Entity to be deleted.
224 *
225 * @return bool TRUE if operation succeeded; otherwise, FALSE.
226 */
227 public function delete( Entity $entity ) {
228 $where = array( 'id' => $entity->getId() );
229 $deleted = false;
230 if ( $this->table_exists() ) {
231 $result = $this->db->delete( $this->get_table_name(), $where );
232 $deleted = ! empty( $result );
233 }
234 if ( $this->table_exists( true ) ) {
235 // Delete from legacy table.
236 $result = $this->db->delete( $this->get_legacy_table_name(), $where );
237 $deleted = $deleted || ! empty( $result );
238 }
239 return $deleted;
240 }
241
242 /**
243 * Counts records that match filter criteria.
244 *
245 * @param QueryFilter $filter Filter for query.
246 *
247 * @return int Number of records that match filter criteria.
248 * @throws QueryFilterInvalidParamException If filter condition is invalid.
249 */
250 public function count( QueryFilter $filter = null ) {
251 /**
252 * Entity object.
253 *
254 * @var Entity $entity
255 */
256 $entity = new $this->entity_class();
257 $type = $entity->getConfig()->getType();
258
259 $query = "SELECT COUNT(*) as `total` FROM {$this->get_table_name()} WHERE type = '$type' ";
260 if ( $filter ) {
261 $query .= $this->apply_query_filter( $filter, IndexHelper::mapFieldsToIndexes( $entity ) );
262 }
263 $count = 0;
264 if ( $this->table_exists() ) {
265 $result = $this->db->get_results( $query, ARRAY_A );
266 $count += empty( $result[0]['total'] ) || ! is_numeric( $result[0]['total'] ) ? 0 : (int) $result[0]['total'];
267 }
268 if ( $this->table_exists( true ) ) {
269 // If the legacy table exists, count the data there too.
270 $query = str_replace( $this->get_table_name(), $this->get_legacy_table_name(), $query );
271 $result = $this->db->get_results( $query, ARRAY_A );
272 $count += empty( $result[0]['total'] ) || ! is_numeric( $result[0]['total'] ) ? 0 : (int) $result[0]['total'];
273 }
274 return $count;
275 }
276
277 /**
278 * Escapes provided value.
279 *
280 * @param mixed $value Value to be escaped.
281 *
282 * @return string Escaped value.
283 */
284 protected function escape( $value ) {
285 return addslashes( strval( $value ) );
286 }
287
288 /**
289 * Checks if value exists and escapes it if it's not.
290 *
291 * @param mixed $value Value to be escaped.
292 *
293 * @return string Escaped value.
294 */
295 protected function escape_value( $value ) {
296 return null === $value ? 'NULL' : "'" . $this->escape( $value ) . "'";
297 }
298
299 /**
300 * Builds WHERE part of select query.
301 *
302 * @param mixed[]$filter_by Filter conditions in query.
303 *
304 * @return string Where condition.
305 */
306 protected function build_condition( $filter_by ) {
307 if ( empty( $filter_by ) ) {
308 return '';
309 }
310
311 $where = array();
312 foreach ( $filter_by as $key => $value ) {
313 if ( null === $value ) {
314 $where[] = "`$key` IS NULL";
315 } else {
316 $where[] = "`$key` = '" . $this->escape( $value ) . "'";
317 }
318 }
319
320 return ' WHERE ' . implode( ' AND ', $where );
321 }
322
323 /**
324 * Converts filter value to index string representation.
325 *
326 * @param QueryCondition $condition Query condition.
327 *
328 * @return string|null Converted value.
329 */
330 protected function convert_value( QueryCondition $condition ) {
331 $value = IndexHelper::castFieldValue( $condition->getValue(), $condition->getValueType() );
332 switch ( $condition->getValueType() ) {
333 case 'string':
334 $value = $this->escape_value( $condition->getValue() );
335 break;
336 case 'array':
337 /**
338 * Values
339 *
340 * @var mixed[] $values
341 */
342 $values = $condition->getValue();
343 $escaped_values = array();
344 foreach ( $values as $value ) {
345 $escaped_values[] = is_string( $value ) ? $this->escape_value( $value ) : $value;
346 }
347
348 $value = '(' . implode( ', ', $escaped_values ) . ')';
349 break;
350 default:
351 // 'integer', 'dateTime','boolean','double'
352 $value = $this->escape_value( $value );
353 break;
354 }
355
356 return $value;
357 }
358
359 /**
360 * Builds query filter part of the query.
361 *
362 * @param QueryFilter $filter Query filter object.
363 * @param mixed[] $field_index_map Property to index number map.
364 *
365 * @return string Query filter addendum.
366 * @throws QueryFilterInvalidParamException If filter condition is invalid.
367 */
368 protected function apply_query_filter( QueryFilter $filter, array $field_index_map = array() ) {
369 $query = '';
370 $conditions = $filter->getConditions();
371 if ( ! empty( $conditions ) ) {
372 $query .= ' AND (';
373 $first = true;
374 foreach ( $conditions as $condition ) {
375 $this->validate_index_column( $condition->getColumn(), $field_index_map );
376 $chain_op = $first ? '' : $condition->getChainOperator();
377 $first = false;
378 $column = 'id' === $condition->getColumn() ? 'id' : 'index_' . $field_index_map[ $condition->getColumn() ];
379 $operator = $condition->getOperator();
380 $query .= " $chain_op $column $operator " . $this->convert_value( $condition );
381 }
382
383 $query .= ')';
384 }
385
386 if ( $filter->getOrderByColumn() ) {
387 $this->validate_index_column( $filter->getOrderByColumn(), $field_index_map );
388 $order_index = 'id' === $filter->getOrderByColumn() ? 'id' : 'index_' . $field_index_map[ $filter->getOrderByColumn() ];
389 $query .= " ORDER BY {$order_index} {$filter->getOrderDirection()}";
390 }
391
392 if ( $filter->getLimit() ) {
393 $offset = (int) $filter->getOffset();
394 $query .= " LIMIT {$offset}, {$filter->getLimit()}";
395 }
396
397 return $query;
398 }
399
400 /**
401 * Transforms raw database query rows to entities.
402 *
403 * @param mixed[]$result Raw database query result.
404 *
405 * @return Entity[] Array of transformed entities.
406 */
407 protected function translateToEntities( array $result ) {
408 /**
409 * Array of decoded entities.
410 *
411 * @var Entity[] $entities
412 */
413 $entities = array();
414 foreach ( $result as $item ) {
415 /**
416 * Raw data.
417 *
418 * @var mixed[] $item
419 */
420 if ( ! isset( $item['data'] ) || ! isset( $item['id'] ) ) {
421 continue;
422 }
423 $data = (array) json_decode( strval( $item['data'] ), true );
424 /**
425 * Entity object.
426 *
427 * @var Entity $entity
428 */
429 $entity = isset( $data['class_name'] ) ? new $data['class_name']() : new $this->entity_class();
430 $entity->inflate( $data );
431 if ( is_numeric( $item['id'] ) ) {
432 $entity->setId( (int) $item['id'] );
433 }
434
435 $entities[] = $entity;
436 }
437
438 return $entities;
439 }
440
441 /**
442 * Saves entity to system storage.
443 *
444 * @param Entity $entity Entity to be stored.
445 *
446 * @return int Inserted entity identifier.
447 */
448 protected function save_entity_to_storage( Entity $entity ) {
449 if ( ! $this->table_exists() ) {
450 return -1;
451 }
452 $storage_item = $this->prepare_entity_for_storage( $entity );
453
454 $this->db->insert( $this->get_table_name(), $storage_item );
455
456 $insert_id = (int) $this->db->insert_id;
457 $entity->setId( $insert_id );
458
459 return $insert_id;
460 }
461
462 /**
463 * Prepares entity in format for storage.
464 *
465 * @param Entity $entity Entity to be stored.
466 *
467 * @return mixed[] Item prepared for storage.
468 */
469 protected function prepare_entity_for_storage( Entity $entity ) {
470 $indexes = IndexHelper::transformFieldsToIndexes( $entity );
471 $storage_item = array(
472 'type' => $entity->getConfig()->getType(),
473 'index_1' => null,
474 'index_2' => null,
475 'index_3' => null,
476 'index_4' => null,
477 'index_5' => null,
478 'index_6' => null,
479 'index_7' => null,
480 'data' => \wp_json_encode( $entity->toArray() ),
481 );
482
483 if ( $entity->getId() ) {
484 $storage_item['id'] = $entity->getId();
485 }
486
487 foreach ( $indexes as $index => $value ) {
488 $storage_item[ 'index_' . $index ] = $value;
489 }
490
491 return $storage_item;
492 }
493
494 /**
495 * Validates if column can be filtered or sorted by.
496 *
497 * @param string $column Column name.
498 * @param mixed[] $index_map Index map.
499 *
500 * @throws QueryFilterInvalidParamException If filter condition is invalid.
501 *
502 * @return void
503 */
504 protected function validate_index_column( $column, array $index_map ) {
505 if ( 'id' !== $column && ! array_key_exists( $column, $index_map ) ) {
506 throw new QueryFilterInvalidParamException( esc_html__( 'Column is not id or index.', 'sequra' ) );
507 }
508 }
509
510 /**
511 * Delete all the entities.
512 *
513 * @param string|null $store_id Delete entities from this store. Passing null will delete all entities.
514 */
515 public function delete_all( $store_id = null ): bool {
516 $deleted = false;
517 $sql = 'DELETE FROM ' . \sanitize_text_field( $this->get_table_name() );
518 if ( $store_id ) {
519 $column = $this->get_store_id_index_column();
520 if ( ! $column ) {
521 return false;
522 }
523 $sql .= ' WHERE ' . \sanitize_text_field( $column ) . ' = ' . \sanitize_text_field( $store_id );
524 }
525 if ( $this->table_exists() ) {
526 $result = $this->db->query( $sql );
527 $deleted = ! empty( $result );
528 }
529 if ( $this->table_exists( true ) ) {
530 $result = $this->db->query( str_replace( $this->get_table_name(), $this->get_legacy_table_name(), $sql ) );
531 $deleted = $deleted || ! empty( $result );
532 }
533 return $deleted;
534 }
535
536 /**
537 * Get the index column name that stores the store ID.
538 *
539 * @return string Index column name or empty string if not applicable.
540 */
541 protected function get_store_id_index_column(): string {
542 return 'index_1';
543 }
544
545 /**
546 * Check if table exists in the database.
547 *
548 * @param boolean $legacy If true, check for legacy table.
549 */
550 public function table_exists( $legacy = false ): bool {
551 $table_name = \sanitize_text_field( ! $legacy ? $this->get_table_name() : $this->get_legacy_table_name() );
552 return $this->db->get_var( "SHOW TABLES LIKE '{$table_name}'" ) === $table_name;
553 }
554
555 /**
556 * Remove entities that are older than a certain date or that are invalid.
557 * This performs a cleanup of the repository data.
558 */
559 public function delete_old_and_invalid() {
560 // Do nothing by default. Implement in child class if needed.
561 }
562
563 /**
564 * Check if the index exists.
565 *
566 * @param Table_Index $index The index to check.
567 * @return bool True if the index exists, false otherwise.
568 */
569 public function index_exists( $index ) {
570 $index_name = \sanitize_key( $index->name );
571 return ! empty( $this->db->get_col( "SHOW INDEX FROM `{$this->get_table_name()}` WHERE Key_name = '{$index_name}'" ) );
572 }
573
574 /**
575 * Add an index to the table.
576 *
577 * @param Table_Index $index The index.
578 * @return bool True if the index was added or already exists, false otherwise.
579 */
580 public function add_index( $index ) {
581 if ( $this->index_exists( $index ) ) {
582 return true;
583 }
584 $index_name = \sanitize_key( $index->name );
585 $columns = array();
586 foreach ( $index->columns as $column ) {
587 $columns[] = '`' . \sanitize_key( $column->name ) . '`' . ( null !== $column->char_limit ? "({$column->char_limit})" : '' );
588 }
589 $columns = implode( ',', $columns );
590 return false !== $this->db->query( "ALTER TABLE `{$this->get_table_name()}` ADD INDEX `{$index_name}` ({$columns})" );
591 }
592
593 /**
594 * Execute the migration process one by one.
595 * This implementation is intended for migrations that don't change the table structure.
596 */
597 public function migrate_next_row() {
598 if ( ! $this->table_exists() || ! $this->table_exists( true ) ) {
599 return;
600 }
601 $raw_results = $this->db->get_results( "SELECT * FROM {$this->get_legacy_table_name()} LIMIT 1;", ARRAY_A );
602 if ( ! is_array( $raw_results ) ) {
603 return;
604 }
605
606 $entity = $this->translateToEntities( $raw_results )[0] ?? null;
607 if ( ! $entity ) {
608 return;
609 }
610 // Check if entity already exists in the table.
611 if ( $this->entity_exists( $entity->getId() ) ) {
612 return;
613 }
614
615 $storage_item = $this->prepare_entity_for_storage( $entity );
616 $result = $this->db->insert( $this->get_table_name(), $storage_item );
617 if ( false !== $result ) {
618 // Delete the row from the legacy table.
619 $this->db->delete( $this->get_legacy_table_name(), array( 'id' => $entity->getId() ) );
620 }
621 }
622
623 /**
624 * Check if the migration process is complete.
625 *
626 * @return bool True if the migration process is complete, false otherwise.
627 */
628 public function is_migration_complete() {
629 // Check if the legacy table exists.
630 if ( $this->table_exists( true ) ) {
631 return false;
632 }
633 // Check if the indexes exist.
634 $indexes = $this->get_required_indexes();
635 foreach ( $indexes as $index ) {
636 if ( ! $this->index_exists( $index ) ) {
637 return false;
638 }
639 }
640
641 return true;
642 }
643
644 /**
645 * Get the SQL statement to create the table without the indexes definition.
646 * Resulting string should include an additional %s placeholder for the indexes.
647 *
648 * @return string The SQL statement to create the table.
649 */
650 protected function get_create_table_sql() {
651 $charset_collate = $this->db->get_charset_collate();
652 return "CREATE TABLE {$this->get_table_name()} (
653 `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
654 `type` VARCHAR(255),
655 `index_1` VARCHAR(127),
656 `index_2` VARCHAR(127),
657 `index_3` VARCHAR(127),
658 `index_4` VARCHAR(127),
659 `index_5` VARCHAR(127),
660 `index_6` VARCHAR(127),
661 `index_7` VARCHAR(127),
662 `data` LONGTEXT,
663 PRIMARY KEY (id) %s) $charset_collate;";
664 }
665
666 /**
667 * Create the table if it doesn't exist.
668 *
669 * @throws Exception If the table creation fails.
670 */
671 public function create_table() {
672 $indexes = array();
673 foreach ( $this->get_required_indexes() as $index ) {
674 $indexes[] = $index->to_sql();
675 }
676 $indexes = implode( ', ', $indexes );
677 if ( ! empty( $indexes ) ) {
678 $indexes = ', ' . $indexes;
679 }
680
681 $sql = sprintf( $this->get_create_table_sql(), $indexes );
682 require_once ABSPATH . 'wp-admin/includes/upgrade.php';
683 $result = \dbDelta( $sql );
684 if ( ! $this->table_exists() ) {
685 throw new Exception( \esc_html( "SQL: $sql\nResult: " . implode( '. ', $result ) ) );
686 }
687 }
688
689 /**
690 * Make sure that the required tables for the migration are created.
691 *
692 * @throws Exception If cannot prepare tables for migration.
693 */
694 public function prepare_tables_for_migration() {
695 // Rename the table to legacy table if it doesn't exist.
696 if ( ! $this->table_exists( true ) && false === $this->db->query( "RENAME TABLE {$this->get_table_name()} TO {$this->get_legacy_table_name()};" ) ) {
697 throw new Exception( \esc_html( "Could not rename table {$this->get_table_name()} to {$this->get_legacy_table_name()}" ) );
698 }
699
700 if ( ! $this->table_exists() ) {
701 // Create the table if not exists.
702 $this->create_table();
703
704 // Add the auto-increment next value to the new table.
705 $raw_id = $this->db->get_var( "SELECT MAX(id) FROM {$this->get_legacy_table_name()};" );
706 $auto_increment = null !== $raw_id && is_numeric( $raw_id ) ? (int) $raw_id + 1 : 1;
707 if ( false === $this->db->query( "ALTER TABLE {$this->get_table_name()} AUTO_INCREMENT = {$auto_increment};" ) ) {
708 throw new Exception( \esc_html( "Could not set auto-increment value for table {$this->get_table_name()} to {$auto_increment}" ) );
709 }
710 }
711 }
712
713 /**
714 * Evaluates if the legacy table should be removed and if so, removes it.
715 *
716 * @return bool True if the legacy table was removed or did not exist, false otherwise.
717 */
718 public function maybe_remove_legacy_table() {
719 if ( ! $this->table_exists( true ) ) {
720 return true;
721 }
722 $raw_results = $this->db->get_results( "SELECT 1 FROM `{$this->get_legacy_table_name()}` LIMIT 1;", ARRAY_A );
723 if ( ! empty( $raw_results ) ) {
724 // Legacy table is not empty, do not remove it.
725 return false;
726 }
727 return false !== $this->db->query( "DROP TABLE IF EXISTS `{$this->get_legacy_table_name()}`;" );
728 }
729
730 /**
731 * Get a list of indexes that are required for the table.
732 *
733 * @return Table_Index[] The list of indexes.
734 */
735 public function get_required_indexes() {
736 return array(
737 new Table_Index( $this->get_table_name() . '_type', array( new Table_Index_Column( 'type', 64 ) ) ),
738 );
739 }
740
741 /**
742 * Check if entity exists in the database.
743 *
744 * @param int $id Entity ID.
745 * @param bool $legacy If true, check for legacy table.
746 * @return bool True if entity exists, false otherwise.
747 */
748 protected function entity_exists( $id, $legacy = false ): bool {
749 $table_name = $legacy ? $this->get_legacy_table_name() : $this->get_table_name();
750 $raw_results = $this->db->get_results( "SELECT 1 FROM `$table_name` WHERE id = {$id} LIMIT 1;", ARRAY_A );
751 return ! empty( $raw_results );
752 }
753 }
754