dbstudios/doctrine-query-document
Build Doctrine DQL filters from a simple array “query document”. Apply conditions to an existing QueryBuilder (from/select required), auto-bind positional parameters, traverse relations via dot notation, and query JSON fields (MySQL 5.7+).
Installation:
composer require dbstudios/doctrine-query-document
Ensure your project uses Doctrine ORM (v2.5+ recommended).
First Use Case:
Replace traditional DQL WHERE clauses with MongoDB-style query documents.
use Doctrine\Common\Persistence\ObjectManager;
use DbStudios\DoctrineQueryDocument\QueryManager;
$manager = new QueryManager($objectManager);
$qb = $objectManager->createQueryBuilder()
->from('App\Entity\User', 'u')
->select('u');
$manager->apply($qb, [
'name' => 'John',
'age' => ['$gt' => 25]
]);
// Resulting DQL: WHERE u.name = ?0 AND u.age > ?1
Key Files:
QueryManager (core class)DoctrineQueryDocumentException (error handling)src/QueryManager.php (extension points)Query Construction:
$query = [
'field1' => 'value',
'field2' => ['$in' => [1, 2, 3]],
'field3' => ['$exists' => true]
];
$manager->apply($qb, $query);
Relationship Handling: Use dot notation for joins/filters:
$manager->apply($qb, [
'posts.title' => 'Hello',
'author.name' => ['$like' => '%Doe%']
]);
Parameter Binding: All values auto-convert to positional parameters:
$manager->apply($qb, ['createdAt' => ['$gt' => new \DateTime()]]);
Hybrid Queries: Mix with native DQL:
$qb->andWhere('u.status = :status')
->setParameter('status', 'active');
$manager->apply($qb, ['name' => 'Admin']); // Appends to existing WHERE
Repository Layer:
public function findByDocument(array $query) {
$qb = $this->createQueryBuilder('u');
$manager = new QueryManager($this->getEntityManager());
$manager->apply($qb, $query);
return $qb->getQuery()->getResult();
}
Dynamic Fields:
Use select() with query documents:
$manager->apply($qb, [], ['fields' => ['name', 'email']]);
// Result: SELECT u.name, u.email FROM ...
Missing from/select:
Throws DoctrineQueryDocumentException if required clauses are absent.
Fix: Always call createQueryBuilder()->from()->select().
Unsupported Operators:
Only $eq, $gt, $lt, $in, $like, $exists are implemented.
Workaround: Use raw DQL for unsupported ops (e.g., $regex).
Parameter Collisions:
Manual parameters (e.g., setParameter()) may conflict with auto-bound values.
Tip: Prefix manual params (e.g., :manual_*).
Nested Relationships:
Deep dot notation (e.g., user.profile.address.city) may fail if intermediate joins lack JOIN clauses.
Solution: Pre-join entities or use innerJoin() explicitly.
echo $qb->getDQL(); // Verify generated SQL
$qb->getQuery()->getParameters(); // Inspect bound params
Custom Operators:
Extend QueryManager to support new operators:
class CustomQueryManager extends QueryManager {
protected function registerOperators() {
$this->operators['$custom'] = function($field, $value) {
return "CUSTOM_FUNCTION($field, :$field)";
};
}
}
Query Modifiers:
Override apply() to pre/post-process queries:
$manager->apply($qb, $query, ['modifier' => function($qb) {
$qb->andWhere('u.deleted = 0');
}]);
Type Casting:
Handle custom types (e.g., UUIDs) by extending QueryManager::castValue().
QueryManager instances for multiple queries.WHERE user.id IN (...)).$like on Large Fields: Use $regex with anchors (/^pattern/) instead.How can I help you explore Laravel packages today?