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

Laravel Dag Manager Laravel Package

telkins/laravel-dag-manager

Laravel package for building and running directed acyclic graphs (DAGs). Define tasks and dependencies, persist graphs to the database, and execute nodes in dependency order using Laravel's queue system. Useful for workflows, pipelines, and job orchestration.

View on GitHub
Deep Wiki
Context7

Getting Started

The package gives you a persisted directed acyclic graph — nodes are your own Eloquent models, edges are stored in package tables — so you can answer "what depends on this?" / "what does this depend on?" without recursive PHP or recursive CTEs.

  1. Install and publish.

    composer require telkins/laravel-dag-manager
    php artisan vendor:publish --provider="Telkins\Dag\Providers\DagServiceProvider"
    php artisan migrate
    

    The service provider is auto-discovered on Laravel; if you run a trimmed bootstrap/providers.php/config/app.php, register it explicitly.

  2. Read config/dag.php before anything else. It controls the DB connection and table/model bindings. Everything else in the package resolves through it, so a wrong connection name is the #1 cause of "table not found" on a fresh install.

  3. Make a model a DAG node by implementing the package's node contract and pulling in the node trait from the README:

    use Illuminate\Database\Eloquent\Model;
    use Telkins\Dag\Contracts\DagNode as DagNodeContract;
    use Telkins\Dag\Models\Traits\HasDagNode;
    
    class Task extends Model implements DagNodeContract
    {
        use HasDagNode;
    }
    
  4. First use case — model task prerequisites in a project tracker:

    use Telkins\Dag\Models\Dag;
    
    $dag = new Dag();
    
    $dag->addEdge($designTask, $buildTask);   // design -> build (prerequisite -> dependent)
    $dag->addEdge($buildTask, $deployTask);
    
    $dag->getAncestors($deployTask);          // everything deploy waits on
    $dag->getDescendants($designTask);        // everything that unblocks if design slips
    $dag->isAncestorOf($designTask, $deployTask); // true
    
  5. Where to look next: Telkins\Dag\Models\Dag is the entire public API surface (edge CRUD + traversal + boolean predicates), and Telkins\Dag\Exceptions\* tells you every failure mode you must handle. Read those two before the README's advanced sections.

Implementation Patterns

Pick one direction and enforce it in a helper. Whether addEdge($a, $b) means "a before b" is the single thing every new contributor gets wrong. Wrap it:

final class Dependencies
{
    public function __construct(private Dag $dag) {}

    public function requires(Task $prerequisite, Task $dependent): void
    {
        if ($this->dag->hasEdge($prerequisite, $dependent)) {
            return; // idempotent
        }

        $this->dag->addEdge($prerequisite, $dependent);
    }
}

Validate cycles at the boundary, not in the model. A form request or custom rule that tries the edge and catches the circular-reference exception produces far better UX than a 500 deep in a save:

try {
    $dag->addEdge($prereq, $dependent);
} catch (CircularReferenceException $e) {
    throw ValidationException::withMessages([
        'prerequisite_id' => 'That would create a dependency loop.',
    ]);
} catch (DuplicateEdgeException $e) {
    // already linked — treat as success or a soft validation warning
}

Wrap graph mutations in transactions. An edge insert and any application-side bookkeeping (an audit row, a "blocked" status flip) must land together; a half-applied edge means your traversal answers are wrong forever until someone diffs the table.

Bulk import in one transaction, one Dag instance. Seeding a category tree or importing a project plan edge-by-edge with fresh new Dag() calls is fine functionally but wasteful. Chunk input (500–1000 edges), guard each with hasEdge, and let exceptions abort the chunk.

Drive queues and state machines off the graph. Before dispatching a job, check whether every ancestor is complete — either fetch getAncestorIds($task) once and compare against completed IDs in a single whereIn, or precompute a "remaining prerequisites" counter maintained by an observer on edge add/remove. Avoid calling hasAncestor() per ancestor (N+1 queries against the edge table).

Cache traversal results with a version key. getAncestors/getDescendants are cheap but not free on wide graphs, and they rarely change. Cache descendant_ids:{nodeId} and bump a per-graph version counter whenever an edge is added or removed for any node in it — invalidation by graph, not by node, keeps it simple and correct.

Multi-tenant / multi-database. Pass the connection through to the Dag instance rather than mutating global config at runtime, and publish the migrations onto that connection (--path/--database) during tenant provisioning. Tenants sharing one edges table without a tenant column is a data-leak waiting to happen; prefer separate connections or separate schemas.

Custom edge model for metadata. If you need edge attributes (label, weight, created_by, valid_from), extend the package edge model and bind your subclass through the dag config — this is the documented extension point referenced by the "Custom DAG edge model" example, whose namespace was corrected in 5.0.0. Copy the namespace from the v5 docs verbatim; the 4.x example was wrong, so anything you pasted from an older blog post will silently reference a non-existent class.

Rendering the graph. Resolve getDescendantsAndSelf($root) once, group by depth (repeat a BFS over the edge collection in PHP), and eager-load your own relations with whereIn on the resulting IDs. Don't re-query the DAG per tree level.

Gotchas and Tips

  • 5.0.0 is a major version bump driven by platform support. The changelog content is just a docs namespace fix plus Laravel 13 / PHP 8.5 support, which means the breaking change is in composer.json constraints (dropped older Laravel/PHP). Check the package's composer.json before upgrading — if you're not on the new minimums, stay on 4.0.x. The DAG schema itself appears unchanged, but run your suite against a restored copy of production data before trusting that.
  • Namespace in the old custom-edge docs is wrong. Anything copied from 4.x docs or third-party posts will fail to autoload. Re-copy from the v5 README.
  • config:cache is a landmine. Table and model bindings live in config/dag.php; if the cached config was built in an environment with a different connection, you'll get missing-table errors in production only. Rebuild the cache as part of deploy after any DAG config change.
  • Exceptions are part of the API. CircularReferenceException, DuplicateEdgeException and friends are control flow, not bugs. Never let them bubble to a 500; map each to a validation message or an idempotent no-op.
  • Don't delete nodes from your own table and assume the graph is clean. Remove edges explicitly first (or wrap both in a transaction with a node-deleting observer); orphaned edge rows make getAncestors return ghost IDs and produce confusing ModelNotFoundExceptions on whereIn hydration.
  • Node identity must be stable. The package stores node keys as integers — avoid UUID/string-keyed models, and be careful with soft deletes (a soft-deleted node still participates in traversal unless you filter the hydrated results yourself).
  • Collection sizes bite on wide graphs. Traversal returns full model collections; select only IDs (getAncestorIds/getDescendantIds or an explicit select('id')) for job dispatch and existence checks, and chunk when iterating.
  • Debugging: Dag::edges()->toSql()-style inspection plus DB::listen is the fastest way to see what the package is actually querying; compare against the raw dag_edges table when a boolean predicate disagrees with a traversal result — that mismatch almost always means a manually inserted or orphaned row.
  • Test on every driver you deploy. SQLite in-memory in CI hides lock/constraint behaviour on MySQL/Postgres; at minimum run one integration test against the production engine, including a concurrent addEdge for the same pair.
  • Extension points are narrow by design: the config-file bindings and the edge model subclass. Don't try to swap the Dag implementation by overriding the container binding unless you also re-bind everything the provider registers — wrap it in your own service (as in the Dependencies example above) instead.
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.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin