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

Component Model Laravel Package

nette/component-model

nette/component-model provides foundational classes for hierarchical component trees in Nette apps. It handles named parent-child relationships, lifecycle events, component lookup, and recursive iteration, enabling reusable, loosely coupled UI and application components.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install (standalone, not a Laravel package)

composer require nette/component-model:^3.2

There is no service provider, no config file, no facade. nette/component-model is a ~4-file library (Component, Container, IComponent, ArrayAccess trait). It does not auto-discover in Laravel and does not need to — you pull it into your own service classes. Requires PHP 8.1+.

2. Alias the imports immediately

Before writing any code, settle on a convention, because Container and Component collide with both Illuminate and Livewire:

use Nette\ComponentModel\Container as ComponentContainer;
use Nette\ComponentModel\Component as ComponentNode;

3. Read these first

  • readme.md — the canonical API tour; this is more useful than the API docs.
  • src/Component.php — getName(), getParent(), monitor(), lookup().
  • src/Container.php — addComponent(), getComponent(), getComponents(), getComponentTree(), NAME_REGEXP.
  • release/v3.2.0.md — the deprecation list you must code against today so 4.0 doesn't break you.

4. First use case: a hierarchical domain tree inside a Laravel service

The natural Laravel fit is not UI rendering — it's a nestable domain structure with lifecycle hooks. A navigation menu tree is the smallest useful demo:

use Nette\ComponentModel\Container as ComponentContainer;
use Nette\ComponentModel\Component as ComponentNode;

class MenuNode extends ComponentNode
{
    public function __construct(
        private string $label,
        private string $url = '#',
    ) {}
}

final class MenuTree extends ComponentContainer {}

final class MenuBuilder
{
    public function build(): MenuTree
    {
        $root = new MenuTree();

        $main = new MenuNode('Products', '/products');
        $root->addComponent($main, 'products');
        $main->addComponent(new MenuNode('Widgets', '/products/widgets'), 'widgets');

        $root->addComponent(new MenuNode('About', '/about'), 'about');

        return $root;
    }
}

Retrieval mirrors the hierarchy:

$tree->getComponent('products');           // direct child
$tree->getComponent('products/widgets');   // path lookup across the tree
$tree->getComponentTree();                 // ['products' => ['widgets' => []], 'about' => []]

Bind the tree in a service provider if you inject it elsewhere — but see the gotchas about scope before you make it a singleton.

Implementation Patterns

Pattern 1 — Use it as a data-structure layer, not a rendering layer

The component model has no rendering, no HTML, no Blade hooks, no reactivity. Keep it in a plain PHP service, build the tree, then hand a flattened array to Blade/Livewire:

// MenuBuilder::toArray() — flatten for a Blade partial
public function toArray(ComponentContainer $node): array
{
    return array_map(fn ($child) => [
        'name'  => $child->getName(),
        'label' => $child->label,
        'url'   => $child->url,
        'children' => $child instanceof ComponentContainer ? $this->toArray($child) : [],
    ], $node->getComponents());
}

This pattern gives you Laravel-side flexibility (cache the array, serialize to JSON, feed Inertia) while keeping tree logic in a tested, type-hinted place.

Pattern 2 — Lifecycle hooks with monitor()

monitor() is the modern replacement for subclassing attached() / detached(). Register listeners on a container, and they fire as children are added/removed:

$root->monitor(MenuNode::class,
    fn (MenuNode $node) => Log::debug('attached', ['name' => $node->getName()]),
    fn (MenuNode $node) => Log::debug('detached', ['name' => $node->getName()]),
);

Typical day-to-day uses: audit logging of tree mutations, invalidating a cache key when a subtree grows, warming a lookup table, or notifying a UI state store. If you need cross-service communication, dispatch a Laravel event from inside the closure — do not reach for Nette's listener machinery further than that.

Pattern 3 — Path-based lookup for routing and permissions

getComponent('a/b/c') traverses the tree by name. This is handy for permission checks against a nested resource tree (e.g. organizations/acme/billing/invoices) or for resolving a breadcrumb chain from a path string. Combine with getComponentTree() for an easy "list all nodes" endpoint used by an admin panel or a Telescope-style debug view.

Pattern 4 — Wrap it, don't extend, in application code

Subclass Container once per domain concept (CategoryTree, FormSectionTree), then inject the builder, not the tree, via Laravel's DI:

