byteincoffee/doctrine-extensions
Laravel package integrating Doctrine Extensions with Eloquent models, adding behaviors like timestampable, sluggable, soft delete, and more. Provides easy configuration, listeners/subscribers, and seamless use of Doctrine-style extensions in Laravel apps.
Installation Add the package via Composer:
composer require byteincoffee/doctrine-extensions
Ensure Doctrine\ORM\Tools\Setup is properly configured in your Laravel app (typically via config/database.php or a custom Doctrine setup).
First Use Case: Sluggable Behavior
Extend your entity with Sluggable trait:
use DoctrineExtensions\Query\Mysql\Sluggable;
/**
* @ORM\Entity
*/
class Post
{
use Sluggable;
/**
* @ORM\Column(type="string", length=255, unique=true)
*/
private $slug;
/**
* @ORM\Column(type="string", length=255)
*/
private $title;
// ... getters/setters
}
Define slug generation in buildSlug():
public function buildSlug()
{
return strtolower($this->title);
}
Enable Extensions in Doctrine
Register the extensions in your Doctrine configuration (e.g., in AppServiceProvider):
use DoctrineExtensions\ORM\Query\AST\Functions\Mysql;
$em->getConfiguration()->addCustomStringFunction(
'CONCAT_WS', Mysql\Functions\StringFunctions::class
);
Sluggable Entities
Sluggable trait for dynamic slug generation.buildSlug() to customize slug logic (e.g., include categories):
public function buildSlug()
{
return strtolower($this->category->name . '-' . $this->title);
}
unique=true in @ORM\Column for slugs.Tree Queries (Nested Sets)
NestedSet trait for hierarchical data (e.g., categories):
use DoctrineExtensions\Query\TreeRepository;
class Category
{
use NestedSet;
// ... fields
}
$children = $category->getChildren();
$parent = $category->getParent();
Soft Deletes
SoftDelete trait for soft-deletion logic:
use DoctrineExtensions\Query\SoftDelete;
class Post
{
use SoftDelete;
// ... fields
}
$query->andWhere('e.deletedAt IS NULL');
Custom DQL Functions
CONCAT_WS):
$em->getConfiguration()->addCustomStringFunction(
'MY_CUSTOM_FUNC', CustomFunction::class
);
$query->select('MY_CUSTOM_FUNC(e.field1, e.field2) AS result');
doctrine/orm alongside Laravel’s Eloquent for hybrid ORM usage. Map entities to Eloquent models via Model::setConnection().lft, rgt for NestedSet) in migrations:
Schema::table('categories', function (Blueprint $table) {
$table->integer('lft')->unsigned();
$table->integer('rgt')->unsigned();
$table->integer('level')->unsigned();
});
$children = Cache::remember("category_{$id}_children", 3600, function () use ($category) {
return $category->getChildren();
});
Doctrine vs. Eloquent Conflicts
prePersist) may conflict with Eloquent events.Model::setConnection() carefully and ensure no duplicate event listeners.Sluggable Uniqueness
unique=true on slug columns causes duplicate-slug errors.buildSlug():
public function buildSlug()
{
$slug = strtolower($this->title);
if (!$this->isSlugUnique($slug)) {
$slug .= '-' . $this->id;
}
return $slug;
}
Nested Set Performance
level > 10) degrade query performance. Use MaterializedPath for deeper hierarchies.MaterializedPath trait if needed:
use DoctrineExtensions\Query\MaterializedPath;
Custom Function Registration
SyntaxError.$query->select('MY_CUSTOM_FUNC(e.field) AS test');
Soft Delete Conflicts
SoftDelete filters:
$query->andWhere('e.deletedAt IS NULL');
Enable Doctrine Logging
Add to config/logging.php:
'doctrine' => [
'driver' => 'single',
'path' => storage_path('logs/doctrine.log'),
'level' => 'debug',
],
Then enable logging in AppServiceProvider:
$em->getConfiguration()->setSQLLogger(new \Doctrine\DBAL\Logging\EchoSQLLogger());
Query AST Errors
For custom DQL functions, check the AST (Abstract Syntax Tree) path:
vendor/bin/doctrine-orm-cli ast:dump
Custom Traits
Extend existing traits (e.g., Sluggable) for domain-specific logic:
trait CustomSluggable extends Sluggable
{
protected function buildSlug()
{
return parent::buildSlug() . '-custom';
}
}
Event Subscribers
Hook into Doctrine events (e.g., onFlush) for pre/post operations:
use Doctrine\Common\EventSubscriber;
class MySubscriber implements EventSubscriber
{
public function getSubscribedEvents()
{
return ['onFlush'];
}
public function onFlush(OnFlushEventArgs $args)
{
// Custom logic before flush
}
}
Custom Query Functions
Implement DoctrineExtensions\Query\AST\Functions\AbstractFunctionNode for advanced DQL:
class CustomFunction extends AbstractFunctionNode
{
public function parse(\Doctrine\ORM\Query\Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
// ... parse arguments
}
}
How can I help you explore Laravel packages today?