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

Public Events Bundle Laravel Package

elefantlabs/public-events-bundle

Elefant Labs Public Events Bundle is a PHP/Symfony package for adding public event listings to your application, with reusable components for managing events, rendering calendars, and integrating event content into existing sites.

View on GitHub
Deep Wiki
Context7

Getting Started

First, settle the framework question — it determines everything else. elefantlabs/public-events-bundle follows Symfony's naming convention. Assume it is not Laravel-native: no extra.laravel block, no ServiceProvider, no facade, no config/migration publishing, no auto-discovery. Before writing a line of integration code, run:

composer require elefantlabs/public-events-bundle
composer show -D elefantlabs/public-events-bundle   # read "requires" carefully

Then read composer.json in the vendor directory. What you're looking for:

  • symfony/framework-bundle, symfony/dependency-injection, twig/twig in require — you cannot boot this thing as-is. You'd have to install a chunk of Symfony alongside Laravel. Stop here; consider forking just the data classes.
  • Only psr/*, symfony/http-client-contracts, nesbot/carbon — good news. The services are probably plain PHP classes you can new up yourself, and the bundle wrapper is just a thin DI shim you can ignore.

That distinction is your entire integration strategy. Where to look first, in order:

  1. DependencyInjection/Configuration.php — the real config schema. This is the source of truth for every option, default, and type. No equivalent exists in Laravel.
  2. Resources/config/services.xml (or .yaml) — tells you the actual service class names, their constructor arguments, and which interfaces they're aliased to. This is your map.
  3. Provider/ or Service/ — the class that actually returns events. That's your entry point; everything else is plumbing.
  4. Resources/views/ — Twig templates. Read them to understand what data the bundle considers canonical for an event, then ignore them permanently.

Your first use case — a /events page listing upcoming events — should be built as an adapter over the provider service, not as an attempt to mount the bundle's routes. Sketch:

// app/Services/Events/BundleEventSource.php
namespace App\Services\Events;

use ElefantLabs\PublicEventsBundle\Provider\EventProviderInterface; // verify real FQCN

class BundleEventSource
{
    public function __construct(private EventProviderInterface $provider) {}

    /** @return \App\Services\Events\EventDto[] */
    public function upcoming(int $limit = 20): array
    {
        return collect($this->provider->findUpcoming())   // verify method name
            ->map(fn ($raw) => EventDto::fromSource($raw))
            ->sortBy(fn (EventDto $e) => $e->startsAt)
            ->take($limit)
            ->values()
            ->all();
    }
}
// app/Providers/PublicEventsServiceProvider.php — register it yourself
public function register(): void
{
    $this->app->singleton(EventProviderInterface::class, function () {
        return new \ElefantLabs\PublicEventsBundle\Provider\EventProvider(
            config('public-events.feed_url'),
            // copy constructor args straight out of Resources/config/services.xml
        );
    });
}

Add the provider to bootstrap/providers.php (Laravel 11+) or config/app.php. That's the whole "installation": manual wiring. There is no php artisan vendor:publish step that will help you.

Implementation Patterns

Adapter + DTO is the pattern, not an optional nicety. Never let the bundle's objects leak into Blade, controllers, or API resources. Keep every bundle type behind one class (BundleEventSource) so that when — not if — the package dies, you swap the internals and nothing else moves.

final class EventDto
{
    public function __construct(
        public string $id,
        public string $title,
        public CarbonImmutable $startsAt,   // always convert to Carbon at the boundary
        public ?CarbonImmutable $endsAt,
        public ?string $venue,
        public string $url,
    ) {}

    public static function fromSource(object $raw): self
    {
        return new self(
            id:       (string) ($raw->id ?? $raw->uuid ?? md5($raw->title.($raw->start ?? ''))),
            title:    (string) $raw->title,
            startsAt: CarbonImmutable::parse($raw->start ?? $raw->startsAt)->utc(),
            endsAt:   isset($raw->end) ? CarbonImmutable::parse($raw->end)->utc() : null,
            venue:    $raw->venue ?? $raw->location ?? null,
            url:      route('events.show', $raw->slug ?? $raw->id),
        );
    }
}

Cache at the adapter, keyed by the query. Public event data is read-mostly and remote-event bundles are slow. This is the single highest-value thing you do all day.

public function upcoming(int $limit = 20): array
{
    return Cache::remember(
        "public-events:upcoming:{$limit}",
        now()->addMinutes(15),
        fn () => $this->fetchUpcoming($limit)
    );
}

Warm the cache out-of-band. Don't let the first visitor of the morning pay the latency, and don't let a slow upstream take down your /events page:

// routes/console.php (or Kernel::schedule)
Schedule::call(fn () => app(BundleEventSource::class)->upcoming(50))
    ->everyFifteenMinutes()
    ->withoutOverlapping();

Wrap the fetch in try/catch and fall back to Cache::get(...) stale data rather than throwing — a stale events list beats a 500.

Do filtering, sorting, and pagination in your layer. The bundle's provider almost certainly returns a flat collection with, at best, an upcoming/past split. Sort, paginate, and filter by venue/category yourself using LengthAwarePaginator::resolveCurrentPage():