// AppServiceProvider
$this->app->singleton(CategoryTreeBuilder::class);

// Controller / Action
public function __construct(private CategoryTreeBuilder $builder) {}

The builder itself is a plain Laravel service; the Nette classes are implementation details.

Pattern 5 — Enforce naming conventions

Container::NAME_REGEXP is provided. Override addComponent() in your tree subclasses to validate names (slugs, numeric keys, etc.) rather than validating at the call site. Remember addComponent($component, $name) accepts int/string names via ArrayAccess but Container::getComponent() accepts only strings.

Pattern 6 — Use the ArrayAccess trait on your wrappers

Nette\ComponentModel\ArrayAccess gives $container['name'] semantics for free. Useful when you want the tree to feel natural to Laravel developers who are used to array access, without exposing the base class everywhere.

Gotchas and Tips

  • Name collision is the #1 footgun. Nette\ComponentModel\Container is not Illuminate\Container\Container, and Nette\ComponentModel\Component is not Livewire\Component. Always alias. If IDE autocomplete suggests the wrong one, you'll get a very confusing "call to undefined method getComponent()" at runtime.

  • Do not confuse the two containers conceptually. Nette's Container is a tree node registry; Laravel's is a DI container. Do not try to bind Nette's container as a Laravel singleton and then inject it as $app->make(Container::class) — it will 500 in subtle ways. Keep them on separate names.

  • Magic properties are deprecated in 3.2.0 and removed in 4.0. $component->name, $component->parent, $container->components — replace with getName(), getParent(), getComponents() today. Same for IComponent::NAME_SEPARATOR → IComponent::NameSeparator.

  • attached() / detached() overrides are on the way out. 3.2.0 deprecates the old monitor($type) without callbacks and the subclass hook pattern. Migrate to monitor($type, $attached, $detached) now; it normalizes callbacks to \Closure, which matters below.

  • Closure identity for dedup. Callbacks are deduplicated by loose comparison. Passing the same closure instance twice registers once; passing two identical-but-distinct closures can register twice if the engine's loose comparison treats them the same. If you see double-fire in production, hoist your callbacks to properties or static methods.

  • Re-entrancy in refreshMonitors(). Since 3.1.4, refreshMonitors() refuses to re-enter itself while listeners are running. A listener that removes and re-adds the same component won't loop — but also won't do what you might expect. Do not mutate the tree inside a monitor callback unless it's the only clear way; prefer queueing the mutation outside the listener.

  • getComponents($deep) is deprecated. In 3.1.0 the $deep parameter was silently deprecated: when false it returns an array (a BC break from the previous iterator behavior). Always call getComponents() with no arguments in new code and iterate the returned collection. Use getComponentTree() when you actually need nesting.

  • One parent, one name. A component can be attached to only one container, and names must be unique within a parent. Attempting to add a component that already has a parent throws — the exception messages are informative (since 3.0.0, and 2.2.1 improved the "component has parent" message), so read them rather than guess.

  • No Laravel integrations to expect. No auto-discovery, no config, no facade, no Telescope integration, no Blade tag. If you need observability, wrap getComponentTree() in your own debug endpoint or dump it into a Laravel log entry.

  • Serialization is awkward by design. Parent and child hold references to each other. Avoid serialize()-ing a tree directly; build a DTO/array (Pattern 1) or implement __sleep/__wakeup carefully. This bites especially when caching a built tree in Redis.

  • Performance: traversal is linear and fine for hundreds to low thousands of nodes. If you're walking the full tree on every request, flatten it once and cache the array; don't re-walk in a Blade loop.

  • Debugging order of operations: when a node "isn't there," check (a) the name passed to addComponent(), (b) whether the parent you're querying is actually the parent of the node you think it is, (c) whether you're using the path form (a/b) vs the direct lookup. getComponentTree() is the fastest way to see the actual shape. Exception messages also suggest close matches (since 2.2.4), so read them.

  • Upgrade posture: the 4.0 release will remove the deprecations above. Budget a migration when you see it. There are zero Laravel dependents listed, so you're effectively the maintainer of this integration for your app — pin ^3.2 and read release/*.md on upgrade.

  • Extension points to prefer: subclass Container for typed factories, use the ArrayAccess trait for ergonomic wrappers, and use monitor() as the single point of cross-cutting hookup. Avoid reaching into internal $parent / $components state — that's the path that hits the 4.0 wall first.

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.
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
cgv/localized-resource