demos-europe/demosplan-core
Core library for demosplan, an open-source digital participation platform used in urban planning and public consultation. It provides shared domain models, services, and workflow logic for managing procedures, statements, and planning documents in PHP applications.
demosplan-core is not a Laravel package — it's a Symfony bundle (namespace DemosEurope\DemosplanCoreBundle) that ships the domain model for demosplan, a German municipal public-participation platform (procedures, statements, consultations, files). A Laravel mental model will actively mislead you: no ServiceProvider, no auto-discovery, no facades, no Eloquent, no Blade, no Artisan. Treat it as a Composer library consumed by a Symfony host app.
Minimum steps:
composer.json — the bundle expects specific symfony/* majors; mixing in a Laravel 10/11 app is not supported.composer require demos-europe/demosplan-core
config/bundles.php:
DemosEurope\DemosplanCoreBundle\DemosplanCoreBundle::class => ['all' => true],
config/routes.yaml and config/packages/ — the bundle ships YAML defaults that the host is expected to override (demosplan_core.yaml, Elasticsearch, permissions).bin/console demosplan:elasticsearch:index
First use case: load the Verfahren (procedure) list page, click into a procedure, view a Stellungnahme (statement). That single path wires up Doctrine entities, the statement service, ES-backed search, and the Twig views — enough to see the shape of the package before you start extending anything.
Where to look first in the repo: src/Entity (Doctrine entities), src/Services (domain logic), src/Controller (routes), src/Command (console commands), src/Resources/config (services and YAML defaults), and tests/ for the expected wiring.
Composition over inheritance. The bundle exposes services; extend behaviour by decorating them, not subclassing controllers.
final class LoggingStatementService implements StatementServiceInterface
{
public function __construct(
private StatementServiceInterface $inner,
private LoggerInterface $logger,
) {}
public function getStatement(string $id): Statement
{
$this->logger->info('statement.fetch', ['id' => $id]);
return $this->inner->getStatement($id);
}
}
services:
App\Service\LoggingStatementService:
decorates: demosplan_core.statement.service
arguments: ['@.inner', '@logger']
Extend the entity graph via Doctrine inheritance, not migrations of the vendor schema. If you need extra fields on procedures or statements, map them in your own entity extending the bundle's, and register an attribute override. Editing src/Entity/* in vendor/ will be wiped on composer update.
Hook into the domain with Doctrine listeners and Symfony event subscribers. There are no Laravel-style model observers; use doctrine.event_listener for entity lifecycle and kernel.event_subscriber for application events (statement creation, assignment, notification). This is where integrations — e.g. pushing new statements to a CRM — belong.
Reuse the repository layer. The bundle's repositories extend ServiceEntityRepository; inject them rather than writing raw DQL, especially for anything touching statements, where ES and Doctrine are kept in sync and bypassing the repo will drift them.
Async work is expected. Statement import, ES indexing, and mail are pushed through RabbitMQ workers. In dev, run:
bin/console messenger:consume async -vv
Otherwise statements appear to "not save" when they're queued.
Console commands are the integration surface for batch work. Look under src/Command for demosplan:import:*, demosplan:elasticsearch:*, and maintenance tasks; wrap them in cron rather than re-implementing logic.
Config layering. The bundle ships Resources/config/*.yaml defaults; override under the host's config/packages/demosplan_core.yaml. Prefer explicit overrides over env-var-only tweaks — many keys are read at container compile time.
NOASSERTION. Read the actual LICENSE file in the repo (demosplan historically ships EUPL-1.2) before you depend on this in a commercial product. NOASSERTION means the packagist metadata could not be normalised — it does not mean "no license", but you cannot assume permissiveness. Get this cleared by legal first.DemosplanCoreServiceProvider, no config publishing, no facades, no Blade. If your team is Laravel-only, budget for a Symfony ramp-up or pick a lighter public-participation package.__construct requirements. Entities with mandatory constructor args require named constructors; don't new Statement() in your own code, use the factory/service or Hydrators. Lazy proxies will bypass constructors in ways that are surprising if you're used to Eloquent's "new model with defaults".php bin/console cache:clear after any config YAML change. The bundle compiles substantial DI and config; stale container errors look like code bugs but are cache.DATABASE_URL, ELASTICSEARCH_*, REDIS_*, MESSENGER_*, MAILER_*, plus demosplan-specific keys. Diff the bundle's .env/config against your app; missing keys fail late at runtime, not at boot.kernel.event_subscriber, and custom console commands. Avoid forking the bundle — track upstream demos-europe/demosplan-core releases and rebase your decorators.How can I help you explore Laravel packages today?