Doctrine ORM 3 integration for CodeIgniter 4.
- ORM integration via
\Daycry\Doctrine\Doctrineand\Config\Services::doctrine(). - Server-side DataTables Builder with safe operator parsing, whitelisted columns, and
[><]/[IN]/[OR]validation. - CodeIgniter Debug Toolbar collector with optional Second-Level Cache (SLC) statistics badge.
- Doctrine Second-Level Cache wired to the framework cache backend (file, Redis, Memcached, array).
getFromCacheOrQuery()cache-aside helper backed by the configured PSR-6 result cache.- Multi-database group support — get a separate Doctrine instance per
Config\Databasegroup.
- PHP ≥ 8.2
- CodeIgniter ^4
- Doctrine ORM ^3, DBAL ^4
- Symfony Cache ^7
See composer.json for the complete dependency graph.
- Installation
- Configuration
- Usage — service, helper, multi-DB,
getFromCacheOrQuery, advanced API - CLI Commands
- DataTables Builder
- DataTables Search Modes — canonical operator reference
- Debug Toolbar — query log + SLC stats + per-request reset filter
- Second-Level Cache (SLC)
- SLC Statistics
- Changelog
composer require daycry/doctrineThen publish the configuration:
php spark doctrine:publishThis copies Config/Doctrine.php into your app namespace and cli-config.php
into the project root for use with the Doctrine ORM CLI.
$doctrine = \Config\Services::doctrine();
$user = $doctrine->em->getRepository(\App\Models\Entity\User::class)->find(1);Add doctrine_helper to your BaseController::$helpers:
protected $helpers = ['doctrine_helper'];$doctrine = doctrine_instance(); // default DB group
$reporting = doctrine_instance('reporting'); // alternate DB group$doctrine = new \Daycry\Doctrine\Doctrine();
$user = $doctrine->em->getRepository(\App\Models\Entity\User::class)->find(1);getFromCacheOrQuery() is autoloaded as a global function (no use is needed
beyond the function import). It looks up $cacheKey in the configured result
cache pool and falls back to the closure on miss.
use function Daycry\Doctrine\Helpers\getFromCacheOrQuery;
$rows = getFromCacheOrQuery(
cacheKey: 'projects_list_v1',
ttl: 300,
queryFn: fn () => $doctrine->em
->createQueryBuilder()
->select('p')
->from(\App\Models\Entity\Project::class, 'p')
->getQuery()
->getArrayResult(),
);When the result cache is disabled (Config\Doctrine::$resultsCache = false)
the closure runs every time. PSR-6 reserves the characters
{}()/\@: in cache keys — avoid them.
See docs/usage.md for advanced API: getEm(), reOpen(),
multi-database groups, Services::resetDoctrine(), and more.
Use the generated cli-config.php from the project root:
php cli-config.php orm:convert-mapping --namespace="App\Models\Entity\\" --force --from-database annotation .
php cli-config.php orm:generate-entities .
php cli-config.php orm:generate-proxies app/Models/ProxiesIf you encounter the JMS Serializer error "The annotation
@JMS\Serializer\Annotation\ExclusionPolicy was never imported", run
composer dump-autoload to refresh annotation discovery.
$datatables = (new \Daycry\Doctrine\DataTables\Builder())
->withColumnAliases([
'id' => 'p.id',
'name' => 'p.name',
])
->withSearchableColumns(['p.name'])
->withCaseInsensitive(true)
->withMaxFilterValues(500) // cap [IN] / [OR] value lists; default 500
->withQueryBuilder(
$this->doctrine->em->createQueryBuilder()
->select('p.id, p.name')
->from(\App\Models\Entity\Project::class, 'p'),
)
->withRequestParams($this->request->getGet());
return $this->response->setJSON($datatables->getResponse());If pagination throws "Not all identifier properties can be found in the
ResultSetMapping", set ->setUseOutputWalkers(false) on the Builder.
The Builder supports bracket-prefixed operators per column:
[%] (LIKE, default) [=] [!=] [>] [<] [IN] [OR] [><]
Synonyms [LIKE] and [%%] map to [%]. Unknown prefixes silently fall
back to [%]. The DataTables regex: true flag is not supported —
sending it raises InvalidArgumentException.
See docs/search_modes.md for the full operator
matrix, validation rules, case-insensitivity behaviour and examples.
A DoctrineCollector automatically captures every DBAL query so you can
inspect them in the CodeIgniter Debug Toolbar.
-
Register the collector in
app/Config/Toolbar.php:public $collectors = [ // ... \Daycry\Doctrine\Debug\Toolbar\Collectors\DoctrineCollector::class, ];
-
Use Doctrine as usual — the middleware self-registers when you instantiate the service.
For long-running CLI workers you can cap the in-memory query log:
\Config\Services::doctrineCollector()->setMaxQueries(500); // FIFO; 0 disables the capSee docs/debug_toolbar.md for the full collector API, the SLC stats badge, and the per-request reset filter.
Doctrine's Second-Level Cache reuses the framework cache backend
(file / Redis / Memcached / array) and its ttl. Enable in
app/Config/Doctrine.php:
public bool $secondLevelCache = true;
public bool $secondLevelCacheStatistics = true; // optional: hits/misses/puts badge
public ?int $secondLevelCacheTtl = null; // null = inherit Config\Cache::$ttl; 0 = no expiryTo reset SLC statistics at the start of every request (useful in development to read per-request hit ratios in the toolbar), register the filter:
// app/Config/Filters.php
public array $globals = [
'before' => [
\Daycry\Doctrine\Debug\Filters\DoctrineSlcReset::class,
],
];The filter is a no-op unless secondLevelCacheStatistics is enabled.
See docs/second_level_cache.md and docs/second_level_cache_stats.md for full details.
Available Composer scripts for contributors:
composer test # PHPUnit test suite
composer phpstan # PHPStan (level 6)
composer psalm # Psalm static analysis
composer rector # Rector dry-run
composer analyze # phpstan + psalm + rector
composer cs # PHP-CS-Fixer dry-run
composer cs-fix # PHP-CS-Fixer applyMIT. Issues and PRs welcome at https://github.com/daycry/doctrine.