Skip to content

SymPress Framework Bundle

Checks Release PHP Downloads License: GPL-2.0-or-later Security Policy

sympress/framework-bundle is a SymPress bundle that brings Symfony-style framework services into WordPress projects. It now uses Symfony's real symfony/framework-bundle as the compatibility layer and keeps the SymPress WordPress object-cache integration on top.

The core surfaces are:

  • Symfony FrameworkBundle web/kernel services such as request stack, HTTP kernel, controllers, error rendering, routing infrastructure and console integration.
  • Symfony Cache 8.2 based PSR-6, PSR-16 and Symfony Contracts cache services.
  • Framework-Bundle-style cache pool services such as cache.app, cache.system, cache.validator, cache.serializer, adapter prototypes, clearers, pruners, tag-aware pools, Redis tag-aware pools, named custom pools and cache:pool:* console commands.
  • WordPress object-cache.php drop-in support with a Symfony-oriented operational surface: APCu, Redis, Memcached, filesystem, SQLite/PDO, request-memory caching, multisite global groups, non-persistent groups, group flushing, runtime flushing, admin-bar flush, WP-CLI flush/purge and scheduled purge.

Optional FrameworkBundle integrations such as Form, Validator, Serializer, Messenger, Mailer, Notifier, HttpClient, Workflow, Lock, RateLimiter, UID, WebLink and Webhook are enabled through Symfony's upstream configuration whenever the matching Symfony component is installed.

Install

Require the package in a SymPress project:

composer require sympress/framework-bundle

Projects must provide a randomly generated APP_SECRET of at least 32 bytes. Missing or shorter secrets fail FrameworkBundle compilation. The bundle passes APP_SECRET directly to Symfony's FrameworkBundle and does not fall back to predictable project paths.

The bundle is discovered through Composer metadata:

{
  "extra": {
    "kernel": {
      "bundle": "SymPress\\Framework\\SymPressFrameworkBundle",
            "entry": "framework-bundle"
        }
    }
}

Local Development

Run the package checks before opening a pull request:

composer qa

The QA script runs PHPCS, PHPStan and PHPUnit. The GitHub workflow uses the shared SymPress workflow set and the organization .github repository provides issue and community templates.

The separate cache-backend smoke runs the native adapters against real Redis and Memcached services in CI. Run it locally with both PHP extensions and services available:

SYMPRESS_LIVE_CACHE_TESTS=1 composer tests:backends

Cache Pool Configuration

The bundle provides defaults that work without project config. Projects can override the framework.cache parameter in a SymPress config file:

<?php

use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;

return static function (ContainerConfigurator $container): void {
    $container->parameters()->set('framework.cache', [
        'app' => 'cache.adapter.redis',
        'default_redis_provider' => 'redis://redis:6379',
        'pools' => [
            'cache.marketing' => [
                'adapter' => 'cache.adapter.filesystem',
                'default_lifetime' => 600,
                'tags' => true,
                'public' => true,
            ],
            'cache.remote' => [
                'adapters' => [
                    ['name' => 'cache.adapter.redis', 'provider' => 'redis://redis:6379'],
                    'cache.adapter.array',
                ],
            ],
        ],
    ]);
};

Symfony-style extension configuration is also supported:

framework:
  secret: '%env(APP_SECRET)%'
  router:
    resource: '%kernel.project_dir%/config/routes.yaml'
  cache:
    app: cache.adapter.redis
    default_redis_provider: 'redis://redis:6379'

Pool configuration accepts Symfony-style adapter or adapters keys, provider-specific adapter entries, tags, default_lifetime, marshaller, clearer and early_expiration_message_bus tag attributes. cache.adapter.redis_tag_aware and cache.adapter.valkey_tag_aware are treated as native tag-aware pools instead of being wrapped in a generic TagAwareAdapter.

Object Cache Configuration

The WordPress drop-in can be configured with constants or environment variables. Constants take precedence; native Runtime/Symfony Dotenv values in $_ENV and $_SERVER are consumed before the process environment. Credentials from .env need no putenv() export to child processes:

define('SYMPRESS_CACHE_DRIVER', 'redis');
define('SYMPRESS_CACHE_DSN', 'redis://redis:6379');
define('SYMPRESS_CACHE_IN_MEMORY', true);
define('SYMPRESS_CACHE_PURGE_INTERVAL', 43200);
define('SYMPRESS_CACHE_SECRET', getenv('APP_SECRET'));

Supported drivers are array, filesystem, apcu, redis, memcached, pdo, sqlite and null. SYMPRESS_CACHE_DRIVER_ARGS accepts JSON or base64-encoded JSON for driver-specific settings. SYMPRESS_CACHE_BYPASS can be set as a constant or environment variable to disable the drop-in for emergency operations.

Object Cache Drop-In

The canonical drop-in source is vendor/sympress/framework-bundle/dropin/object-cache.php. Projects managed by sympress/runtime should publish that file into WP_CONTENT_DIR/object-cache.php through the SymPress Runtime dropins step:

