1. Go to this page and download the library: Download bit-mx/data-entities library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
namespace App\DataEntities;
use BitMx\DataEntities\DataEntity;
class GetAllPostsDataEntity extends DataEntity
{
public function __construct(
protected int $authorId,
) {
}
#[\Override]
public function resolveStoreProcedure(): string
{
return 'spListAllPost';
}
#[\Override]
protected function defaultParameters(): array
{
return [
'author_id' => $this->authorId,
];
}
}
public function author_id', 'status'];
}
use App\DataEntities\GetAllPostsDataEntity;
$dataEntity = new GetAllPostsDataEntity(1);
$dataEntity->parameters()->add('tag', 'laravel');
namespace App\DataEntities;
use BitMx\DataEntities\Attributes\SingleItemResponse;
use BitMx\DataEntities\DataEntity;
#[SingleItemResponse]
class GetPostDataEntity extends DataEntity
{
public function __construct(
protected int $postId,
) {
}
#[\Override]
public function resolveStoreProcedure(): string
{
return 'spListPost';
}
#[\Override]
protected function defaultParameters(): array
{
return [
'post_id' => $this->postId,
];
}
}
namespace App\DataEntities;
use BitMx\DataEntities\DataEntity;
class GetAllPostsDataEntity extends DataEntity
{
// ...
#[\Override]
public function resolveDatabaseConnection(): string|\Illuminate\Database\Connection
{
return 'sqlsrv';
}
}
namespace App\DataEntities\Erp;
use BitMx\DataEntities\DataEntity;
abstract class ErpDataEntity extends DataEntity
{
#[\Override]
public function resolveDatabaseConnection(): string|\Illuminate\Database\Connection
{
return 'erp_sqlsrv';
}
}
class GetCustomerDataEntity extends ErpDataEntity
{
public function resolveStoreProcedure(): string
{
return 'dbo.spGetCustomer';
}
}
namespace App\DataEntities\Crm;
use BitMx\DataEntities\DataEntity;
abstract class CrmDataEntity extends DataEntity
{
#[\Override]
public function resolveDatabaseConnection(): string|\Illuminate\Database\Connection
{
return 'crm_mysql';
}
}
use Illuminate\Database\Connection;
use Illuminate\Support\Facades\DB;
#[\Override]
public function resolveDatabaseConnection(): string|Connection
{
return DB::build([
'driver' => 'sqlsrv',
'host' => $this->host,
'port' => $this->port,
'database' => $this->database,
'username' => $this->username,
'password' => $this->password,
]);
}
use Illuminate\Support\Facades\DB;
(new GetCustomerDataEntity($id))
->onConnection(DB::build([
'driver' => 'mysql',
'host' => $host,
'database' => $database,
'username' => $user,
'password' => $password,
]))
->execute();
(new GetCustomerDataEntity($id))
->onConnection('tenant_mysql') // connection name from config/database.php
->execute();
public function queryTimeout(): ?int
{
return 30;
}
use BitMx\DataEntities\DataEntity;
use Illuminate\Support\Facades\DB;
DB::connection('sqlsrv')->transaction(function () {
(new CreateOrderDataEntity($payload))->execute();
(new ReserveInventoryDataEntity($orderId))->execute();
});
DataEntity::transaction(function () {
(new CreateOrderDataEntity($payload))->execute();
(new ReserveInventoryDataEntity($orderId))->execute();
}, connection: 'sqlsrv');
DataEntity::transaction(function () {
(new CreateOrderDataEntity($payload))->execute();
}, connection: $dynamicConnection);
namespace App\DataEntities;
use BitMx\DataEntities\DataEntity;
use BitMx\DataEntities\Executers\MySqlQueryExecutor;
class GetAllPostsDataEntity extends DataEntity
{
// ...
#[\Override]
public function resolveQueryExecutor(): ?string
{
return MySqlQueryExecutor::class;
}
}
if ($response->isEmpty()) {
// no rows
}
if ($response->isNotEmpty()) {
// has rows
}
if ($response->success()) {
// The stored procedure was executed successfully
} else {
// There was an error executing the stored procedure
}
if ($response->failed()) {
// There was an error executing the stored procedure
} else {
// The stored procedure was executed successfully
}
$response->throw();
$message = $response->getError();
$response->isCached();
namespace App\DataEntities;
use BitMx\DataEntities\DataEntity;
use BitMx\DataEntities\PendingQuery;
class GetAllPostsDataEntity extends DataEntity
{
// ...
#[\Override]
public function boot(PendingQuery $pendingQuery): void
{
$pendingQuery->parameters()->add('tag', 'laravel');
}
}
trait Taggable
{
public function bootTaggable(PendingQuery $pendingQuery): void
{
$pendingQuery->parameters()->add('tag', 'laravel');
}
}
use BitMx\DataEntities\Events\DataEntityExecuted;
use BitMx\DataEntities\Events\DataEntityFailed;
use Illuminate\Support\Facades\Event;
Event::listen(DataEntityExecuted::class, function (DataEntityExecuted $event) {
logger()->info('data-entity.executed', [
'entity' => $event->dataEntity::class,
'duration_ms' => $event->durationMs,
]);
});
Event::listen(DataEntityFailed::class, function (DataEntityFailed $event) {
logger()->warning('data-entity.failed', [
'entity' => $event->dataEntity::class,
'error' => $event->exception->getMessage(),
]);
});
namespace App\DataEntities;
use BitMx\DataEntities\DataEntity;
use BitMx\DataEntities\PendingQuery;
use BitMx\DataEntities\Responses\Response;
class GetAllPostsDataEntity extends DataEntity
{
// ...
#[\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;
});
}
}
use BitMx\DataEntities\Contracts\QueryMiddleware;
use BitMx\DataEntities\PendingQuery;
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 $response): Response
{
$response->addData('tag', 'laravel');
return $response;
}
}
namespace App\DataEntities;
use BitMx\DataEntities\DataEntity;
use BitMx\DataEntities\PendingQuery;
class GetAllPostsDataEntity extends DataEntity
{
// ...
#[\Override]
public function boot(PendingQuery $pendingQuery): void
{
$pendingQuery->middleware()->onQuery(new PageMiddleware());
$pendingQuery->middleware()->onResponse(new TagMiddleware());
}
}
namespace App\DataEntities;
use BitMx\DataEntities\DataEntity;
use BitMx\DataEntities\Plugins\AlwaysThrowOnError;
class GetAllPostsDataEntity extends DataEntity
{
use AlwaysThrowOnError;
// ...
}
namespace App\DataEntities;
use BitMx\DataEntities\Contracts\Cacheable;
use BitMx\DataEntities\DataEntity;
use BitMx\DataEntities\Plugins\HasCache;
class GetAllPostsDataEntity extends DataEntity implements Cacheable
{
use HasCache;
// ...
public function cacheExpiresAt(): \DateTimeInterface
{
return now()->addMinutes(10);
}
}
use App\DataEntities\GetPostDataEntity;
$dataEntity = new GetPostDataEntity(1);
$dataEntity->invalidateCache();
$response = $dataEntity->execute();
use App\DataEntities\GetPostDataEntity;
$dataEntity = new GetPostDataEntity(1);
$dataEntity->disableCaching();
$response = $dataEntity->execute();
$dataEntity->clearCache();
use App\DataEntities\GetPostDataEntity;
$dataEntity = new GetPostDataEntity(1);
$response = $dataEntity->execute();
$response->isCached();
namespace App\DataEntities;
use BitMx\DataEntities\DataEntity;
use BitMx\DataEntities\Plugins\HasRetries;
use Carbon\CarbonInterval;
class GetAllPostsDataEntity extends DataEntity
{
use HasRetries;
protected function maxRetryAttempts(): int
{
return 3;
}
protected function retryBackoff(): int|CarbonInterval
{
return CarbonInterval::milliseconds(50);
// or: return 50; // milliseconds
}
}
namespace App\DataEntities;
use BitMx\DataEntities\Attributes\UseLazyQuery;
use BitMx\DataEntities\DataEntity;
#[UseLazyQuery]
class GetAllPostsDataEntity extends DataEntity
{
public function resolveStoreProcedure(): string
{
return 'spListAllPost';
}
}
use App\DataEntities\GetAllPostsDataEntity;
$dataEntity = new GetAllPostsDataEntity();
$response = $dataEntity->execute();
$posts = $response->lazy();
$count = $posts->count(); // first full pass; rows are now remembered
$titles = $posts->pluck('title'); // second pass; uses remembered rows (no extra DB round-trip)
foreach ($response->stream() as $post) {
// process one row at a time without keeping the full result set in memory
}
// Wrong: second foreach fails
$stream = $response->stream();
foreach ($stream as $post) { /* ... */ }
foreach ($stream as $post) { /* RuntimeException */ }
// Right (moderate sets, multiple passes): use lazy() up front
foreach ($response->lazy() as $post) { /* first pass */ }
foreach ($response->lazy() as $post) { /* second pass; remembered */ }
// Right (large sets, need another pass): re-execute
$response = $dataEntity->execute();
foreach ($response->stream() as $post) { /* ... */ }
namespace App\Data;
class PostData
{
public function __construct(
public int $id,
public string $title,
public string $content,
) {
}
}
namespace App\DataEntities;
use App\Data\PostData;
use BitMx\DataEntities\Attributes\MapTo;
use BitMx\DataEntities\Attributes\SingleItemResponse;
use BitMx\DataEntities\DataEntity;
#[SingleItemResponse]
#[MapTo(PostData::class)]
class GetPostDataEntity extends DataEntity
{
public function __construct(protected int $postId) {}
public function resolveStoreProcedure(): string
{
return 'spListPost';
}
protected function defaultParameters(): array
{
return ['post_id' => $this->postId];
}
}
/** @var PostData $post */
$post = (new GetPostDataEntity(1))->execute()->dto();
namespace App\DataEntities;
use App\Data\PostData;
use BitMx\DataEntities\Attributes\MapTo;
use BitMx\DataEntities\DataEntity;
use Illuminate\Support\Collection;
#[MapTo(PostData::class, Collection::class)]
class GetPostsDataEntity extends DataEntity
{
public function resolveStoreProcedure(): string
{
return 'spListPosts';
}
}
namespace App\DataEntities;
use App\Data\PostData;
use BitMx\DataEntities\Attributes\SingleItemResponse;
use BitMx\DataEntities\DataEntity;
use BitMx\DataEntities\Responses\Response;
#[SingleItemResponse]
class GetPostDataEntity extends DataEntity
{
public function __construct(protected int $postId) {}
public function resolveStoreProcedure(): string
{
return 'spListPost';
}
protected function defaultParameters(): array
{
return ['post_id' => $this->postId];
}
public function createDtoFromResponse(Response $response): PostData
{
$data = $response->data();
return new PostData(
id: $data['id'],
title: $data['title'],
content: $data['content'],
);
}
}
/** @var PostData $post */
$post = (new GetPostDataEntity(1))->execute()->dto();
use App\DataEntities\GetPostDataEntity;
$dataEntity = new GetPostDataEntity(1);
$dataEntity->dd();
$dataEntity->ddRaw();
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');
DataEntity::assertExecutedOnce(GetPostDataEntity::class);
expect(DataEntity::recorded(GetPostDataEntity::class))->toHaveCount(1);
});
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(),
];
}
}
use App\DataEntities\GetPostDataEntity;
use BitMx\DataEntities\DataEntity;
use BitMx\DataEntities\Responses\MockResponse;
use Tests\DataEntityFactories\PostDataEntityFactory;
it('should get the post', function () {
DataEntity::fake([
GetPostDataEntity::class => MockResponse::make(PostDataEntityFactory::new()),
]);
$dataEntity = new GetPostDataEntity(1);
$response = $dataEntity->execute();
$post = $response->data();
expect($post)->toHaveKeys(['id', 'title', 'content']);
});
use App\DataEntities\GetPostDataEntity;
use BitMx\DataEntities\DataEntity;
use BitMx\DataEntities\Responses\MockResponse;
use Tests\DataEntityFactories\PostDataEntityFactory;
it('should get the post', function () {
DataEntity::fake([
GetPostDataEntity::class => MockResponse::make(PostDataEntityFactory::new()->create()),
]);
$dataEntity = new GetPostDataEntity(1);
$response = $dataEntity->execute();
expect($response->data())->toHaveKeys(['id', 'title', 'content']);
});
use App\DataEntities\GetPostDataEntity;
use BitMx\DataEntities\DataEntity;
use BitMx\DataEntities\Responses\MockResponse;
use Tests\DataEntityFactories\PostDataEntityFactory;
it('should get a collection of posts', function () {
DataEntity::fake([
GetPostDataEntity::class => MockResponse::make(
PostDataEntityFactory::new()->count(10)->asCollection()
),
]);
$dataEntity = new GetPostDataEntity(1);
$response = $dataEntity->execute();
expect($response->data())->toHaveCount(10);
});
use App\DataEntities\GetPostDataEntity;
use BitMx\DataEntities\DataEntity;
use BitMx\DataEntities\Responses\MockResponse;
use Tests\DataEntityFactories\PostDataEntityFactory;
it('should get the post', function () {
DataEntity::fake([
GetPostDataEntity::class => MockResponse::make(
PostDataEntityFactory::new()->state([
'title' => 'Custom title',
])
),
]);
$dataEntity = new GetPostDataEntity(1);
$response = $dataEntity->execute();
expect($response->data('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(): DataEntityFactory
{
return $this->state([
'published_date' => now()->toDateTimeString(),
]);
}
}
use App\DataEntities\GetPostDataEntity;
use BitMx\DataEntities\DataEntity;
use BitMx\DataEntities\Responses\MockResponse;
use Tests\DataEntityFactories\PostDataEntityFactory;
it('should get the post', function () {
DataEntity::fake([
GetPostDataEntity::class => MockResponse::make(
PostDataEntityFactory::new()->withPublishedDate()
),
]);
$dataEntity = new GetPostDataEntity(1);
$response = $dataEntity->execute();
expect($response->data())->toHaveKey('published_date');
});
use App\DataEntities\GetPostDataEntity;
use BitMx\DataEntities\DataEntity;
use BitMx\DataEntities\Plugins\AlwaysThrowOnError;
use BitMx\DataEntities\Responses\MockResponse;
it('should throw when the stored procedure fails', function () {
DataEntity::fake([
GetPostDataEntity::class => MockResponse::makeWithException(new \Exception('Error')),
]);
$dataEntity = new class(1) extends GetPostDataEntity
{
use AlwaysThrowOnError;
};
$dataEntity->execute();
})->throws(\Exception::class, 'Error');
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;
}
}
use App\DataEntities\GetPostDataEntity;
use BitMx\DataEntities\DataEntity;
use BitMx\DataEntities\Responses\MockResponse;
use Tests\DataEntityFactories\PostDataEntityFactory;
it('should get a collection of posts', function () {
DataEntity::fake([
GetPostDataEntity::class => MockResponse::make(
PostDataEntityFactory::new()->asCollection()
),
]);
$dataEntity = new GetPostDataEntity(1);
$response = $dataEntity->execute();
expect($response->data())->toBeArray();
});
namespace App\DataEntities;
use BitMx\DataEntities\Attributes\SingleItemResponse;
use BitMx\DataEntities\DataEntity;
#[SingleItemResponse]
class GetPostDataEntity extends DataEntity
{
public function __construct(
protected int $postId,
) {
}
#[\Override]
public function resolveStoreProcedure(): string
{
return 'spListPost';
}
#[\Override]
protected function defaultParameters(): array
{
return [
'post_id' => $this->postId,
];
}
}
declare(strict_types=1);
use BitMx\DataEntities\Rector\RemoveMethodFromDataEntityRector;
use BitMx\DataEntities\Rector\ResponseTypePropertyToAttributeRector;
use Rector\Config\RectorConfig;
return RectorConfig::configure()
->withRules([
ResponseTypePropertyToAttributeRector::class,
RemoveMethodFromDataEntityRector::class,
])
->withImportNames();