anh/doctrine-extensions-resource
Install the Package
composer require anh/doctrine-extensions-resource:0.4.*
Ensure your Laravel project uses Doctrine ORM (e.g., via doctrine/orm or a bundle like anh/doctrine-resource-bundle).
Define Resources
Create a configuration array in config/doctrine-resources.php (or equivalent):
return [
'article' => [
'model' => App\Models\Article::class,
'rules' => [
'isPublished' => [
'isDraft' => false,
'publishedSince <= current_timestamp()',
],
],
],
'category' => [
'model' => App\Models\Category::class,
],
];
Initialize the Resource Manager
In a service provider (e.g., AppServiceProvider):
use Anh\DoctrineResource\ORM\ResourceRepositoryFactory;
use Anh\DoctrineResource\ORM\EventListener\LoadMetadataSubscriber;
use Anh\DoctrineResource\ORM\ResourceManagerFactory;
use Doctrine\ORM\EntityManagerInterface;
public function register()
{
$resources = config('doctrine-resources');
$repositoryFactory = new ResourceRepositoryFactory($resources, new Paginator());
$this->app->singleton(ResourceRepositoryFactory::class, fn() => $repositoryFactory);
// Bind ResourceManagerFactory to Laravel's container
$this->app->singleton(ResourceManagerFactory::class, function ($app) {
$entityManager = $app->make(EntityManagerInterface::class);
$eventDispatcher = $app->make(EventDispatcherInterface::class);
return new ResourceManagerFactory($resources, $eventDispatcher);
});
}
First Use Case: Fetching Resources
Inject ResourceManagerFactory into a controller or service:
public function index(ResourceManagerFactory $resourceManagerFactory)
{
$articleManager = $resourceManagerFactory->create('article', $entityManager);
$publishedArticles = $articleManager->getRepository()->paginate(1, 10, ['[isPublished]']);
return view('articles.index', compact('publishedArticles'));
}
CRUD Operations
Use ResourceManager for standardized CRUD:
// Create
$article = $manager->createResource();
$article->setTitle('Hello World');
$manager->create($article);
// Update
$article->setTitle('Updated Title');
$manager->update($article);
// Delete
$manager->delete($article);
Querying with Criteria Leverage the advanced criteria format for flexible queries:
// Basic filtering
$articles = $repository->fetch(['section' => 'tech']);
// Complex criteria with rules
$publishedTechArticles = $repository->fetch([
'[isPublished]',
'section' => 'tech',
'#or' => [
'%rating' => ['>', 5],
'views' => ['>', 1000],
],
]);
// Pagination
$paginatedArticles = $repository->paginate(1, 20, ['[isPublished]']);
Event-Driven Extensions
Subscribe to resource events (e.g., anh_resource.article.pre_create) to add logic:
$dispatcher->addListener('anh_resource.article.pre_create', function ($event) {
$article = $event->getResource();
$article->setCreatedAt(new \DateTime());
});
Repository Overrides Override default repositories per resource:
$resources = [
'article' => [
'model' => App\Models\Article::class,
'repository' => App\Repositories\CustomArticleRepository::class,
],
];
EntityManager and ResourceManagerFactory as singletons for dependency injection.ResourceManager in form requests to validate and persist entities:
public function store(StoreArticleRequest $request, ResourceManagerFactory $factory)
{
$manager = $factory->create('article', $entityManager);
$article = $manager->createResource();
$request->persistToEntity($article);
$manager->create($article);
}
public function show(Article $article)
{
return new ArticleResource($article);
}
Driver-Specific Operators
Not all comparison operators (e.g., like, in) are supported across Doctrine drivers (ORM, MongoDB, PHPCR). Test queries thoroughly.
Rule Syntax Errors
Rules in resource config must use valid Doctrine query syntax. Example:
// Invalid (syntax error)
'isPublished' => ['publishedAt > NOW()'],
// Valid
'isPublished' => ['publishedAt > current_timestamp()'],
Circular Dependencies
Avoid defining resources in a circular manner (e.g., Article depends on Category, which depends on Article). Use lazy-loading or separate configurations.
Event Dispatcher Conflicts
Ensure the EventDispatcher used in ResourceManagerFactory is the same instance as Doctrine’s to avoid missed events.
$entityManager->getConnection()->getConfiguration()->setSQLLogger(new \Doctrine\DBAL\Logging\EchoSQLLogger());
ResourceRepository::getQueryBuilder() to inspect generated queries:
$qb = $repository->getQueryBuilder(['[isPublished]']);
dump($qb->getDQL());
Custom Paginator
Implement ResourcePaginatorInterface for non-standard pagination (e.g., cursor-based):
class CustomPaginator implements ResourcePaginatorInterface {
public function paginate(array $query, $page, $limit) { ... }
}
Dynamic Rules
Override RuleResolver to add runtime rules:
$ruleResolver = new CustomRuleResolver($resources);
$repositoryFactory = new ResourceRepositoryFactory($resources, $paginator, $ruleResolver);
Metadata Subscribers
Extend LoadMetadataSubscriber to add custom metadata (e.g., indexes) during entity loading:
class CustomMetadataSubscriber extends LoadMetadataSubscriber {
public function loadClassMetadata(ClassMetadata $metadata, $className) {
if ($className === Article::class) {
$metadata->addIndex(['title'], 'idx_title');
}
}
}
'Article' vs 'article').model paths in resources are fully qualified (e.g., App\Models\Article, not Article).How can I help you explore Laravel packages today?