{
    "dropins": {
        "object-cache.php": "vendor/sympress/framework-bundle/dropin/object-cache.php"
    }
}

Run only the drop-in publish step when the source changes:

composer sympress-runtime dropins --no-interaction

Projects without SymPress Runtime can explicitly call DropInInstaller::install() during setup, or dispatch sympress_setup_object_cache. The bundle never scans or publishes the drop-in during ordinary requests. Installation respects wp_is_file_mod_allowed() and DISALLOW_FILE_MODS. Managed SymPress drop-ins are only rewritten when their contents change and third-party drop-ins without the sympress-framework-object-cache marker are left untouched by the runtime installer. SymPress Runtime remains the preferred owner in SymPress Runtime projects because it publishes the drop-in during Composer/project setup instead of an explicit application setup step.

The delegator resolves the Composer autoloader from SYMPRESS_PROJECT_DIR, APP_PROJECT_DIR, WP_CONTENT_DIR, ABSPATH, or nearby parent directories; SYMPRESS_COMPOSER_AUTOLOAD and SYMPRESS_OBJECT_CACHE_FUNCTIONS can override those paths for custom layouts. This makes the same file work when copied by SymPress Runtime, symlinked from content-dev, or installed by the runtime fallback. If a persistent backend cannot be initialized, the drop-in logs or warns about the backend failure before falling back to request-local array cache. WP-CLI cache flushes run directly in the current CLI process and never create temporary PHP endpoints in the web root. Redis and Memcached use native object-cache backends instead of Symfony internals for WordPress counter semantics. add, replace, incr and decr are mapped to backend-native atomic operations where the backend supports them; Redis counters use a Lua script so missing keys are not created and decrements clamp to zero like WordPress expects. Existing non-numeric counter values follow WordPress core semantics and are treated as zero. Flushes use versioned namespaces, so group/runtime invalidation does not depend on scanning or reflecting backend internals. The other Symfony-backed drivers keep best-effort semantics because PSR-6 does not expose cross-process compare-and-swap primitives.

All persistent WordPress drivers require SYMPRESS_CACHE_SECRET or APP_SECRET of at least 32 bytes; AUTH_KEY is not a signing-secret fallback. Missing or short object-cache secrets disable persistence and report the initialization failure before using request-local cache. Redis and Memcached retain native counter operations; APCu, filesystem and SQLite/PDO authenticate serialized values with the same signed codec before reconstructing objects. Treat persistent cache backends and the kernel cache directory as trusted infrastructure; do not expose them to untrusted writers.

WordPress cache prefixes include a hash of the project root and environment, including when SYMPRESS_CACHE_PREFIX is set. Roots resolve from SYMPRESS_PROJECT_DIR, APP_PROJECT_DIR, ABSPATH, WP_CONTENT_DIR, then the working directory. Set a stable project root before loading the drop-in in custom layouts. Environment resolves from APP_ENV, APP_RUNTIME_ENV, WP_ENVIRONMENT_TYPE, then production. The signing key is also bound to this scoped prefix. This changes the cache identity on upgrade; existing cache records remain unused and refill normally.

Operational notes:

  • composer sympress-runtime dropins preserves unmanaged targets in native mode and respects prevent-overwrite. Do not enable this drop-in alongside another object-cache drop-in.
  • The portable delegator performs a few early is_file() checks to resolve the Composer autoloader. For standard SymPress Runtime layouts this avoids absolute build paths while keeping request overhead small.
  • Existing deployments with an older generated drop-in keep using it until SymPress Runtime republishes the file or an explicit setup installer rewrites a managed SymPress drop-in.

Cache payload and filesystem boundaries

When a secret is configured, unsigned sympress-cache-v1 and unframed string payloads are misses; legacy decoding remains available only to the explicitly secretless codec. Counter integers remain native numeric values. Signed payloads validate the HMAC before deserializing. The codec permits only stdClass, WP_Post, WP_Term, WP_Comment, WP_User, WP_Error, WP_Site, WP_Network, DateTime, DateTimeImmutable, and DateTimeZone. Unsupported objects (including nested objects) are rejected rather than invoking application magic methods. Custom application cache objects need a reviewed codec extension or plain data arrays.

Filesystem and default SQLite cache paths use a private per-user temporary directory with a separate project/environment namespace and mode 0700. APCu still requires trusted PHP-FPM pools: the extension can deserialize PHP objects written directly through its API before userland code runs. The SymPress marshaller writes signed strings only. Directories beneath WP_CONTENT_DIR or the declared HTTP document root are rejected; configure a private path outside every location served by your web server. Initialization failure diagnostics include the exception class and never the exception text, which can contain a backend DSN or credentials.

wordpress.content_url is an environment placeholder resolved through WordPressContentUrlProcessor when the container runs, so container compilation does not capture a build host URL. Its runtime value comes from content_url('/').

About

Provides a tight integration between Symfony components/bundle and wordpress

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages