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

Wordpress Bundle Laravel Package

metabolism/wordpress-bundle

Bridges WordPress and Symfony by bootstrapping WordPress inside a Symfony bundle. Query posts, pages, and taxonomies through Symfony services, then render them with Twig and your existing routing. Ideal for hybrid CMS or headless-style setups.

View on GitHub
Deep Wiki
Context7

Getting Started

Same framing as before: this is a Symfony bundle, not a Laravel package — zero Laravel equivalent exists in this shape, and with ~75 stars and no dependents you should treat it as a small-maintainer dependency. What it does is boot WordPress core inside the Symfony kernel (not embed Symfony inside WordPress): it defines the WP constants (WP_HOME, WP_SITEURL, WP_CONTENT_DIR, WP_PLUGIN_DIR, salts, table prefix, auto-update flags) from Symfony config before loading WP, then exposes WP functions, $wpdb, a Wordpress wrapper service, a security user provider, and (new in 3.0.2) the registered block registry to Twig.

Read the version constraints before you install. 3.0.2 drops Symfony 4 support and adds WordPress 7 support. In practice: you need Symfony 5.4+ (5.x/6.x/7.x), and you need to be prepared to run WP 7 core. Two notes for a Laravel dev — (a) dropping a platform major inside a patch release (3.0.2) is a semver smell, so pin exactly and read the diff before composer update; (b) if your Laravel app pulls symfony/* transitively via illuminate/support, an old symfony/http-foundation etc. can now conflict where it previously resolved.

Minimal path:

  1. composer require metabolism/wordpress-bundle:3.0.2 and register it in config/bundles.php (or AppKernel). Confirm composer why-not doesn't show a Symfony 4 hold-over in your tree.
  2. Put a WP core tree where the bundle can reach it — best for reproducibility is roots/wordpress in vendor/ pinned by composer.lock, and pin it to a WP 7.x line so you're on the support path this release targets.
  3. Add the config block: wordpress_directory, table_prefix, wordpress_home_url, wordpress_site_url, content/plugin/upload directories, salts. Keep wordpress_auto_update_* false everywhere.
  4. Point a second Doctrine connection (or none) at the same database — $wpdb is mysqli, not your PDO connection.
  5. Prove it: dump get_bloginfo('name'), $wpdb->get_var('SELECT COUNT(*) FROM wp_posts'), and — new — blog.registered_blocks from a Twig template. If all three work, bootstrap and the new block-registry exposure are both sound.

Your first real use case is still headless content: a controller returning JSON built from get_posts()/WP_Query. The 3.0.2 feature suggests a second, increasingly common one: block-theme/block-template rendering, where you need the list of registered block names to build allowlists.

If you're a Laravel dev inheriting this: services.yaml → service providers, bundle config → config/wordpress.php, WpUserProvider → custom guard/user provider, bundle console commands → artisan commands, and the new blog.registered_blocks Twig variable → a view composer or Blade @php/helper exposing WP_Block_Type_Registry::get_instance()->get_all_registered(). In greenfield Laravel, corcel/corcel remains the idiomatic fit for content reads.

Implementation Patterns

Read through a repository, not through globals. Wrap WP_Query/$wpdb->prepare() in a service that maps WP_Post objects to immutable DTOs. Always pass 'no_found_rows' => true, 'update_post_meta_cache' => false, 'update_term_meta_cache' => false unless you need them.

Lazy-boot WordPress. Keep the Wordpress service lazy so routes and console commands that never touch content don't pay 30–80 ms and tens of MB of plugin loading. A kernel.controller subscriber that boots only for content routes is the cleanest lever.

Use blog.registered_blocks for block allowlists — and only for that. The 3.0.2 addition surfaces the registry to Twig, which is exactly what you want when rendering InnerBlocks with a restricted set:

{% set filtered_blocks = blog.registered_blocks|filter(block => block != 'acf/accordion') %}
<InnerBlocks allowedBlocks='{{ filtered_blocks|slice(0)|json_encode|raw }}'/>

Three practical rules: treat the list as environment-dependent (plugins register/unregister blocks, so dev ≠ prod ≠ editor preview), snapshot it after plugins have loaded (the variable is only meaningful post-boot — don't read it from an early listener), and never cache the raw list as a rendered fragment keyed by something generic (see the cache-key note below). In a Laravel/Blade port this becomes a helper around WP_Block_Type_Registry::get_instance()->get_all_registered() returning block names only.

Auth: let WP own the passwords. Wire WpUserProvider under security.providers, then a firewall with form_login/json_login. It verifies via wp_check_password(), the only thing that handles legacy $P$B… phpass hashes and bcrypt. Map WP roles to Symfony roles in the provider — nothing does that translation for you.

Writes go through WordPress. wp_insert_post(), wp_set_object_terms(), the WP REST API — never raw SQL — so hooks, term counts, and caches stay consistent.

Keep wp-admin off the Symfony front controller. Serve /wp-admin and /wp-login.php directly from the web server. Those paths are full of auth_redirect(), wp_die(), and exit; routing them through the kernel fights your firewall and response lifecycle.

Workers and queues need explicit resets. Boot once, then after each message: wp_reset_postdata(), wp_reset_query(), clear the global $post, flush the object cache. WP's hook registry and globals are process-wide and accumulate.

Cache deliberately now that keys are fixed. 3.0.2 repaired weak cache key generation. That means anything you cache through the bundle is now correctly namespaced — and anything you cached before the upgrade may be sitting under collidable keys. Use the fix as the moment to define your own cache contract: key on (template, block list hash, post ID, plugin set), and don't rely on the bundle's internal key shape staying stable across 3.x.

Gotchas and Tips

__() will fatal on you. WordPress defines a global __($text, $domain); so does Laravel's translator helper. Bridging a Laravel codebase (or anything pulling in illuminate/support) into a process that loads wp-includes/l10n.php gives cannot redeclare __(). Still the single largest practical hazard for a Laravel developer here, and still not something the bundle can paper over.

Upgrading to 3.0.2 is not a no-op. Dropping Symfony 4 support in a patch release means composer update can hard-fail on an otherwise-unrelated lockfile, and WP 7 support means core internals you were implicitly relying on (block registration ordering, default block set, REST shapes) may have moved. Do the upgrade in one commit: bump the bundle, bump pinned WP core to 7.x, flush all cache pools, re-run your block-list-dependent templates.

Flush caches on deploy after this release. The weak-cache-key fix implies previous versions could collide entries between contexts (variants, users, block sets). Stale entries created by 3.0.1 and earlier are still readable — treat the upgrade as a cache-invalidation event, particularly for anything storing block lists or rendered block markup.

Block lists are a runtime fact, not a config value. blog.registered_blocks reflects whatever plugins loaded this request. In prod that's your full plugin set; in a worker or a console command with plugins partially loaded, it can silently differ — and an InnerBlocks allowlist computed from a short list will strip valid blocks from saved content on next save. Validate saved block names against the server-side registry before persisting, not against the template output.

Constant double-definition. If a real wp-config.php sits next to the core, both it and the bundle define() the same constants — PHP notices at best, wrong WP_HOME/salts at worst. Decide explicitly who owns the constants and remove the other.

Two database connections. Doctrine's PDO and $wpdb's mysqli are independent. Doctrine transactions don't roll back WP writes, RefreshDatabase-style isolation doesn't cover wp_* tables, and you burn two connections per request. Test suites need a dedicated WP database plus manual truncation.

exit/die kills the kernel. wp_redirect(), wp_die(), and plenty of plugins terminate the process. Terminable middleware, response listeners, and profiler flushes after that point never run.

Stray output. Plugins that echo on init corrupt JSON responses or trigger "headers already sent". Boot WP inside ob_start() and log the capture — it's usually a plugin you didn't know was active.

Global $post and $wp_query leak. Nested loops or long-running workers hand you another request's post. setup_postdata() is a mutation, not a read.

the_content filters. Decide whether JSON output gets apply_filters('the_content', …). Half-applying it (shortcodes run, wpautop doesn't) is the classic "works in the theme, broken in the API" bug — and with block themes it now also decides whether render_block runs at all.

Debugging. Symfony's debug mode does not set WP_DEBUG/WP_DEBUG_LOG; define them before WP loads. The Symfony toolbar won't appear on WP-rendered pages, so dump() only helps on kernel-routed requests. When a block is unexpectedly missing from blog.registered_blocks, check plugin load order and whether the block is registered conditionally (init vs admin_init).

Updates vs deploys. Auto-updates writing into vendor/ or web/ get wiped by the next release. Pin WP core in composer.lock (now on the 7.x line), disable all auto-update flags, treat core as an artifact.

Extension points. The Wordpress wrapper is thin — decorate it. The user provider can be decorated for role mapping or caching. Constants the config surface doesn't expose can be set from an early bundle boot() or a prepend extension, before the WP bootstrap. If you need the block registry in PHP rather than Twig, read WP_Block_Type_Registry directly inside a decorated wrapper rather than depending on the template variable's name staying stable.

Have an exit plan. The whole bootstrap is roughly "define constants, require wp-load.php, expose a service." At this dependency size — and with platform support shifting inside patch releases — budget for replacing the bundle with ~50 lines in your own service provider, and keep WP_Query confined to one namespace so that swap stays cheap.

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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings