http://www.xdebug.org/download.php
Pour savoir la version à utiliser, dans votre phpinfo(), rearder les informations VC6 et thread safe. (*-vc6.dll pour thread safe)
Dans votre installation WAMP, copier la dll dans le répertoire ext de la version php que vous utilisez. (renommer en xdebug.dll pour pouvoir changer ce fichier sans avoir à changer votre config)
Ajouter les informations suivantes à votre php.ini (adapter le répertoire dans lequel se trouve votre dll)
; Extension du serveur xdebug zend_extension_ts="C:/wamp/bin/php/php5.3.0/ext/php_xdebug.dll" ; Accès distant au serveur xdebug xdebug.remote_enable=1 xdebug.remote_host="127.0.0.1" xdebug.remote_port=9000 ; Trace de xdebug xdebug.auto_trace=0 xdebug.trace_format=0 xdebug.show_mem_delta=1 xdebug.trace_output_dir="C:/wamp/tmp/xdebug" xdebug.trace_output_name="trace.%t" ; Collecte de variables locales, paramètres et retours de fonctions xdebug.collect_vars=1 xdebug.collect_params=4 xdebug.collect_return=1 xdebug.show_local_vars=1 ; Dump variables superglobales xdebug.dump_globals=1 xdebug.dump=COOKIE,FILES,GET,POST,REQUEST,SERVER,SESSION xdebug.dump.SERVER=REMOTE_ADDR,REQUEST_METHOD ; Profileur xdebug (stat et graph) ; Decommenter pour activer le profiler de maniere permente ;xdebug.profiler_enable=1 ;xdebug.profiler_output_dir="C:/wamp/tmp/xdebug/profiler"
Redémarrer tous les services
Pour activer le profiler "à la demande", on préférera modifier le fichier .htaccess
php_flag xdebug.profiler_enable On
php_value xdebug.profiler_output_dir "C:/wamp/tmp/xdebug/profiler"
Allow to remove all fields of a form except a given list:
public function unsetAllExcept($fields = array()) { foreach($this->getObject()->toArray(BasePeer::TYPE_FIELDNAME) as $key => $val) { $tmp[] = strtolower($key); } $tmp = array_diff($tmp, $fields); foreach($tmp as $value) { unset($this[$value]); } }
Remove created_at, updated_at ...
$this->unsetAllExcept(array('comment'));
public function allowExtraFields() { $this->validatorSchema->setOption('allow_extra_fields', true); }
$configuration = ProjectConfiguration::getApplicationConfiguration($arguments['application'], $options['env'], true); sfContext::createInstance($configuration);
if (defined('SYMFONY_VERSION')) { // symfony 1.1.x or 1.2.x $version = explode('.', SYMFONY_VERSION); { $version = $version[0]. '.'. $version[1]; } } else { $version = '1.0'; }
public function getFields() { return array_keys($this->widgetSchema->getFields()); }
pear install -f symfony/symfony-1.2.4
Typiquement on peut utiliser ce snippet dans un template symfony, puisque les différentes variables n'y sont pas déclarées. Eclipse ne sait donc pas le type des variables qu'il utilise.
/*@var $ad_base ipAdBase */
Donc permet de charger un helper depuis un contexte autre que depuis les templates:
sfProjectConfiguration::getActive()->loadHelpers(array('Url'));
Sur un Ubuntu 8.10:
$ sudo apt-get install php5-dev
$ sudo pecl install xdebug
Puis ajouter dans le fichier php.ini dans la section de gestion des extensions:
$ sudo updatedb
$ sudo locate xdebug.so
Remplacer avec le bon chemin vers le fichier xdebug.so:
zend_extension=/usr/lib/php5/20060613+lfs/xdebug.so
[Edit 06/02/09] Enabling the apache mod_rewrite is a little faster than using this snippet. ;)
Well if you are using Windows + Wamp for your dev, the "noscript" setting (settings.yml) does not work well when you are using the prod controller to test your application with you dev computer. (if mod_rewrite is not installed)
So i wanted to deactivate this settings in dev environment with the prod controller and having it on in prod with the prod controller without having 2 different settings.yml files.
So here here a small tip for this:
prod:
.settings:
no_script_name: echo ((isset($_SERVER['HTTP_HOST']) && strstr($_SERVER['HTTP_HOST'], 'dev') ? 'off' : 'on')) . "\n"; ?>
logging_enabled: on
This code assumes that your dev url contains "dev" like http://dev.blogsnippets.com.
Tiré de l'article http://www.miximum.fr/methodes-et-outils/104-mixons-des-formulaires-avec-symfony
public function updateObject() { parent::updateObject(); $address = $this->getObject()->getAddress(); $values = $this->getValues(); $address->fromArray($values['address'], BasePeer::TYPE_FIELDNAME); $address->save(); return $this->object; }
This snippet is deprecated, please use the sfJoomla15BridgePlugin plugin witch works with the new 1.5 Joomla and the symfony 1.2 version.
Well, in fact i have taken the idea from what has already been done with cakePHP, you can find the original blog articles here (1) and thanks to the authors for the work. Now a full project for this purpose is available for cakePHP it is called Jake
(1)
Versions used :
Joomla 1.0.12 stable
Install the plugin (check the plugin Wiki page in Symfony )
Now you must have a sfJoomlaBridge dir in your plugins dir and a com_symfony dir in /web/components
The Symfony application is built as usual and the Joomla installation must be achieved in the /web of the Symfony project.
Open symfony.html.php, line 8 modify the name of Symfony controller file (frontoffice.php by default). Take care to use a production controller, because we should't have any debug information
Un-comment line 34 with file_get_contents if you don't have curl installed on you web server, and comment the curl related line above.
In index.php of Joomla
before (near line 114)
// mainframe is an API workhorse, lots of 'core' interaction routines $mainframe = new mosMainFrame( $database, $option, '.' ); $mainframe->initSession();
add
//Before Joomla initialize its mainframe, lets play… // check missing component — added before $mainframe is being initialized //########################## if (!file_exists($mosConfig_absolute_path . '/components/'. $option )) { $option = 'com_symfony'; //set component to com_cake if a controller is not found. }
Clear the cache
> symfony clear cc
There is nothing really specific except the new link helper to use in the templates. To use the Joomla helpers :
<?php use_helper('Joomla'); ?>
You can also modify the index file of Joomla application in plugins/sfJoomlaBridge/config/config.php
We are going to do a page (module/action) whitch will be called by Joomla with the help of its new component.
Nous are going to create 2 actions, 1 basic (static content) and one with the users list of Joomla administration
index/actions/actions.class.php
/** * Executes index action */ public function executeIndex() { } /** * Test Joomla/Symfony integration * * @author lvernet * @since 26 feb 2007 */ public function executeUsers() { $this->users = JosUsersPeer::doSelect(new Criteria()); }
The plugin introduce a new helper link_to_joomla that allows to do link to others symfony pages but with staying in the Joomla application.
templates/usersSuccess.php
<?php use_helper('Joomla'); ?> <h1>Joomla Users list</h1> <p>This list is retrieved with the help of the ORM of Symfony</p>
<?php if ($users): ?> <table class="contenttoc"> <thead> <th>Id</th> <th>Name</th> <th>User name</th> </thead> <?php foreach($users as $user): ?> <tr> <td><?php echo $user->getId(); ?></td> <td><?php echo $user->getName(); ?></td> <td><?php echo $user->getUsername(); ?></td> </tr> <?php endforeach; ?> </table> <?php endif; ?>
<?php echo link_to_joomla('Back to index', 'index/index', array('query_string' => 'param1=chat¶m2=rat')); ?>
templates/indexSuccess.php
<?php use_helper('Joomla'); ?> <h1>This content is provided through Symfony framework.</h1> <?php echo image_tag('powered_by.png') ?> && <?php echo image_tag('http://www.symfony-project.com/images/symfony_logo.gif') ?>
<?php echo link_to_joomla('Users list', 'index/users'); ?>
Now we must desactivate the view's layout, because we don't want it as we will use the one of Joomla.
In index/config/view.yml :
indexSuccess: has_layout: off usersSuccess: has_layout: off
We can now call our sf pages in Joomla, link are formed as following : index.php?option=index&task=users
Go to the url: http://projectroot/index.php?option=index&task=users, the users list is displayed and you have a link to go to the index page (link created with the joomla helper)
/!\ Be carefull, the option param should not match the name of a Joomla component /!\
Has to be updated for symfony 1.1
I made this quiet quickly, of course there are lot of things to do to have a perfect integration as the Jake project does it for cakePHP. If someone is interested, feel free to take the code and contribute, it would be nice.
COil :)
#!/bin/bash SYMFONY=`dirname $0`"/symfony" SEP="===========================================================================" echo "Rebuilding full database from DB4 schema and fixtures" echo $SEP echo "Converting DB4 schema for propel..." $SYMFONY propel:db4-to-propel frontend --env=cli --debug=1 --file_dir=/doc/database --file=snippets_db4.xml --output_dir=/config --output=snippets_schema --package=lib.model.snippets echo $SEP echo "Rebuilding all tables & ORM classes..." $SYMFONY propel:build-all-load frontend echo $SEP echo "Clearing the cache..." $SYMFONY cc echo $SEP cd .. echo "JOB's DONE ! ;)"
<?php if (sfConfig::get('sf_environment') != 'dev'): ?>
generator: class: sfPropelAdminGenerator param: model_class: Example theme: default edit: fields: pdf: type: admin_input_file_tag upload_dir: pdf_files filename: pdf_%%id%% params: include_link=user_pics include_remove=true
Utiliser la constante SYMFONY_VERSION:
<?php echo link_to('Powered by symfony '. SYMFONY_VERSION, 'http://www.symfony-project.com'); ?>
<?php require_once dirname(__FILE__).'/../lib/vendor/symfony/lib/autoload/sfCoreAutoload.class.php'; sfCoreAutoload::register(); class ProjectConfiguration extends sfProjectConfiguration { } ?>
<?php /** * Form to add a classified ad. * * @author loic.vernet * @since 7 august 08 * * @package form */ class qbNewThreadForm extends qbThreadForm { // Default mask to create fields names public static $main_fields_name = 'qb_thread'; // Default labels protected $labels = array( // Thread 'title' => 'Sujet', 'category_id' => 'Forum', // Contact form 'qb_post' => 'Post', ); // Default fields to keep protected $fields_to_keep = array( // Thread 'id', 'category_id', // Post 'qb_post', ); // Liste of field that can't be modified by direct input values protected static $protected_fields = array( ); /** * Configuration of form. */ public function configure() { // Build widgets $this->setWidgetOptions(); // Set wdigets $this->processWidgets(); // Set validators $this->processValidators(); // Embed other forms $this->embedForms(); } /** * Set generic options for widgets. * * @author loic.vernet * @since */ protected function setWidgetOptions() { // name format $this->widgetSchema->setNameFormat(self::$main_fields_name. '[%s]'); // Set labels $this->setLabels($this->labels); // Allow extra fields for all forms $this->allowExtraFields(); } /** * set all widgets. * * @author loic.vernet * @since */ public function processWidgets() { $this->removeAllFieldsExcept($this->fields_to_keep); $this->widgetSchema['category_id'] = new sfWidgetFormPropelSelect( array( 'model' => 'qbCategory', 'add_empty' => false, //'criteria' => ipAdBasePeer::getCategoriesCriteria() )); $this->widgetSchema['title'] = new sfWidgetFormInput(); } /** * Set all validators. * * @author loic.vernet * @since */ public function processValidators() { $this->validatorSchema['category_id'] = new sfValidatorString( array('required' => true), array('required' => 'Veuillez choisir un forum') ); $this->validatorSchema['title'] = new sfValidatorString( array('required' => true), array('required' => 'Veuillez choisir un titre pour le sujet') ); } /** * Embed other forms to the current form. * * @author loic.vernet * @since */ protected function embedForms() { } }
[Edit 20081017: Ajout affichage des erreurs globales]
<?php /*@var $form qbNewThreadForm */ ?> <?php qb_forum_add_style(); echo qb_include_resources(); ?> <h1><?php echo $forum->getTitle(); ?></h1> <h2>Création d'une nouvelle discussion</h2> <?php include_partial('forum/breadcrumb', array('forum_root' => $forum)); ?> <?php if ($form->hasGlobalErrors()): ?> <?php echo $form->renderGlobalErrors(); ?> <?php endif; ?> <div class="thread-edit"> <div id="thread-edit-bb-tools" class="cadre"> Outils de mise en page </div> <div id="thread-edit-title" class="cadre"> <?php echo $form['title']->renderLabel(); ?> <?php echo $form['title']->renderError(); ?> <?php echo $form['title']->render(); ?> </div> <div id="thread-edit-forum" class="cadre"> <?php echo $form['category_id']->renderLabel(); ?> <?php echo $form['category_id']->renderError(); ?> <?php echo $form['category_id']->render(); ?> </div> <div id="thread-edit-bb-code" class="cadre"> </div> <div id="thread-edit-files" class="cadre"> Fichiers joints </div> <div id="thread-edit-options" class="cadre"> Options </div> <div id="thread-edit-tone" class="cadre"> Ton du message </div> </div> <?php // echo $form; ?>
/** * Ad publication form for the users. * * @todo : Eventually save posted datas in session to be able to retrieve * values even when changer the main category of the ad. */ public function executePost($request) { // Default ad_base object $ad_base = $this->getAdBaseOrCreate($request); // Secure edition if (!$ad_base->isNew()) { $this->forward404Unless($ad_base && ($ad_base->getIpAdContact()->getCreatedBy() == $this->getUser()->getId())); } // Form and params $ip_ad_base_post = $request->getParameter('ip_ad_base'); $form = new ipAdPostForm($ad_base, $ip_ad_base_post); $category_refresh = isset($ip_ad_base_post['category_refresh']) ? $ip_ad_base_post['category_refresh'] : 0; // Refresh category and save submitted form values (without input validation) if ($category_refresh) { $ip_ad_base_post['category_refresh'] = 0; $form->setDefaults($ip_ad_base_post); } // Post and save form if (!$category_refresh && $this->isPost() && $ip_ad_base_post) { $form->bind($ip_ad_base_post, $request->getFiles('ip_ad_base')); if ($form->isValid()) { $form->save(); // Photo deletion if ($delete_flag = $form->hasDeletePhotoFlag() && !$ad_base->isNew()) { $ad_base->deletePhoto(); } // Photo upload if (!$delete_flag && $form->hasPhoto()) { $form->saveAndUploadPhoto($request); } // Send email to administrator if it is a new ad to validate //if ($form->getObject()->isNew()) //{ //ipTools::dump('send email', 'send email', 1); $form->getObject()->notifyWebmasterForNewAd(); //} // Redirect $this->getUser()->addNotice('Votre annonce a été sauvegardée, elle sera visible dès qu\'elle aura été validée par un administrateur'); $this->redirect('iposte_user/adsList'); } } // Pass form to template $this->form = $form; }
Permet de loader un yml dans le plugin avec la possibilité de l'overwritter s'il ce meme fichier est present dans l'application.
Dans /plugin/myPlugin/config faire un fichier config_handlers.yml
config/sfBBCodeParser.yml:
class: sfSimpleYamlConfigHandler
Donc ici, on va preparer une pre-compilation de ce fichier de config dans le cache, mais il ne sera pas inclut par défaut. Il faut donc l'apeller a partir du cache config des lors qu'on en a besoin.
/** * Test and include the default sfBBCodeParserPlugin config if it doesn't exists. * * @author loic.vernet * @since 30 july 08 * * @param Array $config */ function bb_check_and_include_config($config) { if (!$config) { // Include config file from generated cache $bb_config_file = include(sfContext::getInstance()->getConfigCache()->checkConfig(sfBBCodeParserConfig::getConfileFilePattern())); sfConfig::set(sfBBCodeParserConfig::getNamespace(), $bb_config_file); } return $config['config']; }
Ici j'ai ajouté une classe d'abstraction pour gérer la configuration du plugin.
actions: { }
// Check category or query $v = new sfValidatorCallback(array( 'callback' => array($this, 'checkCategoryOrQuery') ,) , array( 'invalid' => 'Veuillez choisir une rubrique ou un mot clé' )); $this->validatorSchema->setPostValidator($v);
et le callback qui va bien:
/** * Check if a category or a keyword has been set. * * @param sfValidator $validator * @param Array $value Form values * @param Array $arguments Validation arguments * * @return mixed */ public function checkCategoryOrQuery($validator, $value, $arguments) { if (empty($value['category_id']) && empty($value['nature_id'])) { throw new sfValidatorError($validator, 'invalid'); } return $value; }
Permet de pouvoir faire des scripts de migration fiables puisque les noms de clé et d'index seront les mêmes sur tout les serveurs.
<?php /** * Migration qui ajoute les colonne permettant d'effectuer la localisation * d'un membre comme les bars. */ class Migration024 extends sfMigration { /** * Up */ public function up() { $this->diag('Debut UP 24'); // Table user_profile $this->diag('MAJ champs user_profile'); $this->executeSQL(" ALTER TABLE `user_profile` ADD `country_code` varchar(2) default NULL COMMENT 'Code du pays' AFTER `personal_url` , ADD `zone_id` int(11) default NULL COMMENT 'Departement du membre' AFTER `country_code` , ADD `lat` decimal(12,9) default NULL COMMENT 'Latitude' AFTER `zone_id` , ADD `lng` decimal(12,9) default NULL COMMENT 'Longitude' AFTER `lat` , ADD `latlng_precision` int( 11 ) default NULL COMMENT 'Precision de la localisation' AFTER `lng`; "); // ALTER TABLE `user_profile` ADD FOREIGN KEY ( `zone_id` ) REFERENCES `zone` (`id`) ON DELETE SET NULL; // Ajout des cle etrangeres $this->diag('MAJ contraintes user_profile'); $this->executeSQL(" ALTER TABLE `user_profile` ADD CONSTRAINT `zone_id_FK` FOREIGN KEY `zone_id_idx` ( `zone_id` ) REFERENCES `zone` (`id`) ON DELETE SET NULL; "); } /** * Down */ public function down() { $this->diag('Debut DOWN 24'); // Suppr $this->diag('SUPPRESSION contraintes champs a supprimer'); $this->executeSQL(" ALTER TABLE `user_profile` DROP FOREIGN KEY `zone_id_FK` "); $this->executeSQL(" DROP INDEX `zone_id_idx` ON `user_profile` "); $this->diag('SUPPRESSION champs user_profile'); $this->executeSQL(" ALTER TABLE `user_profile` DROP `country_code`, DROP `zone_id`, DROP `lat`, DROP `lng`, DROP `town`, DROP `latlng_precision`; "); } }
Ou en décomposant la création des index/clés
<?php /** * Migration qui ajoute les colonne permettant d'effectuer la localisation * d'un membre comme les bars. */ class Migration024 extends sfMigration { /** * Up */ public function up() { $this->diag('Debut UP 24'); // Table am_user_profile $this->diag('MAJ champs user_profile'); $this->executeSQL(" ALTER TABLE `user_profile` ADD `country_code` varchar(2) default NULL COMMENT 'Code du pays' AFTER `personal_url` , ADD `lat` decimal(12,9) default NULL COMMENT 'Latitude' AFTER `country_code` , ADD `lng` decimal(12,9) default NULL COMMENT 'Longitude' AFTER `lat` , ADD `latlng_precision` int( 11 ) default NULL COMMENT 'Precision de la localisation' AFTER `lng`; "); // ALTER TABLE `am_user_profile` ADD FOREIGN KEY ( `zone_id` ) REFERENCES `am_zone` (`id`) ON DELETE SET NULL; // Ajout des cle etrangeres /* $this->diag('MAJ conrtaintes am_user_profile'); $this->executeSQL(" ALTER TABLE `user_profile` ADD CONSTRAINT `zone_id_FK` FOREIGN KEY `zone_id_idx` ( `zone_id` ) REFERENCES `am_zone` (`id`) ON DELETE SET NULL; "); */ $this->diag('CREATION table am_user_zone'); $this->executeSQL(" CREATE TABLE `user_zone` ( `id` int(11) NOT NULL auto_increment, `user_id` int(11) NOT NULL, `zone_id` int(11) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ; "); /* KEY `user_zone_FI_1` (`user_id`), KEY `user_zone_FI_2` (`zone_id`) ALTER TABLE `user_zone` ADD INDEX ( `user_id` ) */ $this->diag('MAJ conrtaintes am_user_zone'); $this->executeSQL(" ALTER TABLE `user_zone` ADD INDEX `user_zone_FI_1` ( `user_id` ) "); $this->executeSQL(" ALTER TABLE `user_zone` ADD FOREIGN KEY `user_zone_FK_1` ( `user_id` ) REFERENCES `sf_guard_user` (`id` ) ON DELETE CASCADE; "); $this->executeSQL(" ALTER TABLE `user_zone` ADD INDEX `am_user_zone_FI_2` ( `zone_id` ) "); $this->executeSQL(" ALTER TABLE `user_zone` ADD FOREIGN KEY `user_zone_FK_2` ( `zone_id` ) REFERENCES `zone` (`id` ) ON DELETE CASCADE; "); } /** * Down */ public function down() { $this->diag('Debut DOWN 24'); // Suppr $this->diag('SUPPRESSION contraintes champs a supprimer'); /* $this->executeSQL(" ALTER TABLE `user_profile` DROP FOREIGN KEY `zone_id_FK` "); $this->executeSQL(" DROP INDEX `zone_id_idx` ON `user_profile` "); */ $this->diag('SUPPRESSION champs user_profile'); $this->executeSQL(" ALTER TABLE `user_profile` DROP `country_code`, DROP `lat`, DROP `lng`, DROP `latlng_precision`; "); $this->diag('SUPPRESSION table user_zone'); $this->executeSQL("DROP TABLE IF EXISTS `user_zone`"); } }
Typiquement pour une action Ajax:
if (!$this->user) { sfLoader::loadHelpers('Partial'); return $this->renderText(get_partial('member/rdv/list')); }
if ($this->getRequest()->isXmlHttpRequest()) { $this->getResponse()->addCacheControlHttpHeader('max_age=3600'); $this->response->setHttpHeader('Pragma', null, false); $timestamp = mktime(0,0,0,date("m"),date("d")+1,date("Y")); $this->getResponse()->setHttpHeader('Expires', $this->getResponse()->getDate($timestamp)); }
$action_instance = sfContext::getInstance()->getController()->getActionStack()->getFirstEntry()->getActionInstance();
<?php echo select_tag('per_page', options_for_select(MessageSent::getMessagesPerPageComboBox(), $sf_user->getMessagesPerPage()), array( 'id' => 'sgMessagesPP', 'style' => 'margin: 0 0 0 5px;', 'class' => 'floatL', 'onchange' => remote_function(array( 'update' => 'messages_list', 'url' => '@messages_per_page', 'with' => "'count=' + this[this.selectedIndex].value + '&message_type_id=' + $message_type_id", 'loading' => "replaceContentWithIndictator('messages_list');" )), )); ?>
// Delete external tables from plugin schema (already defined in another schema (main or other plugin)) if (DCMC_EXTERNAL_TABLE) { foreach (explode(',', DCMC_EXTERNAL_TABLES) as $external_table) { $reg_exp = '/(<table name="'. $external_table. ')(((.)*(\s)*)*?)(<\/table>)/'; $xmlstr = preg_replace($reg_exp, '', $xmlstr); } }
$order = OrderPeer::doSelectOne(new Criteria()); $shop_order = $order->getShopOrders(); $shop_order = $shop_order[0]; // goTools::dump($order, '$order', 0); // goTools::dump($shop_order, '$shop_order', 1); sfContext::getInstance()->getRequest()->setAttribute('client', $order->getClient($con), 'mail'); sfContext::getInstance()->getRequest()->setAttribute('order', $order, 'mail'); sfContext::getInstance()->getRequest()->setAttribute('shop_order', $shop_order, 'mail'); sfContext::getInstance()->getRequest()->setAttribute('shop', $shop_order->getShop($con), 'mail'); sfContext::getInstance()->getLogger()->debug( sfContext::getInstance()->getController()->sendEmail('goMail', 'sendOrderPaidToShop') );
I had to inherit from sfSessionStorage to make it work.. Also, I just couldn't get it to work with domain limitations in that Explorer seems to send the cookies of the main domain to all subdomains. So I had to use a subdomain-based session name Here is the code bit, at the end it came out pretty simple, but I used hours trying to get it to work with domain limiting the cookies
class mySessionStorage extends sfSessionStorage { /** * This override sets the exact (sub)domain for session cookie and then initializes this Storage instance. * * @param sfContext A sfContext instance * @param array An associative array of initialization parameters * * @return boolean true, if initialization completes successfully, otherwise false * * @throws <b>sfInitializationException</b> If an error occurs while initializing this Storage */ public function initialize($context, $parameters = null) { // $parameters['session_cookie_domain'] = $_SERVER['SERVER_NAME']; // This doesn't work $parameters['session_name'] = 'symfony_'.$_SERVER['SERVER_NAME']; // This works // initialize parent parent::initialize($context, $parameters); } }
<?php $order = $order->getRawValue(); ?>
in factories.yml file add a auto_start entry like this:
all:
storage:
param:
auto_start: false
$form_ajax_options = $sf_data->get('form_ajax_options', ESC_RAW);
Donc pour un contrôle non riche, n'utilisant pas le calendrier.
<?php echo go_row_input('born_at', __('Date de naissance :'), object_input_date_tag($client, 'getBornAt', 'year_start=1920 year_end='.date('Y'))) ?>
$storage = sfContext::getInstance()->getStorage(); $storage->write('test', 1); //$_SESSION['test'] = 1;
<?php slot('search-nb-results'); ?> Element.update('search-nb-results', '<?php echo $pager->getNbResults(); ?>'); Element.update('search-nb-results-offers', '<?php echo $count_offers; ?>'); <?php end_slot(); ?> <?php echo javascript_tag(get_slot('search-nb-results')); ?>
Avec echappage du js:
<?php // Maj javascript par slot ?> <?php $slot_name = 'old_match_bar_'. $old_bar_match_id; ?> <?php slot($slot_name); ?> <?php $html = escape_javascript(get_component('member_match', 'barSummaryLeft', array( 'match_bar_id' => $old_bar_match_id, ))); ?> Element.update('membres-going-matchbar<?php echo $old_bar_match_id; ?>', '<?php echo $html; ?>'); <?php echo javascript_tag(get_slot($slot_name)); ?>
<?php echo link_to(__('(code phpbb activé)'), 'http://www.phpbb.com/support/documentation/2.0/#section4_2_5', array('popup' => array(__('Code phpBB'), 'width=750,height=400,left=0,top=0,scrollbars=1'))); ?>
== Liste des options
The parameters URL, name, features, replace have the following meaning: URL String specifying the location of the Web page to be displayed in the new window. If you do not want to specify the location, pass an empty string as the URL (this may be the case when you are going to write some script-generated content to your new window). name String specifying the name of the new window. This name can be used in the same constructions as the frame name provided in the frame tag within a frameset <FRAME NAME=name ...>. For example, you can use hyperlinks of the form <a target=name href="page.htm">, and the hyperlink destination page will be displayed in your new window. If a window with this name already exists, then window.open() will display the new content in that existing window, rather than creating a new one. features An optional string parameter specifying the features of the new window. The features string may contain one or more feature=value pairs separated by commas. replace An optional boolean parameter. If true, the new location will replace the current page in the browser's navigation history. Note that some browsers will simply ignore this parameter. The following features are available in most browsers: toolbar=0|1 Specifies whether to display the toolbar in the new window. location=0|1 Specifies whether to display the address line in the new window. directories=0|1 Specifies whether to display the Netscape directory buttons. status=0|1 Specifies whether to display the browser status bar. menubar=0|1 Specifies whether to display the browser menu bar. scrollbars=0|1 Specifies whether the new window should have scrollbars. resizable=0|1 Specifies whether the new window is resizable. width=pixels Specifies the width of the new window. height=pixels Specifies the height of the new window. top=pixels Specifies the Y coordinate of the top left corner of the new window. (Not supported in version 3 browsers.) left=pixels Specifies the X coordinate of the top left corner of the new window. (Not supported in version 3 browsers.)
$security = $this->getSecurityConfiguration();
'onchange' => remote_function(array( 'update' => 'item_domain', 'url' => 'list/subdomain', 'with' => "'id=' + this.options[this.selectedIndex].value" ))
fillin:
enabled: on
param:
id: seller_stockcreate_form
fields:
shop_product{quantity}:
required:
msg: Vous devez selectionner une quantité
itemQuantityValidator:
shop_product{price}:
required:
msg: Vous devez entrer un prix
shopProductPriceValidator:
# Inclusion de l'ensemble des validateurs definis pour le site
<?php include('validate/_validators.yml'); ?>
/!\ Les fonctions de criterion n'acceptent que des autres criterions en parametre /!\
/!\ Appel d'une methode doSelect sans passer de criteria /!\
/!\ Passage d'une valeur nulle ou d'un objet quelquonque a place d'un criteria /!\
$value = $dateFormat->format($specialoffer_form['begin_at'], 'I', $dateFormat->getInputPattern('d'));
// Debut de la transaction $con = Propel::getConnection(); $con->begin(); try { $this->shop_ribs->saveShopRibsFromRequest($shop_ribs_form); $this->shop->setShopRibs($this->shop_ribs); $this->shop->save(); $con->commit(); } catch (Exception $e) { $con->rollback(); $this->getRequest()->setError('error', $this->getContext()->getI18N()->__('Une erreur est survenue pendant l\'enregistrement, veuillez essayer d\'ici quelques minutes.')); }
sfContext::getInstance()->getController()->getActionStack()->getFirstEntry()->getActionInstance()->getFlash('notice')
page: url: /:sf_culture/:page requirements: { sf_culture: (?:fr|en|de) } params: ...
$url=sfContext::getInstance()->getController()->genUrl($uri);
throw new exception(sfContext::getInstance()->getI18N()->__('Aucun template defini par défaut, vérifier la table Templates'));
<?php include_partial('shop/partials/navigation', array('shop' => $shop)) ?>
methods:
post:
- "user_profile{login}"
- "user_profile{password}"
- "user_profile{check_password}"
- "user_profile{email}"
- "user_profile{civility}"
- "user_profile{first_name}"
- "user_profile{last_name}"
- "user_profile{birth_date}"
- "user_profile{address}"
- "user_profile{postal_code}"
- "user_profile{town}"
- "captcha"
- "user_profile{is_newsletter_subscribed}"
- "user_profile{is_partners_subscribed}"
- "user_profile{photo}"
- "user_profile{personal_url}"
fillin:
enabled: true
param:
name: form_register_member
names:
user_profile{login}:
required: Yes
required_msg: le login est obligatoire
validators: [usernameValidator, loginValidator]
user_profile{password}:
required: Yes
required_msg: le mot de passe est requis
validators: passwordValidator
user_profile{check_password}:
required: Yes
required_msg: La confirmation du mot de passe est obligatoire
validators: passwordValidator
user_profile{email}:
required: Yes
required_msg: l'email est obligatoire
validators: [emailValidator, mailuniqValidator]
user_profile{civility}:
required: Yes
required_msg: veuillez choisir votre civilité
user_profile{first_name}:
required: Yes
required_msg: le prénom est obligatoire
validators: usernameValidator
user_profile{last_name}:
required: Yes
required_msg: le nom est obligatoire
validators: usernameValidator
user_profile{birth_date}:
required: Yes
required_msg: le nom est obligatoire
validators: usernameValidator
user_profile{address}:
required: Yes
required_msg: l'adresse est obligatoire
user_profile{postal_code}:
required: Yes
required_msg: le code postal est obligatoire
validators: zipcodeValidator
user_profile{town}:
required: Yes
required_msg: la ville est obligatoire
captcha:
required: Yes
required_msg: vous devez recopier le code
validators: captchaValidator
user_profile{is_newsletter_subscribed}:
required: no
user_profile{is_partners_subscribed}:
required: no
user_profile{photo}:
required: no
user_profile{personal_url}:
required: no
passwordValidator:
class: sfCompareValidator
param:
check: passconf
compare_error: les mots de passe ne correspondent pas
loginValidator:
class: sfPropelUniqueValidator
param:
class: sfGuardUser
column: username
unique_error: ce login est déjà enregistré, choisissez en un autre.
mailuniqValidator:
class: sfPropelUniqueValidator
param:
class: UserProfile
column: email
unique_error: cet email est déjà enregistré, choisissez en un autre.
zipcodeValidator:
class: sfNumberValidator
nan_error: Veuillez n'entrer que des chiffres
min: 5
min_error: le code postal est au minimum composé de 5 chiffres
usernameValidator:
class: sfStringValidator
param:
min: 3
min_error: ce login doit être plus long
emailValidator:
class: sfEmailValidator
param:
email_error: l'adresse email n'est pas valide
captchaValidator:
class: captchaValidator
param:
error: vous devez recopier correctement le code de validation
methods: [post] # This is the default setting
fillin:
enabled: true
param:
name: send_page_to_friend_form
#skip_fields: [email] # Do not repopulate these fields
#exclude_types: [hidden, password] # Do not repopulate these field types
#check_types: [text, checkbox, radio, password, hidden] # Do repopulate these
validators:
myStringValidator:
class: sfStringValidator
param:
min: 2
min_error: This field is too short (2 characters minimum)
max: 100
max_error: This field is too long (100 characters maximum)
fields:
name:
required:
msg: The name field cannot be left blank
myStringValidator:
email:
required:
msg: The email field cannot be left blank
myStringValidator:
sfEmailValidator:
email_error: This email address is invalid
age:
sfNumberValidator
nan_error: Please enter an integer
min: 0
min_error: "You're not even born. How do you want to send a message?"
max: 120
max_error: "Hey, grandma, aren't you too old to surf on the Internet?"
message:
required:
msg: The message field cannot be left blank
Cacher le résultat d'un processus coûteux pour 24h :
<?php if (!cache('huge_process_of_the_death', 86400)): ?> <?php foreach ($stuff as $item): ?> // Your amazingly huge iteration processes here <?php endforeach; ?> <?php cache_save() ?> <?php endif; ?>
Pour le cacher pour un utilisateur spécifique :
<?php if (!cache('huge_process_of_the_death'.md5($user->getEmail()), 86400)): ?> <?php foreach ($stuff as $item): ?> // Your amazingly huge iteration processes here <?php endforeach; ?> <?php cache_save() ?> <?php endif; ?>
On s'assure juste de prendre l'empreinte md5 d'un attribut unique dans la table associée ;)
$key = md5('myPropelObjectKey'); $cache = new sfProcessCache(); if ($sfProcessCache->has($key)) { $obj = unserialize($cache->get($key)); } else { $obj = Table::doSelect(); $cache->set($key, serialize($obj)) }
require_once($sf_symfony_lib_dir.'/util/sfCore.class.php'); sfCore::initSimpleAutoload('/path/to/libs');
Désactiver les extensions de debug dans apache comme Xdebug ou APD
Installer la derniere version de PHP 5.x (pour la V1.0.0), pour la version Symfony 0.6.3 installer la derniere version 5.1.x
Mettre a off "magic_quotes_gpc" dans php.ini
Taille mémoire allouée minimum (328 mo) (memory_limit)
A downloader ici si pas dans la distrib php du server :
A ajouter dans php.ini : (au minimum)
Sour Windows pour tester en env de dev
extension=php_apc.dll
apc.enabled = 1
apc.enabled a mettre a 0 ou cas ou il y ai un pb.
Apres il y d'autres parametre qui peuvent etre affinés : (documentation : http://www.php.net/manual/en/ref.apc.php)
apc.shm_segments = 1 apc.shm_size = 30 apc.optimization = 0 apc.num_files_hint = 1000 apc.ttl = 0 apc.gc_ttl = 3600 apc.cache_by_default = 1 apc.filters = NULL apc.mmap_file_mask = NULL apc.slam_defens = 0 apc.file_update_protection = 2 apc.enable_cli = 0 apc.max_file_size = 1M apc.stat = 1
Eviter les requêtes inutiles
Mettre en place une politique de cache adéquate (voir doc)
Dans la vue faire (en fin de template)
echo include_javascripts();
Typiquement ce pourra être utile dans un template sans layout.
On pourra aussi utiliser le helper sf_include_resources (js+css) du plugin sfJoomlaBridgePlugin, qui contrairement a include_javascripts, inclu dynamiquement les ressources et ne casse pas la conformite XHTML.
$this->getRequest()->setParameter('confirm', true); $this->forward('users', 'index');
.settings relative_url_root: /relative/path/to/symfony/app
$timer = sfTimerManager::getTimer('method1'); for ($index = 0; $index < $iterations; $index++) { $resultado .= "cadena " . $variable; } $timer->addTime(); $elapsedTime = $timer->getElapsedTime(); $this->debugMessage('"cadena " . $variable; ---> '. round($elapsedTime, 4). ' s');
Pour eviter les problemes de type MIME, notemment sous MAC.
/** * Fonction de validation manuelle de l'importation * * @internal Pour bug Mac type mime * * @author Vernet Loic * @since 6 mars 2007 */ public function validateImport() { $fieldName = 'importfile'; $fileName = $this->getRequest()->getFileName($fieldName); $validExts = array('.CSV'); if ($this->getRequest()->getMethod() == sfRequest::POST) { if ($fileName) { foreach($validExts as $validExt) { if (strpos(strtoupper($fileName), $validExt)) { return true; } } $this->getRequest()->setError($fieldName , 'Extension invalide, l\'extension doît être .csv'); return false; } else { $this->getRequest()->setError($fieldName , 'Fichier invalide'); return false; } } return true; }
Here we go !
The sort in admin generator is for a single field only, but in some complex list, it can be usefull to sort by multiple criterias. This is the main goal of this snippet. Those functions therefore override the ones of your auto-generated class. (1) And to display what are the ongoing sort criterias, you have to modify your 'listth_tabular.php' file.
// /** * Add a sort criteria */ protected function processSort () { $sort = $this->getRequestParameter('sort'); $type = $this->getRequestParameter('type'); // Register sort if ($sort) { $this->getUser()->setAttribute($sort, $type, 'sf_admin/produits/sort'); } } /** * Add the sort criterias to the query */ protected function addSortCriteria(&$c) { $multisort = $this->getUser()->getAttributeHolder()->getAll('sf_admin/produits/sort'); if ($multisort) { foreach($multisort as $sort_column => $sort_type) { $sort_column = Propel::getDB($c->getDbName())->quoteIdentifier($sort_column); if ($sort_type == 'asc') { $c->addAscendingOrderByColumn($sort_column); } elseif ($sort_type == 'desc') { $c->addDescendingOrderByColumn($sort_column); } } } else { // Default sort $sort_column = Propel::getDB($c->getDbName())->quoteIdentifier('libelle'); $c->addAscendingOrderByColumn($sort_column); } } /** * Specific function for multi-sort */ protected function processFilters () { if ($this->getRequest()->hasParameter('filter')) { $filters = $this->getRequestParameter('filters'); // Multi-sort initialisation if (!is_array($filters)) { $this->getUser()->getAttributeHolder()->removeNamespace('sf_admin/produits/sort'); } $this->getUser()->getAttributeHolder()->removeNamespace('sf_admin/produits/filters'); $this->getUser()->getAttributeHolder()->add($filters, 'sf_admin/produits/filters'); } }
(for each sortable field)
<?php $multisort = $sf_user->getAttributeHolder()->getAll('sf_admin/produits/sort'); ?> <th id="sf_admin_list_th_libelle"> <?php if (isset($multisort['libelle'])) { echo link_to('Libellé', 'Produits/list?sort=libelle&type='. ($multisort['libelle'] == 'asc' ? 'desc' : 'asc')); echo ' ('. $multisort['libelle'] . ')'; } else { echo link_to('Libellé', 'Produits/list?sort=libelle&type=asc'); } ?> </th>
'Produits' is the module name, 'libelle' is the field to sort.
The 'reset button' of filters also initialize the multi-sort. The sort is made from the first field clicked to the last. That means, if you want a different primary sort you will have to use the reset filter button before.
PS: Obviously, this modification can be easly integrated in your backoffice theme, note that the default sort criteria set in the 'generator.yml' is used in the addSortCriteria function (from line // default sort)
imageliste:
name: Liste du catalogue
type: admin_input_upload_tag
upload_dir: dyn/produits
params:
include_link:dyn/produits
include_remove:true
include_text: Voir la photo liste du catalogue
include_remove_text:Supprimer la photo
$template = $sf_view->getTemplate()
$template = $context->getCurrentViewInstance()->getTemplate();
<?php echo link_to('interesting article', 'article/read', array('query_string' => 'title=Finance_in_France')) ?>
Dans "view.yml" :
stylesheets: [-*]
sfLoader::loadHelpers(array('First', 'Second'));
sfConfig::set('sf_routing_defaults', array('culture' => 'en'));
$request = $this->getRequest()->getParameterHolder();
$sessionName = sfContext::getInstance()->getStorage()->getParameter('sf_session_name')
$referer = $this->getRequest()->getReferer()
<?php if ($sf_request->hasErrors()) { foreach($sf_request->getErrors() as $key => $error) { echo '<li>'. $key. ' => '. $error. '</li>'; } } ?>
Version synthaxe alternative
<?php if ($sf_request->hasErrors()): ?> <ul> <?php foreach($sf_request->getErrors() as $key => $error): ?> <li><?php echo $key; ?> => <?php echo $error; ?></li> <?php endforeach; ?> </ul> <?php endif; ?>
if ($this->getRequest()->getMethod() == sfRequest::POST) { // $this->actionWhenPosted() }
all:
lucene:
index_path: %MY_CONSTANT%/data/lucene
image_path:
photos: %SF_WEB_DIR%/images/photos
avatars: %SF_WEB_DIR%/images/avatars
Here we go !
The sort in admin generator is for a single field only, but in some complex list, it can be usefull to sort by multiple criterias. This is the main goal of this snippet. Those functions therefore override the ones of your auto-generated class. (1) And to display what are the ongoing sort criterias, you have to modify your '_list_th_tabular.php' file.
// /** * Add a sort criteria */ protected function processSort () { $sort = $this->getRequestParameter('sort'); $type = $this->getRequestParameter('type'); // Register sort if ($sort) { $this->getUser()->setAttribute($sort, $type, 'sf_admin/produits/sort'); } } /** * Add the sort criterias to the query */ protected function addSortCriteria(&$c) { $multisort = $this->getUser()->getAttributeHolder()->getAll('sf_admin/produits/sort'); if ($multisort) { foreach($multisort as $sort_column => $sort_type) { $sort_column = Propel::getDB($c->getDbName())->quoteIdentifier($sort_column); if ($sort_type == 'asc') { $c->addAscendingOrderByColumn($sort_column); } elseif ($sort_type == 'desc') { $c->addDescendingOrderByColumn($sort_column); } } } else { // Default sort $sort_column = Propel::getDB($c->getDbName())->quoteIdentifier('libelle'); $c->addAscendingOrderByColumn($sort_column); } } /** * Specific function for multi-sort */ protected function processFilters () { if ($this->getRequest()->hasParameter('filter')) { $filters = $this->getRequestParameter('filters'); // Multi-sort initialisation if (!is_array($filters)) { $this->getUser()->getAttributeHolder()->removeNamespace('sf_admin/produits/sort'); } $this->getUser()->getAttributeHolder()->removeNamespace('sf_admin/produits/filters'); $this->getUser()->getAttributeHolder()->add($filters, 'sf_admin/produits/filters'); } }
(for each sortable field)
<?php $multisort = $sf_user->getAttributeHolder()->getAll('sf_admin/produits/sort'); ?> <th id="sf_admin_list_th_libelle"> <?php if (isset($multisort['libelle'])) { echo link_to('Libellé', 'Produits/list?sort=libelle&type='. ($multisort['libelle'] == 'asc' ? 'desc' : 'asc')); echo ' ('. $multisort['libelle'] . ')'; } else { echo link_to('Libellé', 'Produits/list?sort=libelle&type=asc'); } ?> </th>
'Produits' is the module name, 'libelle' is the field to sort.
The 'reset button' of filters also initialize the multi-sort. The sort is made from the first field clicked to the last. That means, if you want a different primary sort you will have to use the reset filter button before.
PS: Obviously, this modification can be easly integrated in your backoffice theme, note that the default sort criteria set in the 'generator.yml' is used in the addSortCriteria function (from line // default sort)
COil :)
[En] After having tested the sync command of Symfony i realised that all the permissions of my /web directory were wrong. The symfony command : (1)
It supposed to correct this, unfortunately the command line php version is 4 on my shared hosting, so i had to to it by hand with a little unix script : (2)
There are lot and different problems on shared testing as can can see in this forum thread
[Fr] Apres avoir testé la commande de synchronisation de symfony, plus rien ne marchait... En fait c'est du au fait que tous mes fichiers de /web n'avait plus les bonnes permission et il fallait les repasser en 755. Une commande symfony permet de regler ce problème : (1)
> symfony fix-perms
Néanmoins la version de PHP sur mon server mutualisé est la 4 ! le script symfony ne peut donc pas fonctionner... On peut néanmoins contourner en executant la commande suivante : (2)
> symfony cc > find . -type f | xargs chmod 644 > find . -type d | xargs chmod 755
La joie des serveurs mutualisés avec symfony, ce n'est pas toujours de tout repos comme le montre ce post sur le forum
[Fr] Dans la doc j'ai trouvé la liste de fichier à exclure assez peu fournie. Voici une liste plus complète à personnaliser : (1)
[En] In the documentation i found the exclude file list quiet small. Here is a more consistant one to customize : (1)
.svn .CVS .project .settings .settings/* symfony /cache/* /config/config.php /config/propel.ini /config/databases.yml /config/properties.ini /doc/* /log/* /lib/symfony /lib/symfony/* /data/symfony /data/symfony/* /plugins/.channels/ /plugins/.registry/ /plugins/.depdb /plugins/.depdblock /plugins/.filemap /plugins/.lock /plugins/.pearrc /stats/* /test/* /web/.htaccess /web/frontend_dev.php /web/backend_dev.php /web/phpinfo.php /web/test.php /web/uploads/* /web/sf /web/sf/*
[EN] This is a typical .htaccess file for the french hoster OVH (1)
[FR] Voici un fichier .htaccess typique pour faire fonctionner une application Symfony sur un hébergement mutualisé OVH comme le 240gp.
(1)
SetEnv PHP_VER 5 SetEnv REGISTER_GLOBALS 0 SetEnv MAGIC_QUOTES 0 mod_gzip_on Off Options +FollowSymLinks +ExecCGI <IfModule mod_rewrite.c> RewriteEngine On # we skip all files with .something RewriteCond %{REQUEST_URI} \..+$ RewriteCond %{REQUEST_URI} !\.html$ RewriteCond %{REQUEST_URI} !\.php #RewriteCond %{REQUEST_URI} !\.php RewriteRule .* - [L] # we check if the .html version is here (caching) RewriteRule ^$ /index.html [QSA] RewriteRule ^([^.]+)$ /$1.html [QSA] RewriteCond %{REQUEST_FILENAME} !-f # no, so we redirect to our front web controller RewriteRule ^(.*)$ /index.php [QSA,L] # hidden frontoffice controller RewriteRule ^index\.php/(.*)$ /index.php [QSA,L] # fo controllers RewriteRule ^frontend\.php/(.*)$ /frontend.php [QSA,L] RewriteRule ^frontend_dev\.php/(.*)$ /frontend_dev.php [QSA,L] </IfModule> # big crash from our front web controller ErrorDocument 500 "<h2>Application error</h2>symfony application failed to start properly"
Here is a small patch for the action.class of the admin generator theme :
in executeList() function replace :
$this->pager->setPage($this->getRequestParameter('page', 1));
by :
$this->pager->setPage($this->getRequestParameter('page', $this->getUser()->getAttribute('page', 1, 'sf_admin/<?php echo $this->getSingularName() ?>')));
in executeList() function add at the end :
// Save page if ($this->getRequestParameter('page')) { $this->getUser()->setAttribute('page', $this->getRequestParameter('page'), 'sf_admin/<?php echo $this->getSingularName() ?>'); }
in the process() function, before the 1st removeNamespace statement add :
$this->getUser()->getAttributeHolder()->removeNamespace('sf_admin/<?php echo $this->getSingularName() ?>');
logs
9985.0 KB