Integrating Symfony and WordPress
First off I want to thank this post for getting me started on this project, it saved me a lot of time and most of the steps here are just adjustments to their implementation.
This guide will be geared toward symfony 1.2 and WordPress 2.8+. Earlier versions of WordPress should integrate in relatively the same way but earlier versions of Symfony may cause complications.
The Implementation: This integration works by creating a custom web controller that pre-loads all of the WordPress functionality alongside Symfony. Next we dedicate a module to pass-through all of the WP requests which then uses output buffering to capture the WordPress rendering of the request without a WordPress theme and places it within the Symfony application’s layout.
The Result: Have all of the functionality and extensibility of WordPress within a Symfony application, rendered in your Symfony application’s layout for consistency and having access to the functionality from both platforms.
Step 1: Install WordPress
Download the latest WordPress and extract it into the web folder that you’ll be serving it out of. Integrating an existing WordPress installation? Just copy the entire installation’s containing folder into the web directory of your Symfony app. For the purposes of this guide I’ll be setting it up in /blog/ so after this step I’ll have:
web/blog/index.php web/blog/license.txt web/blog/readme.html ...
Step 2: Create a Dedicated Web Controller
Copy your application’s production and dev web controllers (for this example I’m using an application called ‘frontend’ so my controllers would be index.php and frontend_dev.php) to wordpress.php and wordpress_dev.php respectively. In each of these new files add the following two lines just after loading the application configuration:
define('WP_USE_THEMES', true);
require_once(sfConfig::get('sf_web_dir') . '/blog/wp-load.php');
For reference, my complete production controller becomes:
<?php
require_once(dirname(__FILE__).'/../config/ProjectConfiguration.class.php');
$configuration = ProjectConfiguration::getApplicationConfiguration('frontend', 'prod', false);
define('WP_USE_THEMES', true);
require_once(sfConfig::get('sf_web_dir') . '/blog/wp-load.php');
sfContext::createInstance($configuration)->dispatch();
Add URL redirection for your controller by adding the following to your web root’s .htaccess file before your index.php controller redirection (line 15 of the default Symfony .htaccess file) :
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^blog/(.*)$ wordpress.php [QSA,L]
Step 3: Create the Module
Create a new module in your application:
./symfony init-module frontend wordpress
Adjust the routing.yml (apps/frontend/config/routing.yml) to include the following:
blog:
url: /blog/*
param: { module: wordpress, action: index }
Change your index action (apps/frontend/modules/wordpress/actions/actions.class.php) to be the following:
public function executeIndex(sfWebRequest $request) {
ob_start();
wp();
require_once( ABSPATH . WPINC . '/template-loader.php' );
if (function_exists('is_feed') && is_feed()) {
ob_end_flush();
throw new sfStopException();
}
else {
$this->blog = ob_get_clean();
}
}
Set your index template (apps/frontend/modules/wordpress/templates/indexSuccess.php) to include:
<?php echo $blog ?>
Step 4: Resolving Conflicts
Right now we have everything we need to let both of the systems work together except for one very large issue: both Symfony and WordPress define and use the functions esc_js() and __(). To resolve this I chose to rename WordPress’s versions to wp_esc_js and wp__ respectively. On a Unix system the following shell commands can be run from the top level of your wordpress installation directory to rename all instances of these functions:
find . -name *.php -print | xargs sed -i 's/\(\b\)esc_js\(\b\)/\1wp_esc_js\2/g' find . -name *.php -print | xargs sed -i 's/\(\b\)__\(\b\)/\1wp__\2/g'
Some systems will return an error from find after running the above, the solution for this is to enclose your search pattern in quotes:
find . -name "*.php" -print | xargs sed -i 's/\(\b\)esc_js\(\b\)/\1wp_esc_js\2/g' find . -name "*.php" -print | xargs sed -i 's/\(\b\)__\(\b\)/\1wp__\2/g'
Run this as many times as you’d like on the same files without incident.
In the comments Ka has provided the following for the above on OSX:
find . -name ‘*.php’ | xargs perl -pi -e ’s/(\b)__(\b)/\1wp__\2/g’
Step 5: The Layout
WordPress is still rendering all of it’s content in the configured WordPress theme and then that is all being rendered in your Symfony layout. One option here is to empty your theme’s header.php, footer.php and sidebar.php files. Another approach involves removing all calls to your theme’s get_header(), get_footer() and get_sidebar() functions, the following list should encompass all of the files you’ll need to change:
404.php archive.php archives.php image.php index.php links.php page.php search.php single.php
The good news is that you have access to all of these functions from within your Symfony application’s layout. For example, to include the sidebar back in to your blog pages simply add the following to your current Symfony layout:
<?php if(function_exists('get_sidebar')) { get_sidebar(); } ?>
Step 6: Wrapping Up
Run through the WordPress guided installation if you haven’t already. When you’re finished and logged in you’ll notice that redirecting to the admin homepage after logging in gives you a Symfony 404. This can be corrected by adding the following to your .htaccess file, replacing ‘blog’ with your WordPress installation directory:
RewriteRule ^blog/wp-admin/$ blog/wp-admin/index.php
Step 7: A Model
Run the following to generate a schema from the new WordPress tables:
Propel:
./symfony propel:build-schema
Doctrine:
./symfony doctrine:build-schema
To get proper export and import through fixtures files be sure to add the table references in to the generated schema, they won’t be generated from the previous commands. As of right now this includes:
wp_postmeta.post_id
wp_term_relationships.object_id
wp_term_relationships.term_taxonomy_id
wp_term_taxonomy.term_id
wp_usermeta.user_id
Another note: I can only attest to this issue occurring in a Propel setup but there are 3 primary keys on the wp_options table and Propel doesn’t seem to be able to determine a proper identifier for these objects when generating fixtures so they all overwrite each other until you only end up with one object. To correct this I remove the primaryKey:true attribute on everything but the option_id column for this table and I suggest you do the same.
Enjoy
That’s it, you’re all set. If you have any comments or questions or if you found this guide useful please feel free to leave a comment below.
In The Future
Eventually you’re going to want to upgrade WordPress to another version. After you’ve installed the upgrade re-apply steps 4 and 7 and you’ll be good to go.
My thanks to Ka and Paulo for their improvements to this guide.
Update: After playing with the Trackback piece of WordPress it seems that this feature needs some tweaking to work with the set up above. To get trackback support again add the following to the top of the ‘wp-trackback.php’ in your WordPress installation directory:
global $wp, $wpdb, $posts, $wp_query;
for some reason when I activate the RewriteRule in .htaccess I get 404’s even for css-files, images etc… :-/
do you have an idea? – THX!
made a clean install in the meanwhile. Unsuccessful. Clean means.. I took a new symfony project, and proceeded as you described. No chance. still the same problem. Either a 500 or a 404. Meanwhile its sort of urgent to get it running. :-/ TIA!
Sorry for the late response, WP wasn’t notifying me of new comments.
As far as your 404s are concerned what does your .htaccess look like? Make sure WordPress doesn’t install it’s .htaccess, it isn’t necessary. The modifications I suggested only apply to requests that aren’t going to files that already exist (the same as Symfony’s rules) so they shouldn’t get in the way of your assets (JS, CSS, images, etc).
What errors are you getting from your dev controller when you get the 500 responses?
Hi Nick,
Good tutorial.. I’ve gotten to the point where everything is integrated. However, now when trying to wrap my layout.php around the Wordpress content, all I’m getting back is plain text of HTML rather than the HTML itself. Is there something tricky I have to do, to get symfony to render the text as HTML within the layout?
Me again,
On a side note – simply echo $blog – gives me HTML in plain text. However if I take that out of indexSuccess.php and replace it with the has_posts() loop (found in content/themes/default/index.php) directly into the indexSuccess.php file – I get the content rendered successfully – however, I’m not sure that’s how it’s suppose to work. Any ideas?
It sounds like output escaping is turned on in your application. In your template try this instead:
< ?php echo $sf_data->getRaw(’blog’); ?>
That should give you the unescaped WordPress HTML.
Step 4 : You may get a find error, so try
find . -name ‘*.php’ -print | xargs sed -i ’s/\(\b\)esc_js\(\b\)/\1wp_esc_js\2/g’
Config : Symfony 1.2 – WP 2.9.1
For some reason, the wrapping up (step 6) did not work for me.
Paulo posted a much cleaner solution for step 6 that should fix your issue, I’ve updated the guide with the new method.
Tip/Hack : Instead of going through all template files for removing the calls to get_header(), get_footer(), you can empty header.php and footer.php. Not really clean but time saving
hello!
I follow youe steps but when I run …./frontend_dev.pho/wordpress or …./wordpress_dev.php/wordpress I v got Fatal error: Call to undefined function wp() in C:\wamp\www\avocat1.0\apps\frontend\modules\wordpress\actions\actions.class.php on line 20
please help I need to use the wordpress blog in my symfony project
sf 1.2 doctrine
wp 8.6 or 9.1
Sorry for the late response, WP wasn’t notifying me of new comments.
It sounds like the WordPress functions aren’t getting registered, you’ll need to make sure you’re accessing the blog from a URL that goes through your WordPress web controllers (wordpress.php and wordpress_dev.php in my example). Accessing it from frontend_dev.php won’t work unless you’ve also included the require_once() call for wp-load in there as well. Your request to wordpress_dev.php should have worked correctly as long as you performed Step 2 on it as well. What happens when you access it from just /wordpress/?
the __() function of Wordpress and symfony are conflicting..
Here is the solution :
rename wordpress’ __() function to wp__()
with Mac OS X, inside the wordpress folder, launch the find & replace function :
find . -name ‘*.php’ | xargs perl -pi -e ’s/(\b)__(\b)/\1wp__\2/g’
Thanks for all of the useful tips, I’ve updated the relevant sections in the article. For the Wrapping Up portion, do you still get redirected to the root directory (/)? It may sound strange but clearing your cookies may fix this for you. Other than that ensure that you changed the right admin_url() call as there are quite a few in that file, you can use the code block I provided as a reference to it’s location.
Hello!
Regarding the Step 6, don’t you think it’s better to add a new rule in the .htaccess like the following?
RewriteRule ^blog/wp-admin/$ blog/wp-admin/index.php
This way you don’t have to change wp-login.php . I see it as an interesting alternative.
Regards,
Great idea, that will work much more cleanly with upgrades. I’ve updated my post with that solution.
Hi,
I follow your step by step all the way, but I get the following:
Accessing myurl.com/wordpress_dev.php/blog results on an error:
Warning: require_once(/blog/wp-load.php) [function.require-once]: failed to open stream: No such file or directory in /var/www/vhosts/zizzerdog.com/httpdocs/web/wordpress_dev.php on line 13
Fatal error: require_once() [function.require]: Failed opening required ‘/blog/wp-load.php’ (include_path=’.:/usr/share/pear:/usr/share/php’) in /var/www/vhosts/zizzerdog.com/httpdocs/web/wordpress_dev.php on line 13
So I go and try to fix the path… so removing from Step 2 line 7, first “slash”
from: require_once(sfConfig::get(’sf_web_dir’) . ‘/blog/wp-load.php’);
to: require_once(sfConfig::get(’sf_web_dir’) . ‘blog/wp-load.php’);
Gets me this error
Empty module and/or action after parsing the URL “/wp-admin/install.php” (/).
Also accesing myurl.com/wordpress gives my a symfony error.. wordpress_dev.php gives me same symfony error, no debugging or anything..
Can you help ?
thank you.
It sounds like your sf_web_dir configuration setting isn’t set at the time that you’re trying to load the wp-load.php, it should be loaded after your ProjectConfiguration::getApplicationConfiguration() call is made.
As far as the “empty module and/or action” error I can’t be sure, are you generating a link to that URL using the link_to or url_for helpers?
I checked your site and it seems like it’s working now, it’d be great if you could post your solution in case someone else runs in to this issue.
FWIW, I got this working without issue in Symfony 1.0. I had no use for building the Propel models for the Wordpress tables, so I didn’t try to do that. Everything else worked as described. Thanks for your post on this.