$page = LengthAwarePaginator::resolveCurrentPage();
$items = collect($this->source->upcoming(200))
    ->filter(fn (EventDto $e) => $request->filled('venue') ? $e->venue === $request->venue : true)
    ->values();
$events = new LengthAwarePaginator($items->forPage($page, 12)->values(), $items->count(), 12);

Custom event sources — use the interface, not the config. If you need events from your own events table, implement the bundle's provider interface (if one exists) and rebind it, rather than fighting whatever remote source is hardcoded:

$this->app->bind(EventProviderInterface::class, DatabaseEventProvider::class);
// or decorate, if you want the bundle's source as a fallback:
$this->app->extend(EventProviderInterface::class, fn ($inner) => new ChainProvider($inner, new DatabaseEventProvider()));

Render with Blade from DTOs. Do not attempt to load the bundle's Twig views, do not install Twig, do not translate templates by hand at runtime. Read them once as documentation for what fields exist, then write your own Blade partials. Same for routes — define them in routes/web.php you own, so URL structure and middleware (auth, throttling, caching headers) stay under your control.

Test the seam, not the vendor. In tests, bind a fake BundleEventSource (or fake the provider interface) and never hit the network. That keeps your suite fast and makes it obvious how much of your app actually depends on this package.

Gotchas and Tips

The framework mismatch is the whole assessment. Zero stars, zero dependents, and a Symfony bundle wrapper means no one has run this under Laravel and reported back. Any hour you spend on integration is an hour with no Stack Overflow answers behind it. Budget it explicitly, and be ready to abandon.

The bundle's wrapper is dead weight; the plain classes are the asset. Symfony DI features — tagged services, compiler passes, service decoration via services.yaml, autoconfiguration — will not run under Laravel. If the bundle relies on a compiler pass to assemble its provider, you must replicate that assembly by hand in your own ServiceProvider. Read PublicEventsBundle.php and any CompilerPass/ directory before assuming a simple new works.

Symfony contracts leak into constructors. You will frequently find Symfony\Component\HttpClient\HttpClientInterface, Symfony\Contracts\Cache\CacheInterface, or Psr\Log\LoggerInterface in a constructor signature. Bind Laravel equivalents where the contract matches (PSR log is fine, PSR-6 cache usually isn't), and otherwise instantiate the Symfony concrete class directly and accept the extra dependency — or, better, fork the class to accept Illuminate\Http\Client\Factory.

Check for HTTP timeouts before you ship. This is the most likely production incident. A bundle written for a Symfony app may rely on the app's globally-configured framework.http_client.default_options. Under Laravel there is no such config, so requests get the Symfony default (no timeout / very long). Set them:

$provider = new EventProvider(
    feedUrl: config('public-events.feed_url'),
    httpClient: HttpClient::create(['timeout' => 5, 'max_duration' => 8]),
);

Timezone and DST. Event data is usually stored or transmitted in UTC and formatted in a source locale. Do not trust any pre-formatted date string the bundle returns — find the raw timestamp, parse it explicitly with CarbonImmutable::parse($x, 'UTC'), store UTC, and convert to config('app.timezone') only at render time. Test with an event that crosses a DST boundary and one in a different zone.

Mirror the config schema by hand. Configuration.php has typed nodes with defaults. Laravel's config() validates nothing. Create config/public-events.php and copy defaults from the Symfony tree literally, or you'll get null where the bundle expected 'list' and it'll fail somewhere far from the cause.

Missing/incorrect constructor args fail late. With no container and no debug:container, a wrong argument surfaces as a confusing type error at request time. Mitigate with a single make:-style factory method on your adapter that does all construction in one place, and log the resolved configuration once at boot:

Log::debug('public-events wired', ['source' => get_class($provider), 'feed' => config('public-events.feed_url')]);

Pin the version exactly, and plan to vendor it. "elefantlabs/public-events-bundle": "1.0.3" — not ^1.0. With no downstream users, a minor bump can break you and nobody will file an issue. Better: commit the vendor directory or copy the handful of classes you use into app/Support/PublicEvents/ and own them. Deleting a dependency you've forked is far cheaper than debugging one you can't.

Do a security pass before production. Read every file the bundle executes: hardcoded endpoints, dump()/dd()/var_dump() left in, missing htmlspecialchars/escaping in anything rendered, eval, unserialize, unpinned transitive deps. No one else has audited this.

Extension points worth finding, in priority order: (1) an EventProviderInterface — lets you swap the data source entirely; (2) Symfony event subscribers dispatched on fetch — you can hook post-processing by registering a Laravel listener only if you manually instantiate the dispatcher; (3) service decoration — works in Laravel via $this->app->extend(), which is your cleanest override without forking. If none of these exist, treat the package as a reference implementation and copy the code.

What this package does not do, and you will be asked about it: recurring events (RRULE), ticketing, registration, capacity, structured data (schema.org/Event), and iCal feeds. If any of those land on the roadmap, the bundle gets you no closer — and the sooner that's stated, the less re-litigation later.

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.
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
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