webit/doctrine-json-bundle
Webit Doctrine JSON Bundle adds JSON column support to Doctrine in Symfony applications. It provides custom DBAL types and query helpers so you can store, retrieve, and query structured JSON data directly from your entities with minimal configuration.
The package is a Symfony bundle, not a Laravel package — there is no service provider, no config/doctrine-json.php, and no auto-discovery. The first thing to check is whether you actually need it: Doctrine DBAL has shipped a native json type since 2.6, so this bundle's value is (a) backfilling the type on very old DBAL versions and (b) registering DQL functions for querying inside JSON documents.
composer require webit/doctrine-json-bundlevendor/webit/doctrine-json-bundle/ and read src/ first — it is small enough to audit in one sitting. Identify two things: the DBAL type class (usually extends Doctrine\DBAL\Types\JsonType or Type) and the list of custom DQL function classes (typically JSON_CONTAINS, JSON_EXTRACT, JSON_UNQUOTE, JSON_LENGTH).Webit\DoctrineJsonBundle\WebitDoctrineJsonBundle in config/bundles.php.// AppServiceProvider::boot()
use Doctrine\DBAL\Types\Type;
use Doctrine\ORM\EntityManagerInterface;
public function boot(EntityManagerInterface $em)
{
if (! Type::hasType('json')) {
Type::addType('json', \Webit\DoctrineJsonBundle\DBAL\Types\JsonType::class);
}
$config = $em->getConfiguration();
$config->addCustomStringFunction('JSON_CONTAINS', \Webit\...\JsonContains::class);
}
type="json" and a property typed as array. Persist an array, read it back — that's the smoke test./** @ORM\Column(type="json") */
private array $attributes = [];
Entity mapping. Map the column as json and let the type do the array⇄string conversion. Use nullable=true if you expect docs without the payload, and always initialize to [] so PHP doesn't hand null to an array typed property.
Querying inside the document. The main reason to pull this bundle in is the DQL functions. Typical repository pattern:
$qb->select('p')
->from(Product::class, 'p')
->where("JSON_CONTAINS(p.attributes, :tag) = 1")
->setParameter('tag', json_encode('clearance'));
Read-modify-write. Since Doctrine sees the whole column as one scalar, mutating a nested key requires fetching, $entity->setAttributes([...$attrs, 'k' => $v]), and flushing. Wrap that in a domain method on the entity, not in the controller.
Hybrid pattern for reporting. Use DQL functions for simple predicates, but drop to $em->getConnection()->executeQuery() with native SQL when you need JSON_TABLE, generated columns, or multi-path JSON_EXTRACT in ORDER BY. Mapping the result with ResultSetMapping keeps it honest.
Schema. The bundle doesn't add indexes. If you query JSON paths frequently, add generated columns and indexes by hand in a migration; otherwise these queries table-scan.
json. Adding the type again will either duplicate quietly or throw "type already exists" depending on your guard — check Type::hasType('json') before addType.JSON_EXTRACT(...) selected in a DQL SELECT comes back as a raw string, not an array. Decode in PHP or wrap with JSON_UNQUOTE only when you genuinely want a scalar.LONGTEXT with a check constraint; some functions (e.g. JSON_TABLE, JSON_OVERLAPS) are missing or behave differently. Test against your actual engine, not just mysql.JSON_CONTAINS needs valid JSON. Passing a raw PHP string ('clearance') fails — pass json_encode($value), or '"clearance"' if it's a JSON string literal. This is the most common silent bug: the function returns 0 instead of erroring.JSON_UNQUOTE output inherits the connection collation, so WHERE JSON_UNQUOTE(p.attributes->'$.sku') = :sku can miss matches under utf8mb4_general_ci vs utf8mb4_0900_ai_ci.Type class with your own that implements JsonSerializable-aware conversion, then register yours instead — don't subclass the bundle's type, since the parent may break on DBAL bumps.How can I help you explore Laravel packages today?