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

Ncr Laravel Package

gdbots/ncr

gdbots/ncr is a Composer-installable PHP package from gdbots for NCR-related functionality in Laravel/PHP applications. It offers a lightweight integration point for projects using the gdbots ecosystem, with straightforward setup and compatibility.

View on GitHub
Deep Wiki
Context7

Getting Started

The package is a schema-first content repository abstraction, not a Laravel package — there's no NcrServiceProvider, no config file to publish, and no Eloquent integration. Treat it as a library you wire up yourself.

composer require gdbots/ncr

It pulls in gdbots/pbj (protobuf-json messages), gdbots/common, and a PSR container/event-dispatcher; check composer.json in the package and make sure your PHP version and ext-json satisfy it. Because it's a library, Laravel's package auto-discovery does nothing for you.

Where to look first, in this order:

  1. src/Repository/ — ContentRepository and ContentRepositoryInterface. This is the contract you'll code against. Everything else (Mongo, DynamoDB, Elasticsearch) is an adapter.
  2. src/Query/ — the "DQL" query message and QueryBuilder. Listing/filtering is done here, not with Eloquent.
  3. src/Exception/ — NodeNotFoundException, NodeAlreadyExistsException, etc. You'll map these to HTTP responses.
  4. The PBJ schemas under resources/ or schemas/ — node types are defined as .proto-style messages and code-generated.

Your first use case should be tiny: define one node type (e.g. acme:news:article), generate its PHP classes, register a concrete ContentRepository in a service provider, and do a create → find → publish → query cycle in a tinker session.

// app/Providers/NcrServiceProvider.php
public function register(): void
{
    $this->app->singleton(ContentRepositoryInterface::class, function ($app) {
        return new MongoContentRepository(
            $app->make(Client::class),
            config('ncr.database'),
        );
    });
}

Note that the concrete repositories live in the broader gdbots/triniti ecosystem, not in this repo. If none of the shipped adapters fit (e.g. you want Eloquent, Redis, or Meilisearch), you implement ContentRepositoryInterface yourself — plan for that possibility before committing.

Implementation Patterns

Schema-first node definitions. You don't create node classes by hand. You write a PBJ schema, run the generator (a composer script or the pbj:schema binary), and commit the generated lib/ output. In Laravel, that generated code should live outside app/ (e.g. app/Generated/Ncr autoloaded via a PSR-4 mapping in composer.json) so php artisan doesn't try to treat it as application code.

Wrap the repository, don't leak it. The repository API is low-level and message-oriented. Put a small application-level service in front of it so controllers and jobs never touch Node objects directly:

final class ArticleRepository
{
    public function __construct(private ContentRepositoryInterface $repo) {}

    public function create(ArticleData $data): Article
    {
        $node = $this->repo->createNode(
            Article::fromArray([
                'title' => $data->title,
                'slug'  => Str::slug($data->title),
                'status' => 'draft',
            ]),
        );

        return Article::fromNode($node);
    }

    public function findBySlug(string $slug): ?Article
    {
        try {
            return Article::fromNode($this->repo->getNode($slug));
        } catch (NodeNotFoundException) {
            return null;
        }
    }
}

Lifecycle over raw status columns. Nodes move draft → published → archived/deleted. Use the repository's publish/unpublish/delete methods rather than writing a status field yourself — that's what drives index updates and published_at bookkeeping. In Laravel terms, treat these as domain transitions and fire events from your wrapper so listeners can react.

Query building for listing endpoints. The QueryBuilder produces a Query message you pass to findNodes. Think of it as a portable subset of SQL — it compiles down to Mongo or Elasticsearch under the hood. Pagination is via cursor or page/count depending on the adapter, so check the docs for your concrete repository. Always pass an explicit sort; the default ordering is not something you should rely on across adapters.

$query = QueryBuilder::create('acme:news:article')
    ->where('status', 'published')
    ->where('tags', 'contains', 'php')
    ->sortBy('published_at', 'desc')
    ->build();

foreach ($this->repo->findNodes($query) as $node) { ... }

Separating writes from search. Most NCR setups write to primary storage and index into Elasticsearch asynchronously. For Laravel this maps neatly onto queued jobs: after a successful publish, dispatch ReindexArticle via the queue instead of updating the index inline. Then read listings from search and single-node reads from the primary store. Resist read-after-write from the search side.

Keeping it alongside Eloquent. Don't try to make NCR into an ORM. Keep NCR for content-shaped entities (articles, pages, blocks, media) and Eloquent for transactional data (orders, users, subscriptions). Where they intersect, store the node's _id/slug as a plain string column on the Eloquent model and join in PHP. A small HasNode trait on the Eloquent side is usually enough.

Testing. Because everything hangs off interfaces, tests are easy: implement a FakeContentRepository backed by an array, bind it in TestCase::setUp(), and you get fast, deterministic tests without Mongo or Elasticsearch. Only your integration tests should hit real backends.

Gotchas and Tips

  • Zero stars, zero dependents, small ecosystem. There's no blog post, no Stack Overflow tag, and Laravel-specific docs don't exist. Read src/ directly. Pin to the exact release ("gdbots/ncr": "1.x" or a commit hash) and check composer outdated before upgrading — a breaking change here won't be loudly versioned.
  • Concrete repositories are not here. This package is largely interfaces plus query/exception plumbing. You need the downstream implementations (typically from the triniti/gdbots stack) or you will be writing an adapter, which is a non-trivial week, not an afternoon. Verify the adapter exists before you start.
  • The PBJ dependency is the biggest operational cost. Generated classes must be rebuilt whenever a schema changes, and CI must run that generation step or you'll get class-not-found errors in deploy. Add the generation to a composer script and a make target so nobody forgets.
  • Dates are PBJ timestamp messages, not Carbon. Convert at the boundary ($node->get('published_at')->toDateTime() or similar) and never pass Carbon into node messages. JSON-serializing a node directly will produce protobuf-json shapes your front-end may not expect — normalize via API resources.
  • Read-after-write inconsistency. If listings come from an index, a just-published node may not appear immediately. Design UX around it (redirect to the detail page) rather than polling.
  • Domain exceptions need mapping. NodeNotFoundException → 404, NodeAlreadyExistsException → 409, validation failures → 422. Register this mapping in app/Exceptions/Handler.php or a middleware; otherwise every missing node becomes a 500.
  • Slugs are a real concern. Uniqueness is enforced by the repository, but only within a node type. Decide up front whether slugs are global or per-type, and whether they change on rename (usually they shouldn't — keep a redirect table).
  • Config quirks are yours to invent. Connection strings, index names, index prefixes per environment. Don't hardcode them in the repository constructor; put them in config/ncr.php and read from env() with sensible defaults. Use a distinct index prefix per environment so staging cannot query production search.
  • Debugging queries is painful because the DSL is compiled. Log the built query array ($query->toArray()) and the compiled backend query at debug level. When a listing returns nothing, the bug is almost always a missing status = published filter or a typo'd field name that the backend silently ignores.
  • Extension points worth using. Implement ContentRepositoryInterface for a custom backend, wrap the repository with a decorator for logging/metrics, and implement NodeIndexer if you need to reindex in bulk rather than per-node. These are the seams the package is designed around — subclassing concrete repositories is not.
  • Avoid PBJ in your public API. Your controllers, resources, and jobs should deal in plain DTOs or Eloquent-ish value objects. The moment PBJ messages leak into views or API resources, you're stuck with protobuf-json output and a painful refactor when you change node schemas.
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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource