RebelCode/wp-cqrs-resource-models

View on GitHub
src/Wpdb/InsertCapableWpdbTrait.php

Summary

Maintainability
A
1 hr
Test Coverage
<?php

namespace RebelCode\Storage\Resource\WordPress\Wpdb;

use ArrayAccess;
use Dhii\Util\String\StringableInterface as Stringable;
use InvalidArgumentException;
use OutOfRangeException;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\ContainerInterface;
use Psr\Container\NotFoundExceptionInterface;
use stdClass;
use Traversable;
use wpdb;

/**
 * Common functionality for objects that can insert records into a database using WPDB.
 *
 * @since [*next-version*]
 */
trait InsertCapableWpdbTrait
{
    /**
     * Executes an INSERT SQL query, inserting records into the database.
     *
     * @since [*next-version*]
     *
     * @param array[]|ArrayAccess[]|stdClass[]|ContainerInterface[]|Traversable $records A list of records to insert.
     *
     * @throws ContainerExceptionInterface If an error occurred while reading from a record's container.
     *
     * @return array|stdClass|Traversable The IDs of the inserted records.
     */
    protected function _insert($records)
    {
        if ($this->_canWpdbInsertBulk()) {
            return $this->_execInsert($records);
        }

        $ids = [];

        foreach ($records as $_record) {
            $_recordIds = $this->_execInsert([$_record]);

            if (count($_recordIds) > 0) {
                $ids[] = $_recordIds[0];
            }
        }

        return $ids;
    }

    /**
     * Executes an INSERT SQL query, inserting multiple records into the database.
     *
     * @since [*next-version*]
     *
     * @param array[]|ArrayAccess[]|stdClass[]|ContainerInterface[]|Traversable $records A list of records to insert.
     *
     * @throws ContainerExceptionInterface If an error occurred while reading from a record's container.
     *
     * @return array|stdClass|Traversable The IDs of the inserted records.
     */
    protected function _execInsert($records)
    {
        $processedRecords = $this->_preProcessRecords($records, $hashValueMap);

        $values = array_values($hashValueMap);
        $tokens = array_combine($values, array_fill(0, count($values), '%s'));

        $query = $this->_buildInsertSql(
            $this->_getSqlInsertTable(),
            $this->_getSqlInsertColumnNames(),
            $processedRecords,
            $tokens
        );

        $this->_executeWpdbQuery($query, $values);

        return [$this->_getWpdbLastInsertedId()];
    }

    /**
     * Pre-processes the list of records.
     *
     * @since [*next-version*]
     *
     * @param array[]|ArrayAccess[]|stdClass[]|ContainerInterface[]|Traversable $records A list of records.
     * @param array                                                             $hashMap A hash-to-value map reference
     *                                                                                   to which new hash-value pairs
     *                                                                                   are written.
     *
     * @throws ContainerExceptionInterface If an error occurred while reading from a record's container.
     *
     * @return array The pre-processed record data list, as an array of record data associative sub-arrays.
     */
    protected function _preProcessRecords($records, &$hashMap = [])
    {
        // Initialize variable, in case it was declared implicitly during the method call
        if ($hashMap === null) {
            $hashMap = [];
        }

        $newRecords = [];

        foreach ($records as $_idx => $_record) {
            $newRecords[$_idx] = $this->_extractRecordData($_record, $hashMap);
        }

        return $newRecords;
    }

    /**
     * Extracts record's data from the container and into an array.
     *
     * @since [*next-version*]
     *
     * @param array|ArrayAccess|stdClass|ContainerInterface $record  The record data container.
     * @param array                                         $hashMap A hash-to-value map reference to which new
     *                                                               value-hash pairs are written.
     *
     * @throws ContainerExceptionInterface If an error occurred while reading from the record container.
     *
     * @return array The extracted record data as an associative array.
     */
    protected function _extractRecordData($record, array &$hashMap = [])
    {
        // Initialize variable, in case it was declared implicitly during the method call
        if ($hashMap === null) {
            $hashMap = [];
        }

        $result = [];

        foreach ($this->_getSqlInsertFieldColumnMap() as $_field => $_column) {
            try {
                $_value = $this->_containerGet($record, $_field);
                // Add column-to-value entry to record data
                $result[$_column] = $_value;

                if ($_value === null) {
                    continue;
                }

                // Calculate hash for value
                $_valueStr  = $this->_normalizeString($_value);
                $_valueHash = $this->_getWpdbValueHashString($_valueStr, count($hashMap) + 1);
                // Add value-to-hash entry to map
                $hashMap[$_valueHash] = $_value;
            } catch (NotFoundExceptionInterface $notFoundException) {
                continue;
            } catch (OutOfRangeException $outOfRangeException) {
                continue;
            }
        }

        return $result;
    }

