Usage¶
Key Concepts¶
- Service:
\Config\Services::doctrine($getShared = true, ?string $dbGroup = null)returns a sharedDaycry\Doctrine\Doctrinekeyed by the (lower-cased) DB group name. - Helper:
doctrine_instance(?string $dbGroup = null)is a convenience wrapper around the service for use with thedoctrine_helper. - EntityManager access:
$doctrine->em(direct property) or$doctrine->getEm()(throws if not initialised). - Multi-DB: every
Config\Databasegroup resolves to its own cached Doctrine instance — see Multi-Database Groups.
Loading the Library¶
$doctrine = new \Daycry\Doctrine\Doctrine();
$user = $doctrine->em->getRepository(\App\Models\Entity\User::class)->find(1);
As a Service¶
$doctrine = \Config\Services::doctrine();
$user = $doctrine->em->getRepository(\App\Models\Entity\User::class)->find(1);
As a Helper¶
In your BaseController $helpers:
Then in any method:
$doctrine = doctrine_instance(); // default DB group
$reporting = doctrine_instance('reporting'); // alternate DB group
Accessing the EntityManager¶
The Doctrine wrapper exposes the underlying EntityManagerInterface two ways:
$em = $doctrine->em; // public property — nullable until the constructor finishes
$em = $doctrine->getEm(); // throws RuntimeException if the EM is not initialised
Prefer getEm() in code paths where a null EntityManager would be a bug
(eg. controllers / jobs); use the property for lightweight access in places
where the constructor has clearly already run.
Reopening the Connection¶
Long-running CLI workers and queue consumers occasionally end up with a
closed EntityManager after a swallowed exception. Use:
reOpen() rebuilds the EntityManager from the existing DBAL connection and
re-applies every entry in customTypeMappings. It throws if getEm() is
null.
Multi-Database Groups¶
Config\Database may declare several database groups (default, reporting,
legacy, …). Each group resolves to its own cached Doctrine instance:
$default = \Config\Services::doctrine(); // 'default'
$reporting = \Config\Services::doctrine(true, 'reporting');
// Helper form:
$reporting = doctrine_instance('reporting');
Group names are case-insensitive and stored in the singleton cache as
doctrine_<lowercase_group>. Pass false for $getShared to force a fresh
instance:
To drop a cached instance (eg. between tests):
\Config\Services::resetDoctrine(); // drops 'doctrine_default'
\Config\Services::resetDoctrine('reporting'); // drops 'doctrine_reporting'
Manual Result Caching¶
The library autoloads a global cache-aside helper backed by Doctrine's
resultsCache PSR-6 pool:
use function Daycry\Doctrine\Helpers\getFromCacheOrQuery;
$rows = getFromCacheOrQuery(
cacheKey: 'reports_daily',
ttl: 600,
queryFn: fn () => $em->createQuery(/* DQL */)->getArrayResult(),
);
Signature:
function getFromCacheOrQuery(
string $cacheKey,
int $ttl,
callable $queryFn,
?string $dbGroup = null,
): mixed
- Looks up
$cacheKeyinConfig\DoctrineresultsCachepool (Config\Cachebackend by default). - Cache miss → runs the closure, stores the value with
$ttlseconds expiry (0= no expiration), returns it. - Cache disabled (
Config\Doctrine::$resultsCache = false) → always runs the closure, returns its value, stores nothing. $dbGroupselects the underlying Doctrine instance. Useful for multi-tenant or read-replica setups:
$rows = getFromCacheOrQuery(
cacheKey: 'reports_daily',
ttl: 600,
queryFn: fn () => $em->createQuery(/* DQL */)->getArrayResult(),
dbGroup: 'reporting',
);
PSR-6 reserved characters ({}()/\@:) in the key are normalised automatically
(sanitised and disambiguated with a short hash of the original), so any key
string is accepted.
Second-Level Cache Statistics¶
When Config\Doctrine::$secondLevelCacheStatistics = true, the wrapper
exposes Doctrine's StatisticsCacheLogger:
$logger = \Config\Services::doctrine()->getSecondLevelCacheLogger();
// ?Doctrine\ORM\Cache\Logging\StatisticsCacheLogger
if ($logger !== null) {
$hits = $logger->getHitCount();
$misses = $logger->getMissCount();
$puts = $logger->getPutCount();
}
Reset the counters at the start of a request (eg. to read per-request hit ratios in the toolbar):
The package also ships a filter that performs this reset automatically — see
debug_toolbar.md and
second_level_cache_stats.md.
Notes¶
- If a cache adapter is misconfigured (missing extension, unreachable Redis
server, etc.) the Doctrine service raises
CacheExceptionat construction with a descriptive message and the underlying error chained asprevious. - See
src/Doctrine.phpfor runtime checks, cache wiring, and SLC setup.