![]() |
|
Snippets |
|
All web browsers support the 'text/html' content type. However, since the introduction of XHTML, a new content type has been available that brings the benefits of XML, which is 'application/xhtml+xml' (more info, why do this)
Although not all browsers support 'application/xhtml+xml', fortunately, the designers of the HTTP protocol provided a good solution: the browser tells the server what content types it can accept and which it prefers. So the server is able to choose which content type to serve.
Firefox is one of the browsers that supports 'application/xhtml+xml' and with the new Firefox3 comes incremental rendering, which means that it has fixed the earlier disadvantage in Firefox2 that 'text/html' was faster to render (more info).
The most noticeable advantage of XHTML rendering is that errors are displayed by the browser, meaning that it's easier to identify and fix layout problems caused by muddled content. If your content is ill-formed, Firefox displays a yellow error page indicating where the error was located. This can be a most useful development tool.
So how can we serve 'application/xhtml+xml' content?
Your content has to be written to be XHTML instead of HTML (the W3C website provides helpful information in explaining how to do this). Your pages must include the correct DOCTYPE (how to do this).
Your application has to change the content-type header depending on whether the browser can accept XHTML or not. Obviously, it's useless to send 'application/xhtml+xml' to older browsers that don't support it.
This snippet helps with the second issue. I have written a filter (shown below) to change the response headers depending on what each browser is able to handle.
Create a new class in your lib folder called XHTMLFilter.class.php thus:
<?php class XHTMLFilter extends sfFilter { public function execute ($filterChain) { $applyIn = $this->getParameter('environments', array('dev')); if (!is_array($applyIn)) { throw new sfException("XHTMLFilter param: not an array ".print_r($applyIn, true)); } if (in_array( SF_ENVIRONMENT, $applyIn )) { $accept = $this->context->getRequest()->getAcceptableContentTypes(); if (in_array('application/xhtml+xml', $accept)) { $this->context->getResponse()->setContentType( 'application/xhtml+xml' ); } } $filterChain->execute(); } }
Now add it to your app/config/filters.yml:
rendering: ~
web_debug: ~
security: ~
XHTMLFilter:
class: XHTMLFilter
param:
environments: [ dev ]
cache: ~
common: ~
flash: ~
execution: ~
There is one configuration parameter (environments) that contains an array of the environments to which the filter will apply. By default, only the 'dev' environment is included but you can add any or all of your environments here.
This filter will automatically add css file with the name of current module.
It's great to have these files saved separately that to have one huge file. (And demo of CSSEdit doesn't allow saving files bigger than 2.5k chars :D)
Just create your cssAdderFilter.class.php, and add it to your filters.yml. Clear cache and you are ready to go.
<?php class cssAdderFilter extends sfFilter { public function execute( $filterChain ) { $context = $this->getContext(); $request = $context->getRequest(); $response = $context->getResponse(); $module = $request->getParameter("module"); $path_to_web = sfConfig::get("sf_web_dir"); $path_to_css = $path_to_web . "/css/" . $module . ".css"; if (file_exists($path_to_css)) { $response->addStylesheet($module.".css"); } $filterChain->execute(); } }
Hi!
I am using a lot of hand written jscript codes due to the required behavior not found in any javascript framework. I've created my Objects in distinct files, although some of them are quite small ( <1kb ). So to make it fast, I've written a filter class that actually parses the files found in the /js directories having *.js suffix. It does not only parse them together but removes whitespaces, comments, indentations so the sum filesize gets a bit compressed. The filter also includes the final file into the response so there is no need to include any *.js ( inside /js ) in a template.
<?php class JavascriptParser extends sfFilter { public function execute($filterChain) { $filterChain->execute(); $fp = fopen( "js/compiled.js", "w" ); JavascriptParser::getJSFiles( "js", $fp ); fclose ( $fp ); $response = $this->getContext()->getResponse(); $content = $response->getContent(); if (false !== ($pos = strpos($content, '</head>'))) { $html = "<script type='text/javascript' src='/js/compiled.js'></script>"; if ($html) { $response->setContent(substr($content, 0, $pos).$html.substr($content, $pos)); } } } public function getJSFiles( $dir, &$fp ) { $hDir = opendir( $dir ); while( ( $filename = readdir( $hDir ) ) !== false ) { if ( is_dir( $filename ) ) { } else if ( is_dir( $dir."/".$filename ) ) JavascriptParser::getJSFiles( $dir."/".$filename, $fp ); else if ( strpos( $filename, ".js" ) !== false && $filename != "compiled.js" ) { $tmpFile = fopen( $dir."/".$filename, "r" ); $data = fread( $tmpFile, filesize( $dir."/".$filename ) ); $data = preg_replace( "'\/\*.*?\*\/'si", "", $data ); $data = preg_replace( "'//.*?\n'si", "", $data ); $data = preg_replace( "'[ \t]+'", " ", $data ); fwrite( $fp, " \n".$data ); fclose( $tmpFile ); } } closedir( $hDir ); } } ?>
The same should be done with *.css files, since they are even smaller and loading many small files takes much more time then loading one big.
Best Regards
I had an urgent need to assign a variable across all actions, and copy and pasting the same snippet of code just didn't appeal to me.
Failing to find the answer in the forums or in the Snippets, I decided to roll up my sleeves and dig into creating it as a filter.
After much experimentation and documentation re-reading, I think I finally found a solution.
First you have to set up your own filter. Since it's fairly well laid out in the Handbook, I won't go over it here. (http://www.symfony-project.org/book/1_0/06-Inside-the-Controller-Layer#Filters)
In your filter code, put the following:
class myFilter extends sfFilter { public function execute($filterChain) { // Remove this if condition if you want the variable available to AJAX actions if (!$this->getContext()->getRequest()->isXmlHttpRequest()) { for ($i=0; $i < $this->getContext()->getActionStack()->getSize(); $i++) { $this->getContext()->getActionStack()->getEntry($i)->getActionInstance()->foo = 'foo'; } } // Execute next filter $filterChain->execute(); } }
Replace foo with the variable name you want and assign it the data you need. You should now be able to access it in every action that you call.
For example in your action:
class indexAction extends sfAction { public function execute() { strtoupper($this->foo); } }
or in your view:
<?php echo $foo ?>
Hi, this is my first snippet (in my first Symfony project), so I hope this is useful and not boring for you. I show you my code to solve a problem: how to filter an admin_check_list using a criteria.
In my project I have in the schema also this tables:
role:
type: { primaryKey: true, type: char }
descritpion: { type: varchar(50), required: true }
user:
id:
username: { type: varchar(100), index: unique, required: true }
role_type: { type: char, foreignTable: role, foreignReference: type, required: true }
course:
id:
name: { type: varchar(255), required: true }
research_proposal: { type: longvarchar }
course_teacher:
course_id:
user_id:
Little explanation: Each user have a role. Each course can have more teacher; each teacher is an user with "T" role_type.
In the generator.yml I can write
edit:
fields:
course_teachers: { type: admin_check_list, params: through_class=CourseTeacher }
then in the course creation page I have a list of all users (and not a list of the teacher).
To reach my scope I need to override the ObjectAdminHelper. I create a file named ObjectAdminHelper.php and put it into the /lib/helper folder of my Symfony folder. To override successfully an helper it is necessary to overwrite (or write) all its method. So just copy all from the original ObjectAdminHelper.php file and paste it into this new file. The only method we need to override is "_get_object_list($object, $method, $options, $callback)": it use by default the method "_get_propel_object_list" to get the list to show... But if we would like to modify that list how can we do?
function _get_object_list($object, $method, $options, $callback){ $object = get_class($object) == 'sfOutputEscaperObjectDecorator' ? $object->getRawValue() : $object; // the default callback is the propel one if (!$callback) { $callback = _get_option($options, 'callback'); if (!$callback) { $callback = '_get_propel_object_list'; } } return call_user_func($callback, $object, $method, $options); }
This method get an option called "callback" to get the name of the callback method. If a callback method is not present it use the standard "_get_propel_object_list".
So if I would like to get only the teacher (role_type="T") i can write this function (very similar than _get_propel_object_list):
function _get_teacher_from_users($object, $method, $options){ $criteria = new Criteria(); $criteria->add(UserPeer::ROLE_TYPE, "T"); $through_class = _get_option($options, 'through_class'); $objects = sfPropelManyToMany::getAllObjects($object, $through_class, $criteria); $objects_associated = sfPropelManyToMany::getRelatedObjects($object, $through_class, $criteria); $ids = array_map(create_function('$o', 'return $o->getPrimaryKey();'), $objects_associated); return array($objects, $objects_associated, $ids); }
Now you can write all the function you need to filter the lists.
Last step: how can my generator.yml say to the helper which method it must use? Simply modify the course_teacher row:
edit:
fields:
course_teachers: { type: admin_check_list, params: through_class=CourseTeacher callback=_get_teacher_from_users }
I hope this is useful for you (and sorry for my english).
Pierpaolo Cira
When using fillin filter, after validation success (all form fields are ok) if you want display page without form, fillin filter is still enabled and throws exception "Exception - No form found in this page". Use this snippet to prevent it.
in sfFillInFormFilter.class.php file
public function execute($filterChain) { // execute next filter $filterChain->execute(); $context = $this->getContext(); $response = $context->getResponse(); $request = $context->getRequest(); // add these two lines if(! $request->hasErrors()) return;
This filter use the accepted-languages browser setting to setup the user culture. The autodetection is run once by session.
This filter is sponsored by Dorigo consultants.
It check app.yml for accepted_languages setting in order to fit the available application locales.
It allow the use of sf_culture request parameter overide (this overide is avaible by default in symfony afaik). So you can use language changing links in your application.
It may be enhanced by a check of the really available locales but i don't know how it can be done.
To use this filter, create a file named SwitchLanguageFilter.class.php in lib/ or app/frontend/lib and fill it with this code and follow its documentation :
<?php /** * Setup user culture from request * * from http://www.symfony-project.com/snippets/snippet/80 * Thanks to François Zaninotto * Thanks to Garfield-fr on #symfony-fr * * Add the following lines in your app.yml to configure your application available languages. * all: * accepted: * languages: [en, fr] * * Then add this to your filters.yml * * # Filter that setup user culture * mySwitchLanguageFilter: * class: SwitchLanguageFilter * * @package SwitchLanguageFilter * @subpackage filter * @author Pierre-Yves Landuré <py.landure@dorigo.fr> */ class SwitchLanguageFilter extends sfFilter { /** * Check that the language is a valid application culture. * @param string $language The tested language code * @param string $default_language The default language code * * @return string $language if no accepted languages is set, * else $language if it is in accepted languages * else $default_language */ private function getAvailableCulture($language, $default_language = null) { $all_languages = sfConfig::get('app_accepted_languages', array()); if(count($all_languages)) { if(in_array($language, $all_languages)) // Test if language is available { return $language; } else // Else test if first part of language is available { $language_parts = explode('_', $language); if(count($language_parts)) { if(in_array($language_parts[0], $all_languages)) { return $language_parts[0]; } } } return $default_language; } return $language; } /** * The filter call. */ public function execute ($filterChain) { $context = $this->getContext(); $user = $context->getUser(); $default_culture = sfConfig::get('sf_i18n_default_culture'); $selected_culture = $user->getCulture(); if(!$user->getAttribute('sf_culture_autodetected', false)) { $browser_languages = $context->getRequest()->getLanguages(); foreach($browser_languages as $language) { $allowed_culture = $this->getAvailableCulture($language); if($allowed_culture) { $selected_culture = $allowed_culture; break; } } $user->setAttribute('sf_culture_autodetected', true); } $selected_culture = $context->getRequest()->getParameter('sf_culture', $selected_culture); $selected_culture = $this->getAvailableCulture($selected_culture, $default_culture); if($selected_culture != $user->getCulture()) { // The user wants to see the page in another language $user->setCulture($selected_culture); } $filterChain->execute(); } }
A possible way to implement themes in symfony is to use a different layout depending on the environment that is active. Although that's obviously not the original purpose of symfony's environment model, this solution offers great flexibility which is actually superior to regular themes. It is superior because you may have your page completely rearranged and layouted differently without changing one single line of code in your actions.
Let's say we have a website which shall be displayed nicely on mobile devices as well. For this purpose we create two environments: 'frontend' and 'mobile' (names are arbitrary).
We add the following filter class to the /lib directory of our application:
class myEnvironmentLayoutFilter extends sfFilter { public function execute($filterChain) { switch (SF_ENVIRONMENT) { case 'frontend': $this->getContext()->getController()->getActionStack()->getFirstEntry()->getActionInstance()->setLayout('layout_frontend'); break; case 'mobile': $this->getContext()->getController()->getActionStack()->getFirstEntry()->getActionInstance()->setLayout('layout_mobile'); break; } $filterChain->execute(); switch (SF_ENVIRONMENT) { case 'frontend': $this->getContext()->getResponse()->addStyleSheet('frontend'); break; case 'mobile': $this->getContext()->getResponse()->addStyleSheet('mobile'); break; } } }
Note the environment-dependent style sheets.
And in filters.yml:
# generally, you will want to insert your own filters here myEnvironmentLayoutFilter: class: myEnvironmentLayoutFilter
The advantages should be obvious: performance, as no filesystem access is necessary (no need to check for certain directories or template files). flexibility and expandability - features can be easily added to just one theme/layout, components can be arranged as needed, feature-stripped versions of a website may be realized, too. Less redundant template code. Easy to maintain.
[DEPRECATED SEE COMMENTS]
{-- DEPRECATED SEE COMMENTS --}
---DEPRECATED SEE COMMENTS---
This basic filter can transform pages into pdf using the sfTCPDFPlugin. You can also save the generated PDF to the database using the sfPropelFileStoragePlugin.
this is the first version so please post comments if you like it or use it !
Create a file in apps/myapp/lib/pdfFilter.class.php
<?php /** * Filter for redirecting to PDF for the pages that need it * * @author Laurent Marchal * @version 1 */ class pdfFilter extends sfFilter { /** * Execute filter * * @param FilterChain $filterChain The symfony filter chain */ public function execute ($filterChain) { if ($this->getContext()->getActionName() == 'pdf') { $pdf = $this->initPDF(); // Next filter $filterChain->execute(); $rawpdf = $this->writePDF($pdf, $this->getContext()->getResponse()->getContent()); $pdf->Output(); //shows the pdf to the user //$this->savePdfToDatabase($rawpdf); return; } $filterChain->execute(); } /** * initialyse the TCPDF object, must be instanciated before * filterChain->execute() in order to analyse the request. * */ protected function initPDF() { //create new PDF document $pdf = new sfTCPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true); $pdf->SetFont("FreeSerif", "", 12); return $pdf; } /** * write the contents to the PDF generally * getContext()->getResponse()->getContent() * *@param sfTCPDF $pdf the TCPDF object created with function initPDF() *@param stream $html_content the contents to transform to PDF */ protected function writePDF($pdf, $html_content) { $pdf->AddPage(); $pdf->writeHTML($html_content, true, 0); //$this->logMessage('ficheduActions::createPDF '.$html_content , 'info'); //$document = $pdf->Output('test.pdf', 's'); return $pdf->Output('test.pdf', 's'); } /** * Save the PDF in the database using sfPropelFileStoragePlugin * *@param stream $pdf_content output of function writePDF() */ protected function savePdfToDatabase($pdf_content) { //File Info $file_info = new sfPropelFileStorageInfo(); $file_info->setName('DU_Entreprise_v'.$fichedu->getFdVersion().'.pdf'); $file_info->setSize(null); $file_info->setMimeType('application/pdf'); //File Data $file_data = new sfPropelFileStorageData(); $file_data->setBinaryData($pdf_content); $file_info->addsfPropelFileStorageData($file_data); $file_info->save(); } }
and in apps/myapp/config/filters.yml
rendering: ~
web_debug: ~
security: ~
pdfFilter:
class: pdfFilter
cache: ~
common: ~
flash: ~
execution: ~
then in your module,you will have to create this function in the action.class.php :
public function executePdf() { $this->renderPDF = true; //use a special layout to clean the pdf //remove if you want your entire layout to be generated into pdf $this->setLayout ('pdf'); //the show action generally fit $this->setTemplate ('show'); }
then go to http://mywebsite/yourmodule/pdf to generate the pdf
H@ve Fun !
If you're like me and prefer to code your own Unobtusive Javascript, you'll probably find this snippet handy. It will search through your project's /web/js folder for any .js files that match the current Symfony action.
For example, the snippet/new action would look for /web/js/snippet/snippet.js and /web/js/snippet/new.js, and add them to the response if either exist.
You can also define an optional subfolder within /web/js where you want the filter to look (i.e. "backend").
<?php /** * Looks for Javascript files based on current action/module and adds them to * the current response object. * * Add this filter to the end of your filter chain in filters.yml, after the * execution filter: * * <code> * rendering: ~ * web_debug: ~ * security: ~ * * # generally, you will want to insert your own filters here * * cache: ~ * common: ~ * flash: ~ * execution: ~ * * auto_javascript_include: * class: myAutoJavascriptIncludeFilter * </code> * * @package Automatic Javascript Include (AJI) * @subpackage filter * @author Kris Wallsmith <kris [dot] wallsmith [at] gmail [dot] com> * @version SVN: $Id$ * @copyright Have at it ... */ class myAutoJavascriptIncludeFilter extends sfFilter { /** * Include external Javascript files based on last action called. * * This kicks in on the way back down the filter chain, so we're sure to * catch the last action. Looks through the web/js folder for files that * match the naming syntax and adds them to the response. * * You can specify a subfolder of the web/js folder for the filter to * search in app.yml. * * @author Kris Wallsmith <kris [dot] wallsmith [at] gmail [dot] com> * @see sfConfig::get('app_aji_subfolder') * @param sfFilterChain $filterChain */ public function execute($filterChain) { $filterChain->execute(); $sf_context = sfContext::getInstance(); $sf_response = $sf_context->getResponse(); $sf_web_dir = sfConfig::get('sf_web_dir'); $module = $sf_context->getModuleName(); $action = $sf_context->getActionName(); $sub_folder = sfConfig::get('app_aji_subfolder'); if($sub_folder && $sub_folder{0} != '/') $sub_folder = '/' . $sub_folder; $fmt = '/js%s/%s/%s.js'; $mod_mod_js = sprintf($fmt, $sub_folder, $module, $module); $mod_act_js = sprintf($fmt, $sub_folder, $module, $action); if(file_exists($sf_web_dir . $mod_mod_js)) $sf_response->addJavascript($mod_mod_js); if(file_exists($sf_web_dir . $mod_act_js)) $sf_response->addJavascript($mod_act_js); } } ?>
A common modification to a CRUD module is filtering records on the list page. There are probably several nice ways to do this, including drop-downs, forms, etc. depending on the situation. While some of these approaches are nice, they are often not minimal... We like minimal!
Say you have a book table, with a related author and genre. You want to filter the list of books by those related tables with links, paginate your list, and make it sortable via links in the header of the table.
To do this traditionally, you'll need lots of nasty little if statements in your template code, to check for all of these parameters (author_id, genre_id, title, num_pages, page, etc.) and combine them all together into a meaningful uri for your link_to function (something like "book/list?author_id=10&genre_id=5&sort=title&page=5"). We still want that link in the end, but generating it... How about a better way!
<!-- listSuccess.php --> <?php use_helpers('Filter', 'Pagination') ?> <h2>Books</h2> <h3>Filter By</h3> <table class="filters"> <tbody> <tr> <th>Author:</th> <td><?php echo filter_navigation(objects_for_filter($authors), 'author_id', $author_id) ?></td> </tr> <tr> <th>Genre:</th> <td><?php echo filter_navigation(objects_for_filter($genres), 'genre_id', $genre_id) ?></td> </tr> </tbody> </table> <hr /> <table class="list"> <thead> <tr> <th><?php link_to_unless($sort == 'id', 'Id', filter_url('sort', 'id')) ?></th> <th><?php link_to_unless($sort == 'title', 'Title', filter_url('sort', 'title')) ?></th> <th><?php link_to_unless($sort == 'author_id', 'Author', filter_url('sort', 'author_id')) ?></th> <th><?php link_to_unless($sort == 'genre_id', 'Genre', filter_url('sort', 'genre_id')) ?></th> </tr> </thead> <tbody> <?php foreach ($pager->getResults() as $book): ?> <tr> <td><?php echo $book->getId() ?></td> <td><?php echo $book->getTitle() ?></td> <td><?php echo $book->getAuthor()->getName() ?></td> <td><?php echo $book->getGenre()->getName() ?></td> </tr> <?php endforeach; ?> </tbody> </table> <?php echo pager_navigation($pager) ?>
NOTE: pager_navigation is covered at http://www.symfony-project.com/snippets/snippet/4, but needs to guess the uri, which is done at http://www.symfony-project.com/snippets/snippet/59.
NOTE: filter_navigation returns an unordered list, so you'll need css to display the <li> tags inline, and remove <ul> padding, margin, etc.
I'll leave it as an exercise to the reader to create the controller (action) code for this template, but it should be obvious.
<?php /** * Generate a url using the current internal uri, but replaces a param with a new value. * If the param is not in the current uri's query string, it is added instead. * * This is useful for a page that uses several filters to record sets, * and needs all the filters to work together, instead of blasting each * other away when a new link is clicked. * * <strong>Examples:</strong> * <code> * // with current uri => mymodule/myaction?author=10&genre=3 * * echo link_to('new author', filter_url('author', 5)); * // uri when clicked => mymodule/myaction?author=5&genre=3 * * echo link_to('new genre', filter_url('genre', 1)); * // uri when clicked => mymodule/myaction?author=10&genre=1 * * // with current uri => mymodule/myaction * * echo link_to('an author', filter_url('author', 10)); * // uri when clicked => mymodule/myaction?author=10 * </code> * * @param string the name of the parameter to replace * @param string the value to replace the current value with * @param boolean use route name * @return string the url with the parameter replaced * @see link_to */ function filter_url($param, $new_value, $with_route_name = false) { // fetch params from query string $params = _get_params(_get_query_string()); // replace param with new value $params[$param] = $new_value; return _get_uri($with_route_name) . '?' . _build_query_string($params); } /** * Removes a parameter from the current uri and returns the resulting url. * * @see filter_url */ function remove_filter_url($param, $with_route_name = false) { // fetch params from query string $params = _get_params(_get_query_string()); // remove param unset($params[$param]); return _get_uri($with_route_name) . '?' . _build_query_string($params); } /** * Generates an unordered list of links to filter the current record set by. * Multiple sets of filter_navigation links will work together, using the current uri. * * <strong>Examples:</strong> * <code> * echo filter_navigation(array(10=>'Jones', 12=>'Smith, J.', 13=>'Darby'), 'author_id', 13); * echo filter_navigation(objects_for_filter($authors), 'author_id', 13); * </code> * * @param array list of key=>value pairs of ids and strings * @param string the name of the parameter for this filter * @param string the selected id (or null, if none selected) * @param string the text to use for the "all" link * @see filter_url */ function filter_navigation($list, $param, $selected = null, $all_text = 'All') { $html = ''; $html .= content_tag('li', link_to_unless($selected === null, $all_text, remove_filter_url($param))); foreach ($list as $key => $value) { $html .= content_tag('li', link_to_unless($selected == $key, $value, filter_url($param, $key))); } return content_tag('ul', $html); } /** * Generates a simple list from a record set of propel objects. * Expects a getId function and a toString function. * * @param array objects to be converted to a list * @see filter_navigation */ function objects_for_filter($objects) { $list = array(); foreach ($objects as $object) { $list[$object->getId()] = $object->toString(); } return $list; } function _get_uri($with_route_name = false) { $internal_uri = sfRouting::getInstance()->getCurrentInternalUri($with_route_name); $ar = explode('?', $internal_uri); return ($with_route_name ? '@' : '') . $ar[0]; } function _get_query_string() { $internal_uri = sfRouting::getInstance()->getCurrentInternalUri(); $ar = explode('?', $internal_uri); return isset($ar[1]) ? $ar[1] : ''; } function _get_params($query_string) { // parse query string into associative array $params = array(); if ($query_string != '') { foreach (explode('&', $query_string) as $kvpair) { list($key, $value) = explode('=', $kvpair); $params[$key] = $value; } } return $params; } function _build_query_string($params) { // build list of key=value strings $ar = array(); foreach ($params as $key => $value) { $ar[] = $key . '=' . $value; } return implode('&', $ar); }
NOTE: Place this in apps/myapp/lib/helper/FilterHelper.php.
<?php echo link_to('new author', filter_url('author_id', 10)) ?> <?php echo url_for(filter_url('author_id', 10)) ?> <?php echo link_to_if($condition, 'new author', filter_url('author_id', 10)) ?> <?php echo link_to_unless($condition, 'new author', filter_url('author_id', 10)) ?> <?php echo button_to('new author', filter_url('author_id', 10)) ?>
<?php /** * Shortcut combining link_to and filter_url into single function. * * @see link_to * @see filter_url */ function link_to_filter($name, $param, $new_value, $options = array()) { return link_to($name, filter_url($param, $new_value), $options); } /** * Shortcut combining url_for and filter_url into single function. * * @see url_for * @see filter_url */ function filter_url_for($param, $new_value) { return url_for(filter_url($param, $new_value)); } /** * Shortcut combining link_to_if and filter_url into single function. * * @see link_to_if * @see filter_url */ function link_to_filter_if($condition, $name, $param, $new_value, $options = array()) { return link_to_if($condition, $name, filter_url($param, $new_value), $options); } /** * Shortcut combining link_to_unless and filter_url into single function. * * @see link_to_unless * @see filter_url */ function link_to_filter_unless($condition, $name, $param, $new_value, $options = array()) { return link_to_unless($condition, $name, filter_url($param, $new_value), $options); } /** * Shortcut combining button_to and filter_url into single function. * * @see button_to * @see filter_url */ function button_to_filter($name, $param, $new_value, $options = array()) { return button_to($name, filter_url($param, $new_value), $options); }
If you're like me, you hate having to populate your objects (from the model) with parameters from the request. Wouldn't it be nice to specify a little nugget of configuration, and then never worry about it again? Consider the following, which is tedious and boring:
class mymoduleActions extends sfActions { ... public function executeUpdate() { $employee = new Employee(); $employee->setFirstName($this->getRequestParameter('first_name')); $employee->setLastName($this->getRequestParameter('last_name')); $employee->setSsn($this->getRequestParameter('ssn')); $employee->setDob($this->getRequestParameter('dob')); ... $this->employee = $employee; } }
And the list goes on. Imagine, however, that you have the following in your module filters.yml file:
myPopulateObjectFilter: class: myPopulateObjectFilter param: use_database: on model_object: employee model_class: Employee exclude: [ssn] defaults: first_name: example default name
If you had a filter that automatically created an object, and based upon the previous yaml, populated that object with data from the request (after a user hit submit on a form containing this information), then subsequently set that object for retrieval as a request attribute, you'd be a pretty happy person right?
class mymoduleActions extends sfActions { ... public function executeUpdate() { $this->employee = $this->getRequest()->getAttribute('employee'); ... } }
Of course, you'd be free to do whatever you want with the results of the prepopulated object.
A great application would be an alternative to the fillin form filter. If you have your template set up to populate inputs from an object from the model, this filter can populate that object for you, and you can simply pass it on to the template.
<?php /** * myPopulateObjectFilter * Uses configuration in filters.yml to create an object, * populate it with data from the request, * and set a request attribute with the result. * * @author Stephen Riesenberg */ class myPopulateObjectFilter extends sfFilter { protected $defaults = null, $controller = null, $request = null; public function initialize($context, $parameters = array()) { parent::initialize($context, $parameters); // get defaults from filters.yml (if any) and create new parameter holder to store them $this->defaults = new sfParameterHolder(); $this->defaults->add($this->getParameter('defaults', array())); // get controller and request $this->controller = $this->getContext()->getController(); $this->request = $this->getContext()->getRequest(); } public function execute($filterChain) { // get request variable list $vars = $this->request->getParameterHolder()->getNames(); $exclude = array_merge($this->getParameter('exclude', array()), array('module', 'action')); $vars = array_diff($vars, $exclude); $funcs = array(); foreach ($vars as $var) { $funcs[$var] = 'set'.ucfirst(sfInflector::camelize($var)); } // get model_object and model_class $object = $this->getParameter('model_object'); $class = $this->getParameter('model_class'); // fetch from the database or create the object to fill in if ($this->getParameter('use_database', false) && null !== ($id = $this->request->getParameter('id'))) { $peer_class = $class . "Peer"; $record = $peer_class::retrieveByPk($id); } else { $record = new $class(); } // something like: array('id' => 'setId', 'my_field' => 'setMyField', ...) foreach ($funcs as $var => $func) { if (is_callable(array($record, $func))) { $record->$func($this->getValue($var)); } } // set the updated record into a request attribute $this->request->setAttribute($object, $record); // execute next filter $filterChain->execute(); } protected function getDefault($var) { return $this->defaults->get($var); } protected function getValue($var) { return $this->request->getParameter($var) != '' ? $this->request->getParameter($var) : $this->getDefault($var); } }
NOTE: Place this in a file called myPopulateObjectFilter.class.php in the myproject/apps/myapp/lib/ directory.
This is a very usefull and simple tip if you want to defined default value in edit form (only on creation of a new record) with the value that the user defined in the list filter.
An exemple for a product module which define the category select field with the value of the same field in the list filter.
action.class.php
public function executeEdit () { $filters = $this->getUser()->getAttributeHolder()->getAll('sf_admin/product/filters'); if (!$this->getRequestParameter('product_id', 0) && isset($filters['category_id'])) { $this->product = new Product(); $this->produit->setCatergoryId($filters['category_id']); } parent::executeEdit(); } protected function getProductOrCreate ($product_id = 'product_id') { if (isset($this->product)) return $this->product; else return parent::getProductOrCreate($product_id);
By default, the admin generator allows to filter the data with the rows from the table currently listed.
Here's how to extend this to data from other linked tables.
We'll consider the following example : a table "command" linked to a table "user". This very simple schema.xml shows the relation between these two tables :
<table name="buyer" phpName="BtqBuyer" > <column name="buyer_id" type="BIGINT" required="true" primaryKey="true"/> <column name="buyer_name" type="VARCHAR" size="255" required="true"/> </table> <table name="command" phpName="BtqCommand" > <column name="com_id" type="BIGINT" required="true" primaryKey="true"/> <column name="com_ref" type="VARCHAR" size="6" required="true"/> <column name="com_buyer_id" type="BIGINT" required="true"/> <foreign-key foreignTable="buyer" onDelete="" onUpdate=""> <reference local="com_buyer_id" foreign="buyer_id"/> </foreign-key> </table>
In the file generator.yml, add a partial in the filters parameter to print our specific filter :
filters: [com_ref, _btq_buyer]
The source code for the partial _btq_buyer.php (located in the templates directory) is :
<?php echo input_tag('filters[buyer]', isset($filters['buyer']) ? $filters['buyer'] : '') ?>
Now we have to add our specific filter in the filter process. To do this, we extend the