RebelCode/sql-cqrs-resource-models-abstract

View on GitHub
src/BuildSelectSqlCapableTrait.php

Summary

Maintainability
A
2 hrs
Test Coverage
<?php

namespace RebelCode\Storage\Resource\Sql;

use Dhii\Exception\InternalExceptionInterface;
use Dhii\Expression\LogicalExpressionInterface;
use Dhii\Expression\TermInterface;
use Dhii\Storage\Resource\Sql\EntityFieldInterface;
use Dhii\Storage\Resource\Sql\OrderInterface;
use Dhii\Util\String\StringableInterface as Stringable;
use Exception as RootException;
use InvalidArgumentException;
use OutOfRangeException;
use stdClass;
use Traversable;

/**
 * Common functionality for objects that can build SQL SELECT queries.
 *
 * @since [*next-version*]
 */
trait BuildSelectSqlCapableTrait
{
    /**
     * Builds a SELECT SQL query.
     *
     * @since [*next-version*]
     *
     * @param array|stdClass|Traversable                                        $columns  The columns, as a map of
     *                                                                                    aliases (as keys) mapping to
     *                                                                                    column names, expressions or
     *                                                                                    entity field instances.
     * @param array|stdClass|Traversable                                        $tables   A mapping of tables aliases
     *                                                                                    (keys) to their real names.
     * @param array|Traversable                                                 $joins    A list of JOIN logical
     *                                                                                    expressions, keyed by table
     *                                                                                    name.
     * @param LogicalExpressionInterface|null                                   $where    The WHERE logical expression
     *                                                                                    condition.
     * @param OrderInterface[]|Traversable|null                                 $ordering The ordering, as a list of
     *                                                                                    OrderInterface instances.
     * @param int|null                                                          $limit    The number of records to
     *                                                                                    limit the query to.
     * @param int|null                                                          $offset   The number of records to
     *                                                                                    offset by, zero-based.
     * @param string[]|Stringable[]|EntityFieldInterface[]|stdClass|Traversable $grouping A list of strings, stringable
     *                                                                                    objects or entity-field
     *                                                                                    instances.
     * @param array                                                             $hashmap  Optional map of value names
     *                                                                                    and their hashes.
     *
     * @throws InvalidArgumentException If an argument is invalid.
     * @throws OutOfRangeException      If the limit or offset are invalid numbers.
     *
     * @return string The built SQL query string.
     */
    protected function _buildSelectSql(
        $columns,
        $tables,
        $joins = [],
        LogicalExpressionInterface $where = null,
        $ordering = null,
        $limit = null,
        $offset = null,
        $grouping = [],
        array $hashmap = []
    ) {
        if ($this->_countIterable($tables) === 0) {
            throw $this->_createInvalidArgumentException(
                $this->__('No tables were given'),
                null,
                null,
                $tables
            );
        }

        $columnList = $this->_buildSqlColumnList($columns);
        $from = $this->_buildSqlFrom($tables);
        $rJoins = $this->_buildSqlJoins($joins, $hashmap);
        $rWhere = $this->_buildSqlWhereClause($where, $hashmap);
        $rGroup = $this->_buildSqlGroupByClause($grouping);
        $rHaving = '';

        if (!empty($rGroup)) {
            $rHaving = str_replace('WHERE', 'HAVING', $rWhere);
            $rWhere = '';
        }

        $sOrder = ($ordering !== null)
            ? $this->_buildSqlOrderBy($ordering)
            : '';
        $sLimit = ($limit !== null)
            ? $this->_buildSqlLimit($limit)
            : '';
        $sOffset = ($limit !== null && $offset !== null)
            ? $this->_buildSqlOffset($offset)
            : '';

        $parts = array_filter([$from, $rJoins, $rWhere, $rGroup, $rHaving, $sOrder, $sLimit, $sOffset], 'strlen');
        $tail = implode(' ', $parts);
        $query = sprintf(
            'SELECT %1$s %2$s;',
            $columnList,
            $tail
        );

        return $query;
    }

    /**
     * Builds the SQL column list.
     *
     * @since [*next-version*]
     *
     * @see   EntityFieldInterface
     * @see   TermInterface
     * @see   ExpressionInterface
     *
     * @param array|stdClass|Traversable $columns The columns, as a map of aliases (as keys) mapping to column names,
     *                                            expressions or entity field instances (as values).
     *
     * @return string The built SQL column list.
     */
    abstract protected function _buildSqlColumnList($columns);

    /**
     * Builds the SQL FROM section.
     *
     * @since [*next-version*]
     *
     * @param array|stdClass|Traversable $tables A mapping of tables aliases (keys) to their real names (values).
     *
     * @return string The build SQL table FROM section.
     */
    abstract protected function _buildSqlFrom($tables);

    /**
     * Builds an SQL JOIN clause from a list of join conditions.
     *
     * @since [*next-version*]
     *
     * @param LogicalExpressionInterface[]|Traversable $joinConditions A list of JOIN conditions, keyed by table name.
     * @param string[]|Stringable[]                    $valueHashMap   Optional mapping of term names to their hashes.
     *
     * @return string|string The built SQL JOIN clause.
     */
    abstract protected function _buildSqlJoins(array $joinConditions, array $valueHashMap = []);

    /**
     * Builds the SQL WHERE clause query string portion.
     *
     * @since [*next-version*]
     *
     * @param LogicalExpressionInterface|null $condition    Optional condition instance.
     * @param array                           $valueHashMap Optional map of value names and their hashes.
     *
     * @return string The SQL WHERE clause query portion.
     */
    abstract protected function _buildSqlWhereClause(
        LogicalExpressionInterface $condition = null,
        array $valueHashMap = []
    );

    /**
     * Builds the ORDER BY portion of a query from `OrderInterface` instances.
     *
     * @since [*next-version*]
     *
     * @param OrderInterface[]|Traversable $ordering The `OrderInterface` instances.
     *
     * @throws OutOfRangeException        If the argument contains an invalid element.
     * @throws InternalExceptionInterface If a problem occurred while trying to get the column name for a field name.
     *
     * @return string The built ORDER BY query portion string, or an empty string if an empty $orders list is given.
     */
    abstract protected function _buildSqlOrderBy($ordering);

    /**
     * Builds the LIMIT portion of an SQL query.
     *
     * @since [*next-version*]
     *
     * @param int $limit The number of records to limit to.
     *
     * @throws InvalidArgumentException If the argument is not a valid integer.
     * @throws OutOfRangeException      If the argument is a negative integer.
     *
     * @return string The built LIMIT query portion.
     */
    abstract protected function _buildSqlLimit($limit = null);

    /**
     * Builds the OFFSET portion of an SQL query.
     *
     * @since [*next-version*]
     *
     * @param int $offset The number of records to offset by.
     *
     * @throws InvalidArgumentException If the argument is not a valid integer.
     * @throws OutOfRangeException      If the argument is a negative integer.
     *
     * @return string The built OFFSET query portion.
     */
    abstract protected function _buildSqlOffset($offset = null);

    /**
     * Builds the GROUP BY portion of the an SQL query.
     *
     * @since [*next-version*]
     *
     * @param string[]|Stringable[]|EntityFieldInterface[]|stdClass|Traversable $grouping A list of strings, stringable
     *                                                                                    objects or entity-field
     *                                                                                    instances.
     *
     * @return string The built GROUP BY query portion.
     *
     * @throws InvalidArgumentException   If the argument is not a valid iterable.
     * @throws OutOfRangeException        If an element in the iterable is invalid.
     * @throws InternalExceptionInterface If a problem occurred while trying to retrieve a column name.
     */
    abstract protected function _buildSqlGroupByClause($grouping = []);

    /**
     * Counts the elements in an iterable.
     *
     * @since [*next-version*]
     *
     * @param array|stdClass|Traversable $iterable The iterable to count. Must be finite.
     *
     * @return int The amount of elements.
     */
    abstract protected function _countIterable($iterable);

    /**
     * Creates a new Dhii invalid argument exception.
     *
     * @since [*next-version*]
     *
     * @param string|Stringable|null $message  The error message, if any.
     * @param int|null               $code     The error code, if any.
     * @param RootException|null     $previous The inner exception for chaining, if any.
     * @param mixed|null             $argument The invalid argument, if any.
     *
     * @return InvalidArgumentException The new exception.
     */
    abstract protected function _createInvalidArgumentException(
        $message = null,
        $code = null,
        RootException $previous = null,
        $argument = null
    );

    /**
     * Translates a string, and replaces placeholders.
     *
     * @since [*next-version*]
     * @see   sprintf()
     *
     * @param string $string  The format string to translate.
     * @param array  $args    Placeholder values to replace in the string.
     * @param mixed  $context The context for translation.
     *
     * @return string The translated string.
     */
    abstract protected function __($string, $args = [], $context = null);
}