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

Repository Laravel Package

puli/repository

A PHP repository component for managing resource path and URL mappings. It provides a central registry for locating files across packages, supports filesystem and URL repositories, and integrates with Puli’s resource discovery system.

Deep Wiki
Context7

Getting Started

Install the package on its own — it has no framework dependency:

composer require puli/repository

First place to look: Puli\Repository\Api\RepositoryInterface and Puli\Repository\Api\ResourceInterface. Those two interfaces are the entire contract you program against; everything else (FileRepository, Resource, ResourceCollection, the stream wrapper) is an implementation detail.

Minimal smoke test:

use Puli\Repository\FileRepository;
use Puli\Repository\Resource\Resource;
use Puli\Repository\StreamWrapper;

$repo = new FileRepository(__DIR__.'/storage/puli');

// A resource is a path + a stream payload + optional metadata
$repo->add('/assets/app.css', new Resource('/assets/app.css',
    fopen(__DIR__.'/public/css/app.css', 'r')
));

StreamWrapper::register($repo);

echo file_get_contents('puli://repo/assets/app.css'); // => CSS contents

First real use case: treat it as a virtual filesystem for things that have no fixed disk location — compiled view caches, plugin/package assets, generated config, uploaded blobs — so application code addresses them by logical path (/themes/admin/logo.png) rather than by a physical directory that varies per environment.

If you arrived here via Puli itself, note that the discovery/management layer lives in puli/manager and the CLI in puli/cli; this package is just the storage engine and is perfectly usable alone.

Implementation Patterns

Bind it once, register the wrapper once. In Laravel, a singleton in a service provider is the natural home. FileRepository is cheap to construct but the stream wrapper is process-global, so registration belongs in boot(), not in a resolver:

$this->app->singleton(RepositoryInterface::class, fn () =>
    new FileRepository(config('puli.root'))
);

// boot()
StreamWrapper::register($this->app->make(RepositoryInterface::class));

Separate authoritative sources from generated output. Resource takes an $authoritative flag (and metadata). Mark user-owned/plugin-shipped files authoritative; mark caches, compiled templates and thumbnails non-authoritative so a "rebuild" pass can safely wipe only the derived ones by filtering on isAuthoritative().

Query by glob, not by walking. find() accepts shell-style patterns (*, **, ?, {a,b}) and returns a ResourceCollection, which is iterable and countable:

foreach ($repo->find('/themes/*/**.css') as $resource) {
    $css .= $resource->getContents();
}

This is the single biggest day-to-day win over a plain directory: one query returns assets across many packages that don't live under a common real path.

Batch mutations in a transaction. When an install/upgrade step touches many paths, wrap it so a partial failure doesn't leave the repository half-updated:

$repo->beginTransaction();
try {
    foreach ($package->getAssets() as $path => $file) {
        $repo->add($path, new Resource($path, fopen($file, 'r')));
    }
    $repo->commit();
} catch (\Exception $e) {
    // commit() is what persists; discarding the repo instance aborts
    throw $e;
}

Store derived data in metadata, not in the payload. ETags, mime types, origin package name, and build timestamps belong in the $metadata array (it is JSON-serialized alongside the path map). That lets you answer "which package owns this asset" without opening every stream.

Use references for aliasing. Two paths can point at the same underlying resource; getReferences($path) enumerates every path that resolves to a given resource. Handy for shared vendor assets (e.g. /vendor/jquery.js and /themes/admin/js/jquery.js) so that deleting one alias doesn't orphan the data — check the reference list before removing.

Decorator pattern for cross-cutting concerns. Because the whole API is RepositoryInterface, a caching or logging decorator is trivial and is the intended extension point:

class CachingRepository implements RepositoryInterface
{
    public function get($path) { /* hit redis, else delegate */ }
    // ... delegate everything else
}

Keep the decorator stateless; the underlying FileRepository is the lock-holder.

Gotchas and Tips

This is effectively unmaintained. Last release February 2016. It predates PHP 7.2 features and was written for PHP 5.5/7.0; expect deprecation noise and possible breakage on PHP 8.1+ (stream-wrapper methods returning false where int is expected, null-to-string coercions). Pin your PHP version or vendor-fork it before betting production on it.

The stream wrapper is global per scheme. StreamWrapper::register() claims the puli:// protocol for the whole process. Two repositories means two scheme names, and in tests the registration leaks across test cases — call StreamWrapper::unregister() in tearDown(), or test against $repo->get() directly and skip the wrapper entirely.

Do not include from puli:// casually. Including a user-space stream URL depends on allow_url_include, which is disabled by default and deprecated/removed on modern PHP. Use $resource->getContents() or file_get_contents() instead of treating the wrapper as an autoloader or template loader.

Streams are consumable. A Resource payload is a live stream handle. Read it once and stash the string, or rewind() before a second read. Passing the same Resource instance to two consumers is a classic source of empty files.

Paths are strict. They must be absolute, /-separated, normalized, with no .. segments and no trailing slash (root / excepted). Violations surface as InvalidPathException, not as a helpful "did you mean" message — validate at your boundary.

Check overwrite semantics before you rely on them. add() on an existing path throws DuplicatePathException in the default configuration; do not assume upsert. Guard with has() + remove() or catch the exception explicitly, otherwise your second deploy of the same asset set fails at runtime.

remove() with descendants. Non-recursive removal of a parent path fails while children exist; you'll get an exception rather than a cascade. Decide explicitly which behaviour each call site wants.

Don't share a repository root across machines. FileRepository uses file locks, which behave unreliably on NFS and across container replicas. Keep the root on a local volume, or give each replica its own root and rebuild it.

Never hand-edit the on-disk layout. Payloads live under hashed names and the path↔hash map is a serialized index at the repository root. Grepping for a filename will not find it, rm-ing a hash will corrupt the index, and the mapping format is not part of the public API. All inspection should go through find() / get().

Debugging tips: when a lookup unexpectedly misses, dump iterator_to_array($repo->find('/**')) to confirm what the index actually contains; when a read returns false, verify the wrapper is registered and that the resource wasn't already consumed; when commit() throws, the in-memory journal is gone and nothing partial was written — treat that as your rollback.

Extension points worth knowing: implement RepositoryInterface for a non-filesystem backend (S3, database) and reuse the stream wrapper unchanged; implement ResourceInterface for lazy/computed payloads; and swap FileRepository for a subclass if you need custom locking. Everything above the two interfaces is replaceable.

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.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo