Proper Repository Pattern Design in PHP?

后端 未结 11 777
天涯浪人
天涯浪人 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:56

    I use the following interfaces:

    • Repository - loads, inserts, updates and deletes entities
    • Selector - finds entities based on filters, in a repository
    • Filter - encapsulates the filtering logic

    My Repository is database agnostic; in fact it doesn't specify any persistence; it could be anything: SQL database, xml file, remote service, an alien from outer space etc. For searching capabilities, the Repository constructs an Selector which can be filtered, LIMIT-ed, sorted and counted. In the end, the selector fetches one or more Entities from the persistence.

    Here is some sample code:

    Then, one implementation:

    class SqlEntityRepository
    {
        ...
        public function factoryEntitySelector()
        {
            return new SqlSelector($this);
        }
        ...
    }
    
    class SqlSelector implements Selector
    {
        ...
        private function adaptFilter(Filter $filter):SqlQueryFilter
        {
             return (new SqlSelectorFilterAdapter())->adaptFilter($filter);
        }
        ...
    }
    class SqlSelectorFilterAdapter
    {
        public function adaptFilter(Filter $filter):SqlQueryFilter
        {
            $concreteClass = (new StringRebaser(
                'Filter\\', 'SqlQueryFilter\\'))
                ->rebase(get_class($filter));
    
            return new $concreteClass($filter);
        }
    }
    

    The ideea is that the generic Selector uses Filter but the implementation SqlSelector uses SqlFilter; the SqlSelectorFilterAdapter adapts a generic Filter to a concrete SqlFilter.

    The client code creates Filter objects (that are generic filters) but in the concrete implementation of the selector those filters are transformed in SQL filters.

    Other selector implementations, like InMemorySelector, transform from Filter to InMemoryFilter using their specific InMemorySelectorFilterAdapter; so, every selector implementation comes with its own filter adapter.

    Using this strategy my client code (in the bussines layer) doesn't care about a specific repository or selector implementation.

    /** @var Repository $repository*/
    $selector = $repository->factoryEntitySelector();
    $selector->filter(new AttributeEquals('activated', 1))->limit(2)->orderBy('username');
    $activatedUserCount = $selector->count(); // evaluates to 100, ignores the limit()
    $activatedUsers = $selector->fetchEntities();
    

    P.S. This is a simplification of my real code

提交回复
热议问题