bit-mx / data-entities
Requires
- php: ^8.2
- fakerphp/faker: ^1.23
- illuminate/database: ^10.0 || ^11.0
- illuminate/support: ^10.0 || ^11.0
Requires (Dev)
- larastan/larastan: ^2.9
- laravel/pint: ^1.15
- orchestra/testbench: ^8.0|^9.0
- pestphp/pest: ^2.34
- pestphp/pest-plugin-laravel: ^2.3
- phpstan/extension-installer: ^1.3
- symfony/var-dumper: ^7.1
- tomasvotruba/type-coverage: ^0.2.5
README
在不使用所有样板代码的情况下,从Sqlserver中执行Laravel存储过程。
目录
简介
数据实体是一个库,允许您轻松地在Sqlserver中执行存储过程。它是Laravel的DB Facade的包装器。
安装
您可以通过composer安装此包
composer require bit-mx/data-entities
设置
您需要发布配置文件来设置连接名称。
php artisan vendor:publish --provider="BitMx\DataEntities\DataEntitiesServiceProvider" --tag="config"
此命令将在config
目录中创建一个新的配置文件。
return [ 'database' => env('DATA_ENTITIES_CONNECTION', 'sqlsrv'), ];
兼容性
此包与Laravel 10.x及以上版本兼容。
由于laravel 11需要php 8.2,此包与php 8.2及以上版本兼容。
入门
创建数据实体
要创建数据实体,您需要扩展DataEntity类,并实现resolveStoreProcedure方法,其中包含您要执行的存储过程的名称。
您还可以重写defaultParameters方法来设置存储过程的默认参数。
namespace App\DataEntities; use DataEntities\DataEntity; use BitMx\DataEntities\Enums\Method; use BitMx\DataEntities\Enums\ResponseType; use BitMx\DataEntities\Responses\Response; use Illuminate\Support\Collection; class GetAllPostsDataEntity extends DataEntity { protected ?ResponseType $responseType = ResponseType::SINGLE; public function __construct( protected int $authorId, ) { } #[\Override] public function resolveStoreProcedure(): string { return 'spListAllPost'; } #[\Override] public function defaultParameters(): array { return [ 'author_id' => $this->authorId, ]; } }
您还可以使用parameters方法来设置存储过程的参数。
use App\DataEntities\GetAllPostsDataEntity; $dataEntity = new GetAllPostsDataEntity(1); $dataEntity->parameters()->add('tag', 'laravel');
ResponseType枚举有两个选项:SINGLE和COLLECTION。
SINGLE用于存储过程返回单行时,COLLECTION用于存储过程返回多行时。
您可以使用artisan命令创建新的数据实体
php artisan make:data-entity GetAllPostsDataEntity
此命令将在app/DataEntities
目录中创建新的数据实体。
连接
您可以通过重写resolveDatabaseConnection方法来设置连接名称。
namespace App\DataEntities; use DataEntities\DataEntity; use BitMx\DataEntities\Enums\Method; use BitMx\DataEntities\Enums\ResponseType; class GetAllPostsDataEntity extends DataEntity { ... #[\Override] public function resolveDatabaseConnection(): string { return 'sqlsrv'; } }
执行数据实体
要执行数据实体,您需要在数据实体实例上调用execute方法。
use App\DataEntities\GetAllPostsDataEntity; $dataEntity = new GetAllPostsDataEntity(1); $response = $dataEntity->execute(); $data = $response->data();
execute方法返回一个包含存储过程返回数据的Response对象。
修改器
您可以使用mutators方法在将参数发送到存储过程之前对其进行转换。
namespace App\DataEntities; use Carbon\Carbon;use DataEntities\DataEntity; use BitMx\DataEntities\Enums\Method; use BitMx\DataEntities\Enums\ResponseType; class GetAllPostsDataEntity extends DataEntity { ... #[\Override] public function defaultParameters(): array { return [ 'date' => Carbon::now(), ]; } /** * @return array<string, string> */ #[\Override] protected function mutators(): array { return [ 'date' => 'datetime:Y-m-d H:i', ]; } }
这将把日期参数转换为格式化的日期字符串,然后再发送到存储过程。
可用的修改器
-
datetime: : 使用指定的格式将值转换为日期时间字符串。您可以将格式作为参数传递给cast。示例
datetime
返回 Y-m-d H:i:sdatetime:Y-m-d
datetime:H:i:s
datetime:Y-m-d H:i:s
-
date: : 将值转换为日期
Y-m-d
-
bool: : 将值转换为整数形式的布尔值。示例:如果值为true,则将其转换为1,如果为false,则转换为0。
-
int: : 将值转换为整数。
-
float: : 将值转换为浮点数。您可以将小数位数作为参数传递给cast。示例
- `float` Returns a float with 2 decimals. - `float:4` Returns a float with 4 decimals. - `float:0` Returns an integer.
-
字符串: : 将值转换为字符串。
-
JSON: : 将值转换为JSON字符串。 : 示例
: - 如果传入数组,则将其转换为JSON字符串。
- [1, 2,4] 将被转换为 "[1,2,4]"
- ['name' => 'John'] 将被转换为 '{"name":"John"}'
- 可以将JSON选项作为参数传递给cast。
'json:'. JSON_PRETTY_PRINT
将返回具有JSON_PRETTY_PRINT选项的JSON字符串。
自定义修改器
可以通过实现Mutable接口来创建自定义转换器。
namespace BitMx\DataEntities\Mutators; use BitMx\DataEntities\Contracts\Mutable; class CustomMutator implements Mutable { /** * {@inheritDoc} */ public function transform(string $key, mixed $value, array $parameters): mixed { } }
可以使用Artisan命令创建新的cast。
php artisan make:data-entity-mutator CustomMutator
访问器
可以使用访问器方法转换存储过程返回的数据。
namespace App\DataEntities; use Carbon\Carbon;use DataEntities\DataEntity; use BitMx\DataEntities\Enums\Method; use BitMx\DataEntities\Enums\ResponseType; class GetAllPostsDataEntity extends DataEntity { ... #[\Override] public function defaultParameters(): array { return [ 'date' => Carbon::now(), ]; } /** * @return array<string, string> */ #[\Override] protected function accessors(): array { return [ 'contact_id' => 'integer', ]; } }
这将在返回数据之前将contact_id键转换为整数。
可用的访问器
-
datetime: : 将值转换为DateTime实例。
-
datetime_immutable: : 将值转换为DateTimeImmutable实例。
-
bool: : 将值转换为布尔值 示例:如果值为1,则转换为true
-
int: : 将值转换为整数。
-
float: : 将值转换为浮点数
-
字符串: : 将值转换为字符串。
-
array: : 将值从JSON字符串转换为数组。
- object: : 将值从JSON字符串转换为对象。
-
collection: : 将值从JSON字符串转换为Laravel Collection。
自定义访问器
可以通过实现Accessable接口来创建自定义访问器。
namespace BitMx\DataEntities\Accessors; use BitMx\DataEntities\Contracts\Accessable; class CustomAccessor implements Accessable { /** * {@inheritDoc} */ public function get(string $key, mixed $value, array $data): mixed { } }
可以使用Artisan命令创建新的访问器。
php artisan make:data-entity-accessor CustomAccessor
响应有用方法
响应对象有一些有用的方法来处理存储过程返回的数据。
data
data方法返回存储过程返回的数据作为数组。
$data = $response->data();
带键的数据
可以使用键获取数据
$data = $response->data('key');
带键和默认值的数据
可以使用键和默认值获取数据
$data = $response ->data('key', 'default value');
添加数据值
可以向数据数组添加值
$response->addData('key', 'value');
可以向数据数组添加数组
$response->addData(['key' => 'value']);
合并数据
可以将数组与数据数组合并
$response->mergeData(['key' => 'value']);
作为对象
可以获取数据作为对象
$data = $response->object();
作为集合
可以获取数据作为集合
$data = $response->collect();
成功
success方法在存储过程执行成功时返回true,否则返回false。
if ($response->success()) { // The stored procedure was executed successfully } else { // There was an error executing the stored procedure }
失败
fail方法在存储过程失败时返回true,否则返回false。
if ($response->failed()) { // There was an error executing the stored procedure } else { // The stored procedure was executed successfully }
抛出
默认情况下,响应对象在存储过程失败时不会抛出异常。您可以使用throw方法手动抛出异常。
$response->throw();
引导
可以使用boot方法在存储过程执行前后执行代码。
namespace App\DataEntities; use BitMx\DataEntities\PendingQuery; use DataEntities\DataEntity; use BitMx\DataEntities\Enums\Method; use BitMx\DataEntities\Enums\ResponseType; use BitMx\DataEntities\Responses\Response; use Illuminate\Support\Collection; class GetAllPostsDataEntity extends DataEntity { protected ?ResponseType $responseType = ResponseType::SINGLE; ... #[\Override] public function boot(PendingQuery $pendingQuery): void { $pendingQuery->parameters()->all('tag', 'laravel'); } }
特性
您可以使用特性向您的数据实体添加功能。将bootTrait方法添加到数据实体中以使用特性。
trait Taggable { public function bootTaggable(PendingQuery $pendingQuery): void { $pendingQuery->parameters()->add('tag', 'laravel'); } }
bootTaggable方法将在执行存储过程之前被调用。
中间件
您可以使用中间件在执行存储过程前后执行代码。
namespace App\DataEntities; use BitMx\DataEntities\PendingQuery; use DataEntities\DataEntity; use BitMx\DataEntities\Enums\ResponseType; use BitMx\DataEntities\Responses\Response; use Illuminate\Support\Collection; class GetAllPostsDataEntity extends DataEntity { protected ?ResponseType $responseType = ResponseType::SINGLE; ... #[\Override] public function boot(PendingQuery $pendingQuery): void { $pendingQuery->middleware()->onQuery(function (PendingQuery $pendingQuery) { $pendingQuery->parameters()->add('tag', 'laravel'); }); $pendingQuery->middleware()->onResponse(function (Response $response) { $response->addData('tag', 'laravel'); return $response; }); } }
您还可以使用可调用的类作为中间件。此类应实现QueryMiddleware或ResponseMiddleware接口。
use BitMx\DataEntities\Contracts\QueryMiddleware; class PageMiddleware implements QueryMiddleware { public function __invoke(PendingQuery $pendingQuery): PendingQuery { $pendingQuery->parameters()->add('page', 1); return $pendingQuery; } }
use BitMx\DataEntities\Contracts\ResponseMiddleware; use BitMx\DataEntities\Responses\Response; class TagMiddleware implements ResponseMiddleware { public function __invoke(Response $pendingQuery): Response { $response->addData('tag', 'laravel'); return $response; } }
namespace App\DataEntities; use BitMx\DataEntities\PendingQuery; use DataEntities\DataEntity; use BitMx\DataEntities\Enums\ResponseType; use BitMx\DataEntities\Responses\Response; use Illuminate\Support\Collection; class GetAllPostsDataEntity extends DataEntity { protected ?ResponseType $responseType = ResponseType::SINGLE; ... #[\Override] public function boot(PendingQuery $pendingQuery): void { $pendingQuery->middleware()->onQuery(new PageMiddleware()); $pendingQuery->middleware()->onResponse(new TagMiddleware()); } }
插件
您可以使用插件向您的数据实体添加功能。
AlwaysThrowOnError
AlwaysThrowOnError插件将在存储过程失败时抛出异常。
namespace App\DataEntities; use BitMx\DataEntities\PendingQuery; use BitMx\DataEntities\Plugins\AlwaysThrowOnError; use DataEntities\DataEntity; use BitMx\DataEntities\Enums\Method; use BitMx\DataEntities\Enums\ResponseType; use BitMx\DataEntities\Responses\Response; use Illuminate\Support\Collection; class GetAllPostsDataEntity extends DataEntity { use AlwaysThrowOnError; protected ?Method $method = Method::SELECT; protected ?ResponseType $responseType = ResponseType::SINGLE; ... }
HasCache
HasCache插件将缓存存储过程返回的数据。
数据实体应实现Cacheable接口。
namespace App\DataEntities; use BitMx\DataEntities\Contracts\Cacheable; use BitMx\DataEntities\PendingQuery; use BitMx\DataEntities\Plugins\AlwaysThrowOnError; use BitMx\DataEntities\Plugins\HasCache;use DataEntities\DataEntity; use BitMx\DataEntities\Enums\Method; use BitMx\DataEntities\Enums\ResponseType; use BitMx\DataEntities\Responses\Response; use Illuminate\Support\Collection; class GetAllPostsDataEntity extends DataEntity implements Cacheable { use HasCache; protected ?ResponseType $responseType = ResponseType::SINGLE; ... public function cacheExpiresAt(): \DateTimeInterface { return now()->addMinutes(10); } }
您可以使用invalidateCache方法清除缓存。
use App\DataEntities\GetPostDataEntity; $dataEntity = new GetPostDataEntity(1); $post = $response->invalidateCache(); $response = $dataEntity->execute();
或者,您可以使用disableCaching方法暂时禁用缓存。
use App\DataEntities\GetPostDataEntity; $dataEntity = new GetPostDataEntity(1); $post = $response->disableCaching(); $response = $dataEntity->execute();
响应对象将有一个isCached方法来检查数据是否被缓存。
use App\DataEntities\GetPostDataEntity; $dataEntity = new GetPostDataEntity(1); $post = $response->disableCaching(); $response = $dataEntity->execute(); $response->isCached(); //
数据传输对象
您可以使用数据传输对象(DTO)将存储过程返回的数据映射到PHP对象。
namespace App\Data; class PostDat { public function __construct( public int $id, public string $title, public string $content, ) { } }
namespace App\DataEntities; use DataEntities\DataEntity; use BitMx\DataEntities\Enums\Method; use BitMx\DataEntities\Enums\ResponseType; use BitMx\DataEntities\Responses\Response; use Illuminate\Support\Collection; use App\Data\PostData; class GetPostDataEntity extends DataEntity { protected ?Method $method = Method::SELECT; protected ?ResponseType $responseType = ResponseType::SINGLE; public function __construct( protected int $postId, ) { } #[\Override] public function resolveStoreProcedure(): string { return 'spListPost'; } #[\Override] public function defaultParameters(): array { return [ 'post_is' => $this->postId, ]; } public function createDtoFromResponse(Response $response): PostData { $data = $response->getData(); return new PostData( id: $data['id'], title: $data['title'], content: $data['content'], ); } }
您可以使用dto方法从响应中获取DTO。
use App\DataEntities\GetPostDataEntity; $dataEntity = new GetPostDataEntity(1); $response = $dataEntity->execute(); /** @var PostData $post */ $post = $response->dto();
调试
您可以使用dd和ddRaw方法来调试发送到数据库的查询。
use App\DataEntities\GetPostDataEntity; $dataEntity = new GetPostDataEntity(1); $dataEntity->dd(); $dataEntity->ddRaw();
测试
您可以轻松地为数据实体创建集成测试。
模拟数据实体
您可以使用 DataEntity::fake 方法模拟数据实体。
use App\DataEntities\GetPostDataEntity; use BitMx\DataEntities\DataEntity; use BitMx\DataEntities\Responses\MockResponse; it('should get the post', function () { DataEntity::fake([ GetPostDataEntity::class => MockResponse::make([ 'id' => 1, 'title' => 'Post title', 'content' => 'Post content', ]), ]); $dataEntity = new GetPostDataEntity(1); $response = $dataEntity->execute(); $post = $response->dto(); expect($post->id)->toBe(1); expect($post->title)->toBe('Post title'); expect($post->content)->toBe('Post content'); });
在使用 fake 方法时,execute 方法将返回 MockResponse::make 方法中指定的数据,而不会执行存储过程。
断言
您可以使用 assert 方法断言数据实体已被执行。
use App\DataEntities\GetPostDataEntity; use BitMx\DataEntities\DataEntity; use BitMx\DataEntities\Responses\MockResponse; it('should get the post', function () { DataEntity::fake([ GetPostDataEntity::class => MockResponse::make([ 'id' => 1, 'title' => 'Post title', 'content' => 'Post content', ]), ]); $dataEntity = new GetPostDataEntity(1); $response = $dataEntity->execute(); $post = $response->dto(); DataEntity::assertExecuted(GetPostDataEntity::class); });
断言
您可以使用以下断言:
- assertExecuted: 断言数据实体已被执行。
- assertNotExecuted: 断言数据实体未被执行。
- assertExecutedCount: 断言数据实体执行了特定次数。
- assertExecutedOnce: 断言数据实体只执行了一次。
使用工厂
您可以使用工厂为数据实体创建模拟数据。
namespace Tests\DataEntityFactories; use BitMx\DataEntities\Factories\DataEntityFactory; class PostDataEntityFactory extends DataEntityFactory { /** * {@inheritDoc} */ public function definition(): array { return [ 'id' => $this->faker->unique()->randomNumber(), 'title' => $this->faker->sentence(), 'content' => $this->faker->paragraph(), ]; } }
要创建一个工厂,您应该扩展 DataEntityFactory 类并实现定义方法。
您可以使用 faker 属性生成模拟数据。
use App\DataEntities\GetPostDataEntity; use Tests\DataEntityFactories\PostDataEntityFactory; use BitMx\DataEntities\Responses\MockResponse; it('should get the post', function () { $dataEntity = MockResponse::make(PostDataEntityFactory::new()); $response = $dataEntity->execute(); $post = $response->dto(); expect($post->id)->toBe(1); expect($post->title)->toBe('Post title'); expect($post->content)->toBe('Post content'); });
您可以直接将工厂传递给 MockResponse::make 方法。或者,您可以使用 create 方法创建一个数组。
use App\DataEntities\GetPostDataEntity; use Tests\DataEntityFactories\PostDataEntityFactory; use BitMx\DataEntities\Responses\MockResponse; it('should get the post', function () { $dataEntity = MockResponse::make(PostDataEntityFactory::new()->create()); $response = $dataEntity->execute(); $post = $response->dto(); expect($post->id)->toBe(1); expect($post->title)->toBe('Post title'); expect($post->content)->toBe('Post content'); });
您还可以使用 count 方法创建一个模拟数据的数组。
use App\DataEntities\GetPostDataEntity; use Tests\DataEntityFactories\PostDataEntityFactory; use BitMx\DataEntities\Responses\MockResponse; it('should get the post', function () { $dataEntity = MockResponse::make(PostDataEntityFactory::new()->count(10)); $response = $dataEntity->execute(); $posts = $response->dto(); expect($posts)->toHaveCount(10); });
您可以使用 state 方法更改工厂的默认值。
use App\DataEntities\GetPostDataEntity; use Tests\DataEntityFactories\PostDataEntityFactory; use BitMx\DataEntities\Responses\MockResponse; it('should get the post', function () { $dataEntity = MockResponse::make(PostDataEntityFactory::new()->state([ 'title' => 'Custom title', ])); $response = $dataEntity->execute(); $post = $response->dto(); expect($post->title)->toBe('Custom title'); });
或者在工厂中创建一个新方法来更改默认值。
namespace Tests\DataEntityFactories; use BitMx\DataEntities\Factories\DataEntityFactory; class PostDataEntityFactory extends DataEntityFactory { /** * {@inheritDoc} */ public function definition(): array { return [ 'id' => $this->faker->unique()->randomNumber(), 'title' => $this->faker->sentence(), 'content' => $this->faker->paragraph(), ]; } public function withPublishedDate(array $state): DataEntityFactory { return $this->state([ 'published_date' => now(), ]); } }
use App\DataEntities\GetPostDataEntity; use Tests\DataEntityFactories\PostDataEntityFactory; use BitMx\DataEntities\Responses\MockResponse; it('should get the post', function () { $dataEntity = MockResponse::make(PostDataEntityFactory::new()->withPublishedDate()); $response = $dataEntity->execute(); $post = $response->dto(); expect($post->published_date)->toBe(now()); });
您可以使用异常创建一个模拟。
use App\DataEntities\GetPostDataEntity; use Tests\DataEntityFactories\PostDataEntityFactory; use BitMx\DataEntities\Responses\MockResponse; it('should get the post', function () { $dataEntity = MockResponse::makeWithException(new \Exception('Error')); $response = $dataEntity->execute(); }) ->throws(\Exception::class, 'Error');
响应类型
您可以使用 responseType 方法设置响应类型。
namespace Tests\DataEntityFactories; use BitMx\DataEntities\Enums\ResponseType;use BitMx\DataEntities\Factories\DataEntityFactory; class PostDataEntityFactory extends DataEntityFactory { /** * {@inheritDoc} */ public function definition(): array { return [ 'id' => $this->faker->unique()->randomNumber(), 'title' => $this->faker->sentence(), 'content' => $this->faker->paragraph(), ]; } public function responseType() : ResponseType{ return ResponseType::COLLECTION; } }
您可以在 MockResponse 上更改响应类型。
use App\DataEntities\GetPostDataEntity; use Tests\DataEntityFactories\PostDataEntityFactory; use BitMx\DataEntities\Responses\MockResponse; it('should get the post', function () { $dataEntity = MockResponse::make(PostDataEntityFactory::new()->asCollection()); $response = $dataEntity->execute(); .... });
您可以使用 artisan 命令创建一个新的工厂。
php artisan make:data-entity-factory PostDataEntityFactory
此命令将在 tests/DataEntityFactories
目录中创建一个新的工厂。