Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Doctrine Commons Laravel Package

stinger-soft/doctrine-commons

A shared Doctrine commons library for PHP projects, providing reusable persistence utilities and foundational components that help standardize entity, repository, and database setup across Stinger Soft applications.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a small utility package for Doctrine ORM: it ships custom DQL functions and a handful of helper functions. Treat it as a toolbox you reach for when native DQL can't express what the query needs, not as a framework.

Install and wire it up

composer require stinger-soft/doctrine-commons

In a Laravel app using laravel-doctrine/orm, the integration point is config/doctrine.php. DQL functions are registered by category, so the exact key depends on what each function returns:

// config/doctrine.php
'custom_string_functions' => [
    'FIELD' => StingerSoft\DoctrineCommons\DQL\FieldFunction::class,
    'FIND_IN_SET' => StingerSoft\DoctrineCommons\DQL\FindInSetFunction::class,
],
'custom_numeric_functions' => [
    // ...
],
'custom_datetime_functions' => [
    // ...
],

If you're on plain Doctrine (Symfony-style or standalone), do the same registration in the Configuration object:

$config->addCustomStringFunction('FIELD', FieldFunction::class);

Where to look first

  1. src/ — skim the directory listing; the DQL function classes are the bulk of the value. Each class declares getName() and getSql().
  2. The README — it's the only place the function list is likely documented; there's no API docs site for a package this small.
  3. The composer.json — check the Doctrine ORM version constraint. This tells you immediately whether it will work with your installed doctrine/orm major version, which is the #1 compatibility risk.

First use case

The canonical reason to install this is ordering by a fixed list of IDs or slugs without raw SQL:

$qb->addSelect('FIELD(p.status, :statuses) AS HIDDEN sortOrder')
   ->setParameter('statuses', ['pending', 'active', 'archived'])
   ->orderBy('sortOrder', 'ASC');

Or FIND_IN_SET for filtering against a comma-separated column when you've inherited a schema you can't normalize:

$qb->andWhere('FIND_IN_SET(:tag, p.tags) > 0')
   ->setParameter('tag', 'php');

If neither of those applies to your codebase, you probably don't need the package — verify the function list before committing to the dependency.

Implementation Patterns

Register once, use everywhere

The registration is global to the EntityManager, so any repository, query builder, or Paginator on that connection sees the functions. Register in a service provider or config bootstrap, never per-query.

Repository + QueryBuilder pattern

Keep the function usage inside repository methods rather than in controllers. The DQL string is platform-coupled (see Gotchas), so isolating it makes a future swap tractable.

class ProductRepository
{
    public function findOrderedByStatus(array $statusOrder): array
    {
        return $this->createQueryBuilder('p')
            ->addSelect('FIELD(p.status, :order) AS HIDDEN sort')
            ->setParameter('order', $statusOrder)
            ->orderBy('sort', 'ASC')
            ->addOrderBy('p.name', 'ASC')
            ->getQuery()
            ->getResult();
    }
}

Note the HIDDEN keyword — without it the scalar aliased column becomes part of the result shape and breaks getResult() hydration into entities.

Parameterized only

DQL custom functions receive AST\Node arguments and translate them to SQL verbatim. Anything you inline into the DQL string is injected into SQL. Always bind list values as parameters; Doctrine expands array parameters correctly for functions that accept variable arity, but verify that behavior for the specific function — not all custom functions implement variadic handling.

Pagination with Paginator

The Doctrine Paginator runs a count query derived from your DQL. Functions in SELECT are usually fine; functions in ORDER BY can occasionally confuse the count-query rewrite. If pagination returns wrong totals, run the getCountQuery variant manually and compare. Pattern:

$paginator = new Paginator($qb->getQuery(), $fetchJoinCollection = false);
$total = count($paginator);

Composition with native DQL

These helpers pair well with CASE WHEN, COALESCE, and IDENTITY() because they fill gaps native DQL leaves open (ordering, string matching). Reach for native constructs first; use the package only where DQL genuinely has no equivalent.

Helper functions in entities

If the package includes PHP-side helpers (string normalization, comparison utilities), use them in entity lifecycle callbacks or value objects rather than duplicating logic in services. Keep them out of the query path — mixing PHP helpers and DQL functions with similar names is a common source of confusion.

Testing

Unit-test repository methods against a real database (SQLite in-memory is cheap but will not support MySQL-specific functions like FIELD/FIND_IN_SET). Either:

  • run integration tests against the same engine as production, or
  • register the functions with SQLite-compatible implementations in the test bootstrap.

The second option is more work but keeps the test suite fast.

Gotchas and Tips

Version drift is the main risk

doctrine/orm 2.x → 3.x changed the FunctionNode API and removed some legacy behavior. With zero stars and zero dependents, this package has no community pressure keeping it current. Check the doctrine/orm constraint in composer.json and pin the package version explicitly. If you're on ORM 3 and the package targets 2.x, expect to fork and patch the getSql() signatures.

Platform-specific SQL

FIELD() and FIND_IN_SET() are MySQL/MariaDB functions. The DQL parser accepts them everywhere because it delegates to the custom function, but the generated SQL will fail on PostgreSQL or SQLite. If your app runs more than one engine (common with SQLite in tests), this is the failure mode you'll hit first. Options: guard per-platform, or register alternate DQL function classes per platform and choose at bootstrap.

Registration category mismatches

Doctrine dispatches parsing by the function's return type category. Registering a numeric-returning function under custom_string_functions produces a parse error or a silently wrong AST. Match the category to what the function actually returns, and confirm the class's intended category (NumericFunction vs StringPrimary return type) by reading its source — the README may not say.

Clear the metadata cache

After changing DQL function registration, clear the Doctrine metadata/query cache (php artisan doctrine:clear:metadata:cache, doctrine:clear:query:cache, or your equivalent). Stale parser state produces bewildering "unknown function" errors that persist across restarts of the request cycle.

Arity errors surface late

Custom functions typically validate argument count inside getSql() rather than during DQL parsing. A wrong number of arguments fails at query execution, not at build time — so it can slip past tests that never execute that branch. Add an execution test for each function you depend on.

HIDDEN and hydration

Any aliased scalar in SELECT needs HIDDEN if you're hydrating entities. Forgetting it yields arrays or EntityManager hydration exceptions. For array hydration, drop HIDDEN and use the alias deliberately.

Escape hatch: write your own function

The extension point is straightforward — extend Doctrine\ORM\Query\AST\Functions\FunctionNode, implement getSql(Query $query, Parser $parser), register it. For a one- or two-function need, that's 30 lines and you own the maintenance. Given the package's low activity, this is often the more defensible choice; import the package when you need several functions at once, or treat its source as a reference implementation to copy from.

Vendor risk discipline

With 0 stars and 0 dependents, assume no downstream bug reports and no security review. Read the actual source of every function class you register (they're small), confirm it does what the name says, and keep a note in your repo about what you rely on. That way, if you need to vendor a fork, you know exactly the surface area.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo