headio / phalcon-service-layer
Phalcon项目的服务层实现
Requires
- php: >=8.0.0 <8.0.99
- ext-mbstring: *
- ext-phalcon: >=5.1.4
Requires (Dev)
- codeception/codeception: ^4.1.0
- codeception/module-asserts: ^1.1
- codeception/module-phalcon5: ^1.0.0
- codeception/specify: ^1.4
- codeception/verify: ^1.5
- friendsofphp/php-cs-fixer: *
- headio/phalcon-bootstrap: 5.x
- mockery/mockery: ^1.3
- monolog/monolog: ^2.1.0
- phalcon/ide-stubs: ^5.1.4
- vimeo/psalm: ^4.14
- vlucas/phpdotenv: ^5.1
Suggests
- ext-memcached: Required when the cache manager is configured to use libmemcached.
- ext-redis: Required when the cache manager is configured to use the redis cache.
This package is auto-updated.
Last update: 2024-09-11 14:16:11 UTC
README
为Phalcon 5项目提供简单的仓库服务实现
介绍
此库提供了一种分层架构,可以更容易地进行单元和集成测试。
服务层处理业务逻辑,在应用层(控制器或处理器)和领域之间进行调解,与单个仓库或多个仓库进行交互。所有仓库都扩展了一个抽象的查询仓库,提供了一个类似集合的接口,具有定义良好的查询方法。因此,所有查询都被隔离在仓库层。
Phalcon ORM实现了活动记录模式,因此持久性的责任仍然由活动记录承担,这与仓库服务模式/数据映射(Doctrine)不同,在仓库服务模式/数据映射中,仓库管理实体生命周期。
如果你在字里行间阅读,你可能已经了解到这是一个提供测试性、可重用性和防止逻辑泄漏到应用层的混合解决方案。权衡是您需要编写、测试和维护一些额外的样板代码。
当然,您可以通过将数据映射器(Doctrine、Atlas ORM等)与Phalcon集成来避免这种范式。然而,对于那些喜欢Phalcon ORM性能的人来说,这个库可能很有兴趣。
依赖
- PHP >=8.0.0 <=8.0.99
- Phalcon 5.0.0+
安装
Composer
打开一个终端窗口并运行
composer require headio/phalcon-service-layer
用法
假设以下项目结构,让我们创建处理从存储中删除记录的层,作为一个简单的使用示例。
├── src │ │── Module │ │ │── Admin │ │ │ │── Controller │ │ │ │ │── Foo │ │ │ │── Module.php │ │── Domain │ │ │── Model │ │ │ │── Foo │ │ │── Repository │ │ │ │── Foo │ │ │── Service │ │ │ │── Foo │ │── Provider │ │ │── FooService └──
注册服务提供者
在服务提供者目录/src/Provider/内创建一个新的Foo服务依赖。
declare(strict_types=1); namespace App\Service; use App\Domain\Repository\Foo as Repository; use App\Domain\Service\Foo as Service; use Headio\Phalcon\ServiceLayer\Repository\Factory; use Phalcon\Di\ServiceProviderInterface; use Phalcon\Di\DiInterface; class Foo implements ServiceProviderInterface { public function register(DiInterface $di): void { $di->setShared( 'fooService', function () { $repository = Factory::create(Repository::class) $service = new Service($repository); return $service; } ); } }
或者,基于每个模块创建依赖。
declare(strict_types=1); namespace App\Module\Admin; use App\Domain\Repository\Foo as Repository; use App\Domain\Service\Foo as Service; use Headio\Phalcon\ServiceLayer\Repository\Factory; use Phalcon\Di\DiInterface; use Phalcon\Loader; use Phalcon\Mvc\ModuleDefinitionInterface; class Module implements ModuleDefinitionInterface { /** * {@inheritDoc} */ public function registerAutoloaders(DiInterface $container = null) { } /** * {@inheritDoc} */ public function registerServices(DiInterface $container) { $container->setShared( 'fooService', function () use ($container) { $repository = Factory::create(Repository::class); $service = new Service($repository); return $service; } ); } }
控制器/处理器
现在服务已经就位,控制器可以通过通过将服务注入控制器中的OnConstruct方法与应用层服务层进行交互。
namespace App\Module\Admin\Foo; use Phalcon\Mvc\Controller; class Foo extends Controller { private FooInterface $service; /** * Inject service layer dependencies */ public function onConstruct(): void { $this->service = $this->getDI()->get('fooService'); } }
服务层
服务层与一个仓库(或多个仓库)交互,以处理业务逻辑。在下面的示例中,服务调用删除方法(为了简单起见省略了实现)通过主键删除模型实例并返回到列表视图。
declare(strict_types=1); namespace App\Domain\Service\Foo; use App\Domain\Repository\FooInterface; use App\Domain\Service\FooInterface as ServiceInterface; use Phalcon\Di\Injectable; use Phalcon\Http\ResponseInterface; class Foo extends Injectable implements ServiceInterface { public function __construct(private FooInterface $repository) { } /** * Delete a model instance */ public function deleteModel(int $id): ResponseInterface { $model = $this->repository->findByPk($id); if ($this->delete($model)) { $this->flashSession->notice('Task completed'); return $this->response->redirect(['for' => 'adminFoos']); } } }
仓库
所有仓库都必须扩展抽象查询仓库并实现一个抽象方法。
declare(strict_types=1); namespace App\Domain\Repository; use App\Domain\Model\Foo as Model; use App\Domain\Repository\FooInterface; use Headio\Phalcon\ServiceLayer\Repository\QueryRepository; class Foo extends QueryRepository implements FooInterface { /** * Return the model name managed by this repository. */ protected function getModelName(): string { return Model::class; } }
“Foo”仓库可以实现额外的接口,例如“FooInterface”,为服务层提供更多的具体方法。
抽象查询仓库实现了以下仓库接口
declare(strict_types=1); namespace Headio\Phalcon\Repository\Repository; use Headio\Phalcon\ServiceLayer\Model\CriteriaInterface; use Headio\Phalcon\ServiceLayer\Model\ModelInterface; use Phalcon\Mvc\Model\ResultsetInterface; use Phalcon\Mvc\Model\Query\BuilderInterface; interface RepositoryInterface { /** * Return an instance of the query criteria pre-populated * with the model managed by this repository. */ public function createCriteria(): CriteriaInterface; /** * Return an instance of the query builder. */ public function createBuilder(array $params = null): BuilderInterface; /** * Fetch a column value by query criteria from storage. */ public function fetchColumn(CriteriaInterface $criteria): mixed; /** * Fetch records by query criteria from storage. */ public function find(CriteriaInterface $criteria): ResultsetInterface; /** * Fetch record by primary key from storage. */ public function findByPk(int $id): ModelInterface; /** * Fetch first record by query criteria from storage. */ public function findFirst(CriteriaInterface $criteria): ModelInterface; /** * Fetch first record by property name from storage. */ public function findFirstBy(string $property, mixed $value): ModelInterface; /** * Return the fully qualified (or unqualified) class name * for the model managed by this repository. */ public function getModel(bool $unqualified = false): string; }
此外,还提供了一个关系特性,用于简化处理模型关系。
declare(strict_types=1); namespace App\Domain\Repository; use App\Domain\Model\Foo as Model; use App\Domain\Repository\FooInterface; use Headio\Phalcon\ServiceLayer\Repository\QueryRepository; use Headio\Phalcon\ServiceLayer\Repository\Traits\RelationshipTrait; class Foo extends QueryRepository implements FooInterface { use RelationshipTrait; /** * Return the model name managed by this repository. */ protected function getModelName(): string { return Model::class; } }
查询缓存
查询缓存是通过Phalcon的事件管理器处理的。要开始,首先在您的仓库中包含一个CacheableTrait;在缓存特性中实现了EventsAwareInterface。
declare(strict_types=1); namespace App\Domain\Repository; use App\Domain\Model\User as Model; use Headio\Phalcon\ServiceLayer\Model\ModelInterface; use Headio\Phalcon\ServiceLayer\Repository\QueryRepository; use Headio\Phalcon\ServiceLayer\Repository\Traits\CacheableTrait; use Phalcon\Events\EventsAwareInterface; class User extends QueryRepository implements UserInterface, EventsAwareInterface { use CacheableTrait; /** * Return the model name managed by this repository. */ protected function getModelName(): string { return Model::class; } }
然后为您的服务层创建一个服务提供者,或者如果您想省略服务层并直接与仓库一起工作,则创建一个仓库。下面的示例使用了Phalcon的服务提供者接口。
declare(strict_types=1); namespace App\Service; use App\Domain\Repository\Foo as Repository; use App\Domain\Service\Foo as Service; use Headio\Phalcon\ServiceLayer\Cache\Listener\CacheListener; use Headio\Phalcon\ServiceLayer\Repository\Factory; use Phalcon\Di\ServiceProviderInterface; use Phalcon\Di\DiInterface; class Foo implements ServiceProviderInterface { public function register(DiInterface $di): void { $di->setShared( 'fooService', function () { $eventsManager = new EventsManager(); // factory instantiation $repository = Factory::create(Repository::class); $repository->setEventsManager($eventsManager); $cacheManager = $this->get('cacheManager'); // attach the cache event listener and inject the // cache manager dependency $eventsManager->attach( 'cache', new CacheListener( $cacheManager ) ); $service = new Service($repository); return $service; } ); } }
缓存事件监听器
事件监听器提供了两个方法来处理缓存,请参阅以下内容。
/** * This event listener provides caching functionality for repositories. */ class CacheListener { public function __construct(private ManagerInterface $manager) { } /** * Appends a cache declaration to a Phalcon query instance. */ public function append( EventInterface $event, RepositoryInterface $repository, QueryInterface $query, ): QueryInterface; /** * Fetches data from cache or storage using the cache-aside * strategy. */ public function fetch( EventInterface $event, RepositoryInterface $repository, array $context, ): ModelInterface|ResultsetInterface; }
要触发一个缓存事件,请参阅以下来自缓存特性的具体示例。
trait CacheableTrait { /** * Fetch first record by query criteria from cache or storage. * * @throws NotFoundException */ public function findFirst(CriteriaInterface $criteria): ModelInterface { $query = $criteria ->createBuilder() ->getQuery() ->setUniqueRow(true) ; $this->eventsManager->fire('cache:append', $this, $query); $model = $query->execute(); if (!$model instanceof ModelInterface) { throw new NotFoundException('404 Not Found'); } return $model; } /** * Fetch data from cache or storage. */ public function fromCache( QueryInterface|array $query, Closure $callable, DateInterval|int $lifetime = null, ): ResultsetInterface|ModelInterface|null { $key = $this->cacheManager->generateKey( $this->getModel(), $query, ); return $this->eventsManager->fire( 'cache:fetch', $this, [$key, $callable], ); }
分页
此库提供了一个基于游标的分页适配器;请参阅测试目录中的_stub目录的使用方法。
模型
所有模型都必须扩展抽象类 Model,该类实现了以下模型接口
declare(strict_types=1); namespace Headio\Phalcon\ServiceLayer\Model; use Phalcon\Di\DiInterface; use Phalcon\Mvc\Model\CriteriaInterface; interface ModelInterface { /** * Return the model primary key attribute. */ public function getPrimaryKey(): string; /** * Return the property binding type for a given property. */ public function getPropertyBindType(string $property): int; /** * Return the model validation errors as an array representation. */ public function getValidationErrors(): array; /** * Return an instance of the query criteria pre-populated * with the model. */ public static function query(DiInterface $container = null): CriteriaInterface; }
验证
验证可以在服务层或模型类中实现。
测试
要查看测试,请运行
php vendor/bin/codecept run -f --coverage --coverage-xml
许可证
Phalcon服务层是开源的,并且根据MIT许可证授权。