One of the challenges with caching is deciding where the limit is. What’s worth caching, and what isn’t. Because cleaning up that old data is hard… isn’t it?
Not so much!
You just need to make a helper and a checks, and you’ll be expertly picking out the templates to invalidate!
The first hurdle with clearing out invalid data, is that usually your backend is separate from the frontend, so it’s a non-trivial reach to try and invalidate cache files of other applications. Or is it?
<?phpclass cacheAssistant
{public static function clearCachePattern($pattern){$envs=array('prod','dev');$apps=array('backend','frontend');foreach($envsas$env){foreach($appsas$app){$app_cache_dir= sfConfig::get('sf_cache_dir').DIRECTORY_SEPARATOR.$app.DIRECTORY_SEPARATOR.$env.DIRECTORY_SEPARATOR.'template';$cache_vars=array('cache_dir'=>$app_cache_dir,'cache_key_use_vary_headers'=>true,'cache_key_use_host_name'=>true,);$cache=new sfFileCache($cache_vars);$cache->removePattern($pattern);}}}}
Your $cache_vars will differ depending on your cache settings, but essentially, that’s your main helper setup.
Now here’s what I use in my objects for cache clear detection:
Of course, your patterns will change depending on your cache options (I.e. I use the **/** because I have enabled cache_key_use_host_name, other settings may not accept this pattern.)
Happy caching, and if you have any questions, feel free to let me know!
One of the questions I see quite frequently is “How do I customize the user management interface for sfDoctrineGuard?” There are many different ways to go about it, but let me tell you about mine.
If you know me, you know I’m about attention to detail. I want the absolute cleanest solution you can come up with, and then I want it to look polished. So what you’re about to see will be quite a bit more involved than you would normally expect for such a tutorial, but I believe you will agree, at the end, that it was well worth the trip.
The first step is to get ready to customize. It’s easy, I promise. Here we go!
Note: pay special attention to integer(4). If you forget this, MySQL (and I assume other databases) won’t be able to build the relation properly as the field types will not match.
Now we need to rebuild things. If you don’t know how to do a migration, here’s the code you want:
Your sfDoctrineGuardUserProfile is ready to go, so let’s build our new admin form.
2. Add embedMergeForm to BaseFormDoctrine
The embedMergeForm function originates from (I believe) Roland Tapken of Cybso in this post. It’s a great piece of code. It gives us the function of embedded forms, with the look of merged forms. Here is the code I do (also adapted with one of the fixes from the comments section):
<?php// File: lib/form/doctrine/BaseFormDoctrine/**
* Project form base class.
*
* @package dcms
* @subpackage form
* @author Jacob Mather
* @version SVN: $Id: sfDoctrineFormBaseTemplate.php 23810 2009-11-12 11:07:44Z Kris.Wallsmith $
*/abstractclass BaseFormDoctrine extends sfFormDoctrine
{/**
* Embeds a form like "mergeForm" does, but will still
* save the input data.
*/publicfunction embedMergeForm($name, sfForm $form){// This starts like sfForm::embedForm$name=(string)$name;if(true===$this->isBound()||true===$form->isBound()){thrownew LogicException('A bound form cannot be merged');}$this->embeddedForms[$name]=$form;$form= clone $form;unset($form[self::$CSRFFieldName]);// But now, copy each widget instead of the while form into the current// form. Each widget ist named "formname|fieldname".foreach($form->getWidgetSchema()->getFields()as$field=>$widget){$widgetName="$name-$field";if(isset($this->widgetSchema[$widgetName])){thrownew LogicException("The forms cannot be merged. A field name '$widgetName' already exists.");}$this->widgetSchema[$widgetName]=$widget;// Copy widget$this->validatorSchema[$widgetName]=$form->validatorSchema[$field];// Copy schema$this->setDefault($widgetName,$form->getDefault($field));// Copy default valueif(!$widget->getLabel()){// Re-create label if not set (otherwise it would be named 'ucfirst($widgetName)')$label=$form->getWidgetSchema()->getFormFormatter()->generateLabelName($field);$this->getWidgetSchema()->setLabel($widgetName,$label);}}// And this is like in sfForm::embedForm$this->resetFormFields();}/**
* Override sfFormDoctrine to prepare the
* values: FORMNAME|FIELDNAME has to be transformed
* to FORMNAME[FIELDNAME]
*/publicfunction updateObject($values=null){if(is_null($values)){$values=$this->values;foreach($this->embeddedFormsAS$name=>$form){foreach($formAS$field=>$f){if(isset($values["$name-$field"])){// Re-rename the form field and remove// the original field$values[$name][$field]=$values["$name-$field"];unset($values["$name-$field"]);}}}}// Give the request to the original method
parent::updateObject($values);}}
Save your file, and let’s move on to step three.
3. Build our new interface form.
Let’s make a new file, lib/form/doctrine/myGuardUserAdminForm.class.php, and fill it with the following:
Now all you have to do is clear your cache, and go to your sfGuardUser form page, and you will see your new field!
5. Cleaning up messes.
Remember back in the schema.yml where we specified the onDelete: CASCADE? Bad news, that probably didn’t take care of it. There’s a bug somewhere between Symfony and Doctrine where sometimes foreign key constraints aren’t always handled appropriately. To counteract that, we will just run an SQL command to correctly establish the relationship so that when users are removed, we also remove their auxiliary profile data.
And we’re done! See? It was quite a long trip, but I hope you agree with me, the result is worth it.
02/16/2011 – Note: A thanks to hectorh30 from #symfony for pointing out an error in step three regarding the name of the profile form. It has been corrected.
02/12/2011 – Note: And in step one about paths. Thanks hectorh30!
03/03/2011 – Note: Thanks to dmclark for pointing out myDoctrineGuardAdminForm should be myGuardUserAdminForm
03/28/2011 – Note: Thanks to Richard Linkster for pointing out a copy command that was all busted up.
In part 1 we explored why exactly we may want to build our own jQuery plugin, so now in part two, we will cover exactly how to do so.
Note: these examples are through a bash command line, as I am fairly certain that is the most common way one interacts with Symfony. If you’re using Windows, you’ll get the gist of what to do, however.
Let’s start with initializing the plugin:
./symfony generate:plugin majaxJqueryPlugin
Now we will switch to our plugin directory:
cd plugins/majaxJqueryPlugin
We’re going to be providing web resources, so we will want to make web, js, and css directories. We will also make a temp directory to download files to:
mkdir web
mkdir web/js
mkdir web/css
mkdir temp
Prepare a place to download what we need:
cd temp
Here is our download and copy code. I have decided to host it from here, as jQuery UI doesn’t seem to provide direct download links.
Now for the fun part, to make it work. Since I like things to ‘just work’, here’s how I will do it. I will take a page from jQuery Reloaded and use a helper to actually load the files into the response, but instead of making people manually add it where they want it, we will just automatically shove it in if the plugin is enabled!
It’s time to make our helper directory:
mkdir lib/helper
Here is the contents of lib/helper/MajaxjQueryHelper.php:
For the last little trick before we enable the plugin, open up config/majaxJqueryPluginConfiguration.class.php and add the following code to the initialize() function:
You’re done! Now you just have to go back to your project root, and edit your project configuration to add the plugin, and publish it’s assets.
Here is the line to add to your ProjectConfiguration’s setup() function, just in case you need it:
$this->enablePlugins('majaxJqueryPlugin');
To publish it’s assets:
./symfony plugin:publish
And now you’re ready to use jQuery throughout your Symfony project!
To download a copy of the majaxJqueryPlugin I have made (and save yourself some work!), use the link below, and you just have to follow the last step to enable the plugin in your project:
The simplest way, hands down, is to simply include the sfJqueryReloadedPlugin in your project. This also enables you to include the sfAdminDashPlugin which makes for easy navigation and a nice login screen for your back-end systems.
However, the problem with jQuery Reloaded, is that it is both old, and for our purposes, incomplete.
Since the last update to jQuery Reloaded, jQuery has progressed to version 1.4.2 (from 1.3.2), and more importantly jQuery UI has upgraded to 1.8.2 (from 1.7.3) and added some very nice and easy pieces we can use in our quest to simplify some of the widgets users are commonly presented.
The other problem we will run into with jQuery Reloaded, is that it doesn’t provide the CSS half of the jQuery UI library, meaning we would not be able to see any widgets we used properly.
Just a quick bit that I’m sure I will revisit later, but that I wanted to get out there sooner…
When embedded forms are saved, if you want to hook into the form save for the embedded form, use the saveEmbededForms function as save or doSave is never called on the embedded form.
The embedded form has it’s saveEmbeddedForms function called, and then $form->getObject()->save().
I need to get in touch with the Symfony devs and see if we can’t get some clarification on this setup in 2.0, as this “smells” wrong, but maybe it is just me.