Proper Repository Pattern Design in PHP?

后端 未结 11 772
天涯浪人
天涯浪人 2020-11-29 14:22

Preface: I\'m attempting to use the repository pattern in an MVC architecture with relational databases.

I\'ve recently started learning TDD in PHP, and I\'

11条回答
  •  清歌不尽
    2020-11-29 14:51

    I think graphQL is a good candidate in such a case to provide a large scale query language without increasing the complexity of data repositories.

    However, there's another solution if you don't want to go for the graphQL for now. By using a DTO where an object is used for carring the data between processes, in this case between the service/controller and the repository.

    An elegant answer is already provided above, however I'll try to give another example that I think it's simpler and could serve as a starting point for a new project.

    As shown in the code, we would need only 4 methods for CRUD operations. the find method would be used for listing and reading by passing object argument. Backend services could build the defined query object based on a URL query string or based on specific parameters.

    The query object (SomeQueryDto) could also implement specific interface if needed. and is easy to be extended later without adding complexity.

    getQueryBuilder();
    
            foreach ($query->getSearchParameters() as $attribute) {
                $qb->where($attribute['field'], $attribute['operator'], $attribute['value']);
            }
    
            return $qb->get();
        }
    }
    
    /**
     * Provide query data to search for tickets.
     *
     * @method SomeQueryDto userId(int $id, string $operator = null)
     * @method SomeQueryDto categoryId(int $id, string $operator = null)
     * @method SomeQueryDto completedAt(string $date, string $operator = null)
     */
    class SomeQueryDto
    {
        /** @var array  */
        const QUERYABLE_FIELDS = [
            'id',
            'subject',
            'user_id',
            'category_id',
            'created_at',
        ];
    
        /** @var array  */
        const STRING_DB_OPERATORS = [
            'eq' => '=', // Equal to
            'gt' => '>', // Greater than
            'lt' => '<', // Less than
            'gte' => '>=', // Greater than or equal to
            'lte' => '<=', // Less than or equal to
            'ne' => '<>', // Not equal to
            'like' => 'like', // Search similar text
            'in' => 'in', // one of range of values
        ];
    
        /**
         * @var array
         */
        private $searchParameters = [];
    
        const DEFAULT_OPERATOR = 'eq';
    
        /**
         * Build this query object out of query string.
         * ex: id=gt:10&id=lte:20&category_id=in:1,2,3
         */
        public static function buildFromString(string $queryString): SomeQueryDto
        {
            $query = new self();
            parse_str($queryString, $queryFields);
    
            foreach ($queryFields as $field => $operatorAndValue) {
                [$operator, $value] = explode(':', $operatorAndValue);
                $query->addParameter($field, $operator, $value);
            }
    
            return $query;
        }
    
        public function addParameter(string $field, string $operator, $value): SomeQueryDto
        {
            if (!in_array($field, self::QUERYABLE_FIELDS)) {
                throw new \Exception("$field is invalid query field.");
            }
            if (!array_key_exists($operator, self::STRING_DB_OPERATORS)) {
                throw new \Exception("$operator is invalid query operator.");
            }
            if (!is_scalar($value)) {
                throw new \Exception("$value is invalid query value.");
            }
    
            array_push(
                $this->searchParameters,
                [
                    'field' => $field,
                    'operator' => self::STRING_DB_OPERATORS[$operator],
                    'value' => $value
                ]
            );
    
            return $this;
        }
    
        public function __call($name, $arguments)
        {
            // camelCase to snake_case
            $field = strtolower(preg_replace('/(?addParameter($field, $arguments[1] ?? self::DEFAULT_OPERATOR, $arguments[0]);
            }
        }
    
        public function getSearchParameters()
        {
            return $this->searchParameters;
        }
    }
    

    Example usage:

    $query = new SomeEnitityQuery();
    $query->userId(1)->categoryId(2, 'ne')->createdAt('2020-03-03', 'lte');
    $entities = $someRepository->find($query);
    
    // Or by passing the HTTP query string
    $query = SomeEnitityQuery::buildFromString('created_at=gte:2020-01-01&category_id=in:1,2,3');
    $entities = $someRepository->find($query);
    

提交回复
热议问题