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

Offline Locker Bundle Laravel Package

text-media/offline-locker-bundle

A Symfony bundle that adds an offline locker to PHP applications, letting users store, organize, and serve media and files locally for offline access. Simple configuration and straightforward integration keep content available without a network connection.

View on GitHub
Deep Wiki
Context7

Getting Started

Reality check first. This bundle has no public stars, no dependents, and no discoverable documentation. Treat it as an internal/private package: the source code is the documentation, and you should budget time to read it before writing a line of integration code. Do not assume Symfony Flex recipe support — it almost certainly has none.

1. Get it installed (private Bitbucket VCS).

{
  "repositories": [
    { "type": "vcs", "url": "[email protected]:text-media/offline-locker-bundle.git" }
  ],
  "require": {
    "text-media/offline-locker-bundle": "dev-main"
  }
}

Authenticate via SSH deploy key or COMPOSER_AUTH with a Bitbucket app password — both locally and in CI. If no tags exist (likely), pin to a commit hash (dev-main#abc1234) rather than a moving branch.

2. Read these files before anything else, in this order:

File What it tells you
composer.json PHP/Symfony version constraints, real dependencies, autoload namespaces
*Bundle.php Bundle class name, whether it registers compiler passes
DependencyInjection/Configuration.php The actual config reference — every option, default, and required value
DependencyInjection/*Extension.php Which services are wired, aliases, tags, parameters
Resources/config/services.* Service IDs, visibility, constructor args
README.md, tests/ Intended usage and the only working examples you'll get

3. Register and configure (Symfony host app).

// config/bundles.php
TextMedia\OfflineLockerBundle\OfflineLockerBundle::class => ['all' => true],
# config/packages/offline_locker.yaml
offline_locker:
    # copy keys from Configuration.php — do not guess

4. Verify the wiring before writing features.

bin/console config:dump-reference offline_locker
bin/console debug:config offline_locker
bin/console debug:container --show-private | grep -i locker
bin/console lint:container

5. First use case. Pick the smallest possible vertical slice: resolve the bundle's main service, push one item into the locker/store, flush it, assert it landed. Write that as a functional test rather than poking it manually — you'll need it as a regression net.

Laravel note: this is a Symfony bundle, not a Laravel package. There is no service provider, no facade, no config/offline_locker.php. Your options, in order of preference: (a) run it in a Symfony service and call it over HTTP/queue; (b) if the core classes are framework-agnostic PSR-compliant services with no ContainerAwareInterface, pull just those classes in and bind them manually in a ServiceProvider; (c) don't — pick a native Laravel offline-queue/outbox implementation. Verify (b) by grepping for Symfony\Component\DependencyInjection, AbstractController, and ContainerInterface in the classes you actually need.

Implementation Patterns

Wrap it behind your own port. A zero-star, unversioned dependency should never leak into your domain code. Define an interface that expresses your need (OfflineQueue::enqueue(), flush()), implement it with an adapter that calls the bundle, and bind the adapter in your container. Swapping the package later becomes a one-file change.

interface Outbox { public function enqueue(Operation $op): void; public function flush(): int; }

final class LockerOutbox implements Outbox
{
    public function __construct(private object $locker) {} // bundle service, ID confirmed via debug:container
    // ...
}

Constructor injection, not container lookups. Symfony services in modern bundles are private by default. Inject the specific service ID or its interface alias — don't reach into $container->get() at runtime.

Offline-first outbox workflow. The natural pattern for anything called "offline locker":

  1. Capture the user action, persist an operation record locally (with a client-generated idempotency key) and return immediately.
  2. Attempt the remote call; on network failure leave the record pending.
  3. Flush pending records from a console command on cron, or from a Messenger worker.
  4. Mark done after the remote confirms; delete or archive.

Trigger flushing from a kernel event, not a controller. A kernel.terminate listener lets you attempt a flush without adding latency to the response.

class FlushLockerListener implements EventSubscriberInterface
{
    public static function getSubscribedEvents(): array
    { return [KernelEvents::TERMINATE => ['onTerminate', -100]]; }
}

Background flush as a Symfony command. Register it in cron or as a Messenger handler so retries and backoff are managed by a real queue rather than a while loop.

Decorator over the bundle service when you need logging, metrics, or a feature flag kill switch without touching the package:

App\Locker\GuardedLocker:
    decorates: 'text_media.offline_locker.locker'   # confirm ID with debug:container
    arguments: ['@.inner', '@logger']

Testing. Spin a minimal test kernel (or reuse KernelTestCase) and point the locker's storage at a temp directory. If the package touches time, wrap it behind a clock or use symfony/clock's MockClock so "still pending after 5 minutes" assertions are deterministic. Add a contract test suite that asserts the bundle's behaviour — that's what catches a silent break on upgrade.

Laravel-side integration. If you extract the classes, bind them in register():

$this->app->singleton(Locker::class, fn ($app) => new Locker($app['config']['services.locker']));

and drive flushing from a queued job + schedule:run rather than Symfony's console.

Gotchas and Tips

Version and stability. No tags means no semver guarantees and no upgrade safety. Pin to a commit SHA. Before any Composer update, diff the two commits and read the changelog if one exists. Add the bundle to your "review on update" list.

Symfony Flex won't help you. Private bundles get no auto-registration and no auto-generated config. If you forget config/bundles.php, the failure mode is an unhelpful "service not found" — not a "bundle not registered" message. lint:container catches this early.

Private repo auth is the #1 CI failure. Use a read-only SSH deploy key in CI. If HTTPS, set COMPOSER_AUTH with the Bitbucket app password, and note that Bitbucket's API auth differs from git-over-HTTPS auth — mixing them produces 401s that look like network errors.

The config tree is the only spec. Configuration.php will tell you which keys are isRequired(), which have defaults, and which silently accept null. Run config:dump-reference once and keep the output in the repo — it's your de facto docs page.

Private services. If debug:container shows the service as private, you cannot $container->get() it. Inject it, alias it, or make it public in a compiler pass — the last is a smell; prefer injection.

Concurrency will bite you. Any "flush pending items" routine run by two workers at once will double-send unless the package uses a proper lock. Grep for flock, LockableTrait, symfony/lock, or a DB SELECT ... FOR UPDATE. If none exist, add your own LockableTrait guard around the flush call and send idempotency keys downstream. Assume at-least-once delivery.

Ephemeral storage. If the locker persists to a filesystem path (likely, given the name), a container redeploy wipes it. Mount a volume, or point config at a durable adapter if one exists. Also check directory permissions — running as a non-root www-data in a container is a common first-deploy failure.

Cache after config changes. Config changes need bin/console cache:clear (dev) or a full deploy; stale containers are a frequent source of "it ignored my option".

Logging is probably sparse. Add a Monolog channel for the bundle's namespace and raise it to debug when integrating. Don't rely on exceptions surfacing — a fire-and-forget offline queue often swallows them by design.

Timezone and clock. "Offline" windows and retry backoff are time-sensitive. Set an explicit timezone and pass a clock into anything you compute, rather than calling time() inline.

Extension points, cheapest first: configuration options → decorating the service (decorates:) → an event subscriber on the bundle's dispatched events (grep for dispatch() → a compiler pass for tagged services → forking the repo. Only fork as a last resort, and if you do, keep the fork in the same namespace so Composer replace can swap it out.

Bus-factor mitigation. This package has effectively no community. Assume it will go unmaintained. Keep the adapter boundary thin, have your contract tests in place, and identify a replacement before you need one — this is the single most valuable hour you'll spend on it.

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