    /**
     * Retrieves the ID of the record that was last inserted with WPDB.
     *
     * @since [*next-version*]
     *
     * @return int|string|Stringable The last inserted ID.
     */
    abstract protected function _getWpdbLastInsertedId();

    /**
     * Retrieves whether or not multiple records can be inserted in bulk (in a single INSERT query).
     *
     * @since [*next-version*]
     *
     * @return bool True if records should be inserted in a single query, false otherwise.
     */
    abstract protected function _canWpdbInsertBulk();

    /**
     * Retrieves a value from a container or data set.
     *
     * @since [*next-version*]
     *
     * @param array|ArrayAccess|stdClass|ContainerInterface $container The container to read from.
     * @param string|int|float|bool|Stringable              $key       The key of the value to retrieve.
     *
     * @throws InvalidArgumentException    If container is invalid.
     * @throws ContainerExceptionInterface If an error occurred while reading from the container.
     * @throws NotFoundExceptionInterface  If the key was not found in the container.
     *
     * @return mixed The value mapped to the given key.
     */
    abstract protected function _containerGet($container, $key);

    /**
     * Retrieves an entry from a container or data set.
     *
     * @since [*next-version*]
     *
     * @param array|ArrayAccess|stdClass|ContainerInterface $container The container to read from.
     * @param string|int|float|bool|Stringable              $key       The key of the value to retrieve.
     *
     * @throws ContainerExceptionInterface If an error occurred while reading from the container.
     * @throws OutOfRangeException         If the container or the key is invalid.
     *
     * @return bool True if the container has an entry for the given key, false if not.
     */
    abstract protected function _containerHas($container, $key);

    /**
     * Hashes a query value for use in WPDB queries when argument interpolating.
     *
     * @since [*next-version*]
     *
     * @param string $value    The value to hash.
     * @param int    $position The position of the value in the hash map.
     *
     * @return string The string hash.
     */
    abstract protected function _getWpdbValueHashString($value, $position);

    /**
     * Builds an INSERT SQL query.
     *
     * @since [*next-version*]
     *
     * @param string|Stringable $table        The name of the table to insert into.
     * @param array|Traversable $columns      A list of columns names. The order is preserved in the built query.
     * @param array|Traversable $records      The list of record data containers.
     * @param array             $valueHashMap Optional map of value names and their hashes.
     *
     * @throws InvalidArgumentException If the row set is empty.
     *
     * @return string The built INSERT query.
     */
    abstract protected function _buildInsertSql($table, $columns, $records, array $valueHashMap = []);

    /**
     * Retrieves the SQL database table name for use in SQL INSERT queries.
     *
     * @since [*next-version*]
     *
     * @return string|Stringable The table.
     */
    abstract protected function _getSqlInsertTable();

    /**
     * Retrieves the names of the columns for use in SQL INSERT queries.
     *
     * @since [*next-version*]
     *
     * @return string[]|Stringable[] A list of column names.
     */
    abstract protected function _getSqlInsertColumnNames();

    /**
     * Retrieves the fields-to-columns mapping for use in INSERT SQL queries.
     *
     * @since [*next-version*]
     *
     * @return array|Traversable A map containing the field names as keys and the matching column names as values.
     */
    abstract protected function _getSqlInsertFieldColumnMap();

    /**
     * Executes a query using wpdb.
     *
     * @since [*next-version*]
     *
     * @param string|Stringable $query     The query to execute.
     * @param array             $inputArgs An array of arguments to use for interpolating placeholders in the query.
     *
     * @return array A list of associative arrays, each representing a single record.
     */
    abstract protected function _executeWpdbQuery($query, array $inputArgs = []);

    /**
     * Normalizes a value to its string representation.
     *
     * The values that can be normalized are any scalar values, as well as
     * {@see StringableInterface).
     *
     * @since [*next-version*]
     *
     * @param string|int|float|bool|Stringable $subject The value to normalize to string.
     *
     * @throws InvalidArgumentException If the value cannot be normalized.
     *
     * @return string The string that resulted from normalization.
     */
    abstract protected function _normalizeString($subject);
}