black/common
Common utilities for Black projects: an abstract Doctrine Manager helper with convenience methods plus simple specification classes (and/or/not). Install via Composer and reuse across apps needing lightweight Doctrine-oriented shared code.
Installation
Add the package to your composer.json with a specific version (avoid @stable):
composer require black/common:1.0.0
Register a custom service provider (e.g., BlackCommonServiceProvider) in config/app.php:
'providers' => [
// ...
App\Providers\BlackCommonServiceProvider::class,
],
First Use Case: Basic Query Specification
Use the DoctrineManager abstract class to wrap Doctrine’s EntityManager and apply specifications:
// In a repository or service
use Black\Common\Doctrine\Specification\AndSpecification;
use Black\Common\Doctrine\Specification\NotSpecification;
use Black\Common\Doctrine\Specification\FieldEquals;
$doctrineManager = app(DoctrineManager::class);
$spec = new AndSpecification(
new NotSpecification(new FieldEquals('active', false)),
new FieldEquals('role', 'admin')
);
$results = $doctrineManager->findBy($spec, User::class);
Key Files to Inspect
vendor/black/common/src/Doctrine/AbstractManager.php: Abstract base for Doctrine operations.vendor/black/common/src/Doctrine/Specification/: Specification classes (AndSpecification, OrSpecification, NotSpecification).vendor/black/common/src/Doctrine/Specification/FieldSpecification.php: Base for field-based specs (e.g., FieldEquals, FieldGreaterThan).Repository Layer Integration
Extend the abstract DoctrineManager in a repository to encapsulate domain-specific queries:
namespace App\Repositories;
use Black\Common\Doctrine\AbstractManager;
use App\Models\User;
class UserRepository extends AbstractManager
{
public function findActiveAdmins()
{
$spec = new AndSpecification(
new FieldEquals('active', true),
new FieldEquals('role', 'admin')
);
return $this->findBy($spec, User::class);
}
}
Dynamic Filtering in APIs Use specifications to build composable filters for API endpoints:
// In a controller
public function index(Request $request)
{
$spec = $this->buildFilterSpecification($request->query());
$users = $this->userRepository->findBy($spec, User::class);
return response()->json($users);
}
private function buildFilterSpecification(array $filters)
{
$specs = [];
foreach ($filters as $field => $value) {
$specs[] = new FieldEquals($field, $value);
}
return new AndSpecification(...$specs);
}
Admin Panel Filters
Leverage OrSpecification for multi-criteria searches (e.g., search by name or email):
$searchTerm = $request->input('q');
$spec = new OrSpecification(
new FieldContains('name', $searchTerm),
new FieldContains('email', $searchTerm)
);
$results = $doctrineManager->findBy($spec, User::class);
Composing Complex Queries Chain specifications to build nested conditions:
$spec = new AndSpecification(
new FieldGreaterThan('created_at', '2023-01-01'),
new OrSpecification(
new FieldEquals('status', 'pending'),
new AndSpecification(
new FieldEquals('status', 'approved'),
new FieldLessThan('value', 100)
)
)
);
Negation Logic
Use NotSpecification to invert conditions (e.g., "not active"):
$spec = new NotSpecification(new FieldEquals('active', true));
$inactiveUsers = $doctrineManager->findBy($spec, User::class);
Performance Optimization
findBy() with setMaxResults():
$query = $doctrineManager->createQueryBuilder(User::class)
->where($spec->getDql())
->setMaxResults(50);
Laravel Service Container
Bind the abstract DoctrineManager to a concrete implementation in your service provider:
public function register()
{
$this->app->bind(
DoctrineManager::class,
fn() => new class($this->app['doctrine.orm.entity_manager']) extends AbstractManager {}
);
}
Doctrine Event Listeners
Extend the DoctrineManager to listen to Doctrine events (e.g., postLoad) for side effects:
use Doctrine\ORM\Event\LifecycleEventArgs;
public function postLoad(LifecycleEventArgs $args)
{
$entity = $args->getObject();
// Custom logic (e.g., lazy-loading)
}
Testing
Mock the DoctrineManager in tests using interfaces or abstract classes:
$mockManager = Mockery::mock(DoctrineManager::class);
$mockManager->shouldReceive('findBy')
->withArgs([$spec, User::class])
->andReturn([$mockUser]);
$this->app->instance(DoctrineManager::class, $mockManager);
Archived Package Risks
Specification::getDql() logic).Doctrine Version Mismatch
^2.5). Test with:
composer why-not black/common
DQL Complexity
toSql(): Unlike Laravel’s Query Builder, DQL lacks a direct toSql() method. Debug with:
$query = $doctrineManager->createQuery($spec->getDql());
$query->getSQL(); // Requires Doctrine 2.7+
EXPLAIN in your database to check query plans.Laravel Integration Gaps
DoctrineManager requires manual binding to Laravel’s EntityManager.Specification Validation
FieldEquals don’t validate field existence. Add checks:
if (!property_exists(User::class, $field)) {
throw new \InvalidArgumentException("Field '$field' does not exist on User.");
}
Log DQL Queries
Extend the DoctrineManager to log generated DQL:
public function findBy(Specification $spec, string $entityClass)
{
$dql = $spec->getDql();
\Log::debug("Generated DQL: $dql");
return parent::findBy($spec, $entityClass);
}
Test Specifications Isolated Unit test specifications to ensure correct DQL generation:
public function testAndSpecification()
{
$spec = new AndSpecification(
new FieldEquals('active', true),
new FieldEquals('role', 'admin')
);
$this->assertEquals(
'(u.active = :d0 AND u.role = :d1)',
$spec->getDql()
);
}
Handle Doctrine Exceptions
Wrap DoctrineManager calls in try-catch blocks for malformed DQL:
try {
$results = $doctrineManager->findBy($spec, User::class);
} catch (\Doctrine\ORM\Query\QueryException $e) {
\Log::error("Invalid specification DQL: " . $e->getMessage());
throw new \RuntimeException("Query failed: " . $e->getMessage());
}
Black\Common\Doctrine\Specification\Specification to create domain-specific specs:
class StatusInSpecification extends Specification
{
private $statuses;
public function __construct(array $statuses)
{
$this->statuses = $statuses;
How can I help you explore Laravel packages today?