i campi di testo devono essere definiti del tipo TEXT(65000 bytes) che esporta in database.yml come "clob" che non da problemi di validazione
public function executeListimages(sfWebRequest $request) {
// parametri dal controller
$id = $request->getParameter('id');
// qry
$this->car_images =
Doctrine_Query::create()
->select('*')
->from('CarImage')
->where('car_image_id = ?', $id)
->execute();
$q = $this->createQuery()
->select('c.name, j.title')
->from('Category c')
->leftJoin('c.Job j')
->where('c.level = ?',1)
->where('j.expires_at > ?',date( 'Y-m-d h:i:s', time() ))
->setHydrationMode(Doctrine::HYDRATE_ARRAY);
// relations
$q = Doctrine_Query::create()
->from('User u')
->leftJoin('u.Phonenumbers p')
->leftJoin('u.Groups g');
$users = $q->execute();
// controlla la sintassi con
echo $q->getSqlQuery();
}
// accedere all'internazionalizzazione, fuori dal template
//$this->getContext() or
sfContext::getInstance()->getI18N()->__($text, $args, 'messages');
// da action
$culture = $this->getUser()->getCulture();
// la form va configurata per mostrare el form relative alle lingue
class HorsesForm extends BaseHorsesForm {
public function configure() {
$this->embedI18n(array('en', 'it'));
}
}
/*
//generator.yml
generator:
class: sfPropelGenerator # solo questo generatore funziona propel:generate-admin! init-admin è il vecchio generator e non funziona!
form:
# le form embedded devono essere chiamate esplicitamente
display:
Lingue: [ en, it ]
Immagini: [_images]
*/
//in action.php
$this->horses_list = HorsesPeer::doSelectWithI18n($c, 'it');
// nel template
echo $horse->getSex('en'); // tradotto in una lingua
echo $horse->getSex(); // nella lingua del utente
aggiungere una action senza modificare il plugin
// file: addExpirationAction.class.php
class addExpirationAction extends sfAction {
function execute() {}
}
caricare helpers fuori dalle view
public function preExecute(){ // sf1.4 sfProjectConfiguration::getActive()->loadHelpers(array('Url','Tag','I18N','Asset','Date','Mail')); }
rendere parti di HTML o altro(es. body email, JSON, PDF) con template PHP dal controller
controller{ method() { $this->getPartial('partial_name', array('var'=>'value')); } }
public function executeListjson(sfWebRequest $request){
$param = $request->getGetParameter('query', '');
$objects = Doctrine_Query::create()
->select('m.*')
->from('Model m')
->where("m.name LIKE ? ", "%$param%")
->addOrderBy('name')
->limit(50)
->execute();
$key_field = 'name';
$val_field = 'model_id';
$total_count = 0;
$pos = 0;
$json = YUI::objects_to_json($objects, $key_field, $val_field, $total_count , $pos);
$this->setLayout(false);
return $this->renderText($json);
}
// aggiungere la cache dei dati
public function executeDisabled(sfWebRequest $request) {
$this->setLayout(false);
// calcola un id per la richiesta corrente
$cache_id = $this->calcCacheKeyFor($request);
// istanzia oggetto file cache
$cache = new sfFileCache( array( 'cache_dir' => sfConfig::get('sf_cache_dir')."/configurator/" ) );
// se esiste una risposta precalcolata
if ( $cache->has($cache_id) ) {
$json = '/*cache:'.$cache_id.'*/'.$cache->get($cache_id);
}else{
// algoritmo
$json = $this->calcJSONDisabled($request);
$cache->set($cache_id, $json, 7*24*60*60 );// 1 settimana di validità
}
$q = Doctrine_Query::create()
->useQueryCache(new Doctrine_Cache_Apc(), 1*24*60*60 );
return $this->renderText($json);
}
// nei template delle form hai a disposizione $form, che ha un l'accesso all'oggetto corrente
$car = $form->getObject();
var_dump( $car->car_id );
var_dump(get_class_methods( $this ) );
// e l'oggetto corrente, ad esempio in /car hai $car nella view
// $car ha tutti gli oggetti collegati
$car->get('Model')
// se occorrono altri oggetti, nel controller attaccali a form o all'oggetto corrente
$this->form->CA = Doctrine_Query::create()
->select('c.*')
->from('CopyApproximation c')
->execute();
use_helper('I18N')
// usare la cultura corrente per mostrare immagini differenti
echo image_tag($sf_user->getCulture().'/myText.gif')
// gestione dei plurali
echo format_number_choice(
'[0]Nobody is logged|[1]There is 1 person logged|(1,+Inf]There are %1% persons logged',
array('%1%' => count_logged()), count_logged())
use_helper('CIQuickCreate','CIYUI');
$cur_fc = sfConfig::get('env')=='prod' ? '/backend.php' : '/backend_dev.php';
echo YUI::sf_ajax_autocomplete(
$form,
'color_id',
"$cur_fc/color/listjson",
'name',
'color_id',
array('url'=>"$cur_fc/color/new?menu=0",'height'=>'600'));
// http://www.symfony-project.org/forms/1_2/en/A-Widgets#chapter_a_choice_widgets
$w = new sfWidgetFormChoice(array(
'renderer_class' => 'sfWidgetFormPropelJQueryAutocompleter',
'renderer_options' => array(
'model' => 'Article',
'url' => '/autocomplete_script',
),
));
class mymoduleActions extends sfActions{
// Store data in the user session
$this->getUser()->setAttribute('nickname', $nickname);
// Retrieve data from the user session with a default value
$nickname = $this->getUser()->getAttribute('nickname', 'Anonymous Coward');
// altri metodi
$this->getUser()->getAttributeHolder()->remove('nickname');
$this->getUser()->getAttributeHolder()->clear();
}
// nella view
<?php echo $sf_user->getAttribute('nickname') ?>
in una adminForm, inserire nel model il getter relativo, quindi in generator.yml è disponibile il campo "brand"
class Car extends BaseCar {
public function getBrand(){
return $this->get('Model')->get('Brand')->get('name');
}
}
richiede plugin:install sfFormExtraPlugin
class ContentForm extends BaseContentForm {
public function configure() {
$img_field = 'image';
$this->widgetSchema[$img_field] = new sfWidgetFormInputFileEditable(array(
'label' => 'Immagine',
'file_src' => $this->getObject()->getImageThubnail(150, 150),
'is_image' => true,
'edit_mode' => !$this->isNew() ,
'template' => '<div class="form-img-upload">%file%<br />%input%<br />%delete% %delete_label%</div>',
));
/*
* max_size: The maximum file size
* mime_types: Allowed mime types array or category (available categories: web_images)
* mime_type_guessers: An array of mime type guesser PHP callables (must return the mime type or null)
* mime_categories: An array of mime type categories (web_images is defined by default)
* path: The path where to save the file - as used by the sfValidatedFile class (optional)
* validated_file_class: Name of the class that manages the cleaned uploaded file (optional)
*/
$this->validatorSchema[$img_field] = new sfValidatorFile(array(
'required' => false,
'path' => CI::calcFilePATH($this->getObject(), $img_field),
'mime_types' => 'web_images',
'max_size' => 65*1000 // 65 KB
));
$this->validatorSchema[$img_field.'_delete'] = new sfValidatorBoolean();
/* validatori da ricordare:
$this->setValidators(array(
'email' => new sfValidatorEmail(),
'message' => new sfValidatorString(array('max_length' => 255)),
)); */
}
}
$this->widgetSchema['body'] = new sfWidgetFormTextarea(array(),array(
'rows' => 8,
'cols' => 60
));
/*
$this->widgetSchema['body'] = new sfWidgetFormTextareaTinyMCE(
array(),
array(
'class' => 'foo'
//'theme' The Tiny MCE theme (advanced by default)
//width Width
//height Height
//config An array of specific JavaScript configuration
)
);
*/
$this->validatorSchema['body'] = new sfValidatorString(array('max_length' => 10000));
$img_field = 'movie';
$this->widgetSchema[$img_field] = new sfWidgetFormInputFileEditable(array(
'label' => 'Filmato',
'file_src' => $this->getObject()->getMovieThumbnailTag(),
'edit_mode' => !$this->isNew() ,
'template' => '<div>%input%<br /> %file% %delete% %delete_label%</div>'
));
$this->validatorSchema[$img_field] = new sfValidatorFile(array(
'required' => false,
'path' => CI::calcFilePATH($this->getObject(), $img_field),
'mime_types' => array(
'video/3gpp',
'video/dl' ,
'video/gl' ,
'video/mj2',
'video/mpeg',
'video/quicktime',
'video/vdo',
'video/vivo',
'video/vnd.fvt' ,
'video/vnd.mpegurl',
'video/vnd.nokia.interleaved-multimedia' ,
'video/vnd.objectvideo' ,
'video/vnd.sealed.mpeg1' ,
'video/vnd.sealed.mpeg4' ,
'video/vnd.sealed.swf',
'video/vnd.sealedmedia.softseal.mov' ,
'video/vnd.vivo' ,
'video/x-fli',
'video/x-ms-asf' ,
'video/x-ms-wmv' ,
'video/x-msvideo' ,
'video/x-sgi-movie'
),// avi flv mov mpeg ogg wmv
'max_size' => 36*1000*1000 // 16MB
));
$this->validatorSchema[$img_field.'_delete'] = new sfValidatorBoolean();
class ItemForm extends BaseItemForm
{
public function configure()
{
$cultures = array('it', 'en', 'fr', 'de');
$this->embedI18n($cultures);
$item = $this->getObject();
$available_note = ItemNotePeer::doSelectWithI18n(new Criteria(), 'it');
$a_id_note = array();
foreach($available_note as $n){
$a_id_note[$n->getID()] = $n->getTitle();
}
$this->widgetSchema['item_has_note_list'] = new sfWidgetFormSelectDoubleList(array(
'choices' => $a_id_note,
'label_associated' => 'Associate',
'label_unassociated' => 'Non Associate',
'is_hidden' => $item->isNew(),
'label' => 'Note associate'
));
//$this->validatorSchema->setOption('allow_extra_fields', true);
// validazione
$this->validatorSchema['item_has_notes'] = new sfValidatorChoice(array(
'choices' => $a_id_note,
'required' => false
));
parent::configure();
}
}
schema associato necessario
item_has_note: item_id: { type: integer, primaryKey: true, foreignTable: item, foreignReference: id, required: true, onDelete: cascade, onUpdate: cascade } note_id: { type: integer, primaryKey: true, foreignTable: item_note, foreignReference: id, required: true, onDelete: cascade, onUpdate: cascade } item_note: id: ~ item_note_i18n: title: { type: VARCHAR, size: '255', required: false } description: { type: LONGVARCHAR, required: false }
setup schema
tableName: blog_post columns: id: type: integer primary: true autoincrement: true title: type: string(255) notnull: true content: type: clob notnull: true excerpt: type: clob notnull: true relations: Tags: class: Tag local: blog_post_id foreign: tag_id type: many foreignType: many foreignAlias: BlogPosts refClass: BlogPostTag BlogPostTag: tableName: blog_post_tag columns: blog_post_id: type: integer primary: true tag_id: type: integer primary: true Tag: tableName: tag columns: id: type: integer primary: true autoincrement: true name: type: string(255)
form widget:
class BlogPostForm extends BaseBlogPostForm { public function configure() { $autocompleteWidget = new sfWidgetFormChoice(array( 'multiple' => true, 'choices' => $this->getObject()->getTags(), 'renderer_class' => 'sfWidgetFormJQueryAutocompleterMany', 'renderer_options' => array( 'config' => '{ json_url: "'.sfContext::getInstance()->getController()->genUrl('tag/autocomplete').'", json_cache: true, filter_hide: true, filter_selected: true, maxshownitems: 8 }') )); $this->widgetSchema['tags_list'] = $autocompleteWidget; } }
class CarFormFilter extends BaseCarFormFilter {
public function configure() {
$this->setWidget('brand_id', new sfWidgetFormDoctrineChoice(array('model' => 'Brand', 'add_empty' => true)));
$this->setValidator('brand_id', new sfValidatorDoctrineChoice(array('required' => false, 'model' => 'Brand', 'column' => 'brand_id')) );
$this->validatorSchema->addOption('allow_extra_fields', true);
}
public function getFields() {
$a = parent::getFields();
$a['brand_id'] = 'Number';
return $a;
}
public function addBrandIDColumnQuery(Doctrine_Query $q, $element, $value) {
// r pare sia sempre l'alias della tabella principale, die(var_dump( $q )); per chiarirsi le idee
$q
->leftJoin('r.Model m')
->leftJoin('m.Brand b')
->andWhere('m.brand_id = ?', $value );
}
// se occorre aggiungere più volte lo stesso alias, controllare che non sia già stato definito
// uso: if( !$this->qry_contains($q, 'province p') ){$q->leftJoin('l.Province p');}
function qry_contains($q, $needle) {
return (strpos($q->getSql(), $needle)!==false);
}
}
config/properties.ini name=myproject [production] host=myapp.example.com port=22 user=myuser dir=/home/myaccount/myproject/
Don't confuse the production server (the host server, as defined in the properties.ini file of the project) with the production environment. Doing an rsync over SSH requires several commands, symfony automates this process with just one command:
# senza --go vengono mostrate le differenze esistenti symfony sync production go
clear the cache in the production server after synchronization.
schema.yml
News:
tableName: news
columns:
id:
type: integer(8)
primary: true
autoincrement: true
date: date(25)
url: string(255)
tmb: string(255)
image: string(255)
title: string(255)
subtitle: string(255)
description: string(255)
actAs:
I18n:
fields: [title, subtitle, description]
#sf12 doctrine:build-all-reload
lib/form/BaseFormDoctrine.php
abstract class BaseFormDoctrine extends sfFormDoctrine
{
public function setup()
{
if($this->isI18n()){
$this->embedI18n(array('en', 'it'));
$this->widgetSchema->setLabel('en', 'Inglese');
$this->widgetSchema->setLabel('it', 'Italiano');
}
}
}
// forza la lingua predefinita del backend se da config non funziona
class myUser extends sfGuardSecurityUser /*sfBasicSecurityUser*/ {
public function __construct(sfEventDispatcher $dispatcher, sfStorage $storage, $options = array()) {
parent::__construct($dispatcher,$storage, $options);
$this->setCulture('it_IT');
}
}
//in routing.yml, abilita lo switch della lingua via /en/configurator
homepage:
url: /:sf_culture/
param: { module: configurator, action: index }
//in apps/your_backend_application/config/settings.yml:
all:
settings:
i18n: on
default_culture: it
// in backend/i18n carica sf_admin.it.xml e sf cc
http://symforc.com/post/2008/07/10/Foreign-keys-onDelete-for-dummies
class Subscriber extends BaseSubscriber{
public function preDelete($event) {
parent::preDelete($event);
$r = Doctrine_Query::create()
->select('s.*')
->from('Subscription s')
->where('s.subscriber_id = ?', $this->get('subscriber_id') )
->execute();
foreach( $r as $s){ $s->delete(); }
}
}
configurator.yml
config: list: table_method: retrieveBackendJobList
class SubscriberTable extends Doctrine_Table{
public function retrieveBackendSubscriber(Doctrine_Query $q){
$rootAlias = $q->getRootAlias();// ritorna 'r'
$q->leftJoin($rootAlias.'.Locality l');
$q->leftJoin('l.Province p');
$q->leftJoin('p.State st');
$q->leftJoin($rootAlias.'.Subscription s');
$q->leftJoin('s.CopyApproximation co');
$q->leftJoin('s.Lang la');
return $q;
}
}
doctrine genera automaticamente i left join necessari
$q = Doctrine_Query::create()
->from('Car c, c.Model m, m.Brand b, c.Fuel f, c.Gear g, c.Color p, c.Door d, c.CarImage, c.CarType')
->where('c.car_id=?', $_GET['car_id'])
-limit(1);
$pdo = Doctrine_Manager::getInstance()->getCurrentConnection()->getDbh(); $query = "SELECT max(0+number) as num FROM invoice WHERE anno = :anno"; $stmt = $pdo->prepare($query); $stmt->execute( array( "anno" => $year, )); $a = $stmt->fetch(); return $a['num'];
debug
$sql = BasePeer::createSelectSql($c, $a=array() );
$c = new Criteria();
$c->addJoin( ItemNotePeer::ID, ItemHasNotePeer::NOTE_ID, Criteria::LEFT_JOIN);
$c->addJoin( ItemNotePeer::ID, ItemNoteI18nPeer::ID, Criteria::LEFT_JOIN);
$c->add(ItemHasNotePeer::ITEM_ID, $item_id);
$c->add(ItemNoteI18nPeer::CULTURE, $this->getUser()->getCulture() );
$c->addSelectColumn(ItemNoteI18nPeer::TITLE);
$c->setPrimaryTableName( ItemNotePeer::TABLE_NAME );
//var_dump( $c->toString() );
$data = ItemNotePeer::doSelectWithI18n($c);
app.yml configuration file contain any setting for your specific application. available through the global sfConfig class, and keys are prefixed with the app_ string:
sfConfig::get('app_active_days');
sfConfig::get('sf_environment') == 'prod'
sfConfig::get('sf_environment')== 'dev'
// altre chiavi interessanti
sfConfig::get('sf_lib_dir')
sfConfig::get('sf_root_dir')
sfConfig::get('sf_upload_dir')
sfConfig::get('sf_data_dir')
sfConfig::get('sf_symfony_lib_dir')
sfConfig::get('sf_apps_dir')
sfConfig::get('sf_debug')
// lib/form/doctrine/ProductForm.class.php
public function configure()
{
$subForm = new sfForm();
for ($i = 0; $i < 2; $i++)
{
$productPhoto = new ProductPhoto();
$productPhoto->Product = $this->getObject();
$form = new ProductPhotoForm($productPhoto);
$subForm->embedForm($i, $form);
}
$this->embedForm('newPhotos', $subForm);
}
Routing:
external:
url: /external/:filename.:action
param: { module: external}
Action:
class externalActions extends sfActions
{
public function executeJs(sfWebRequest $request)
{
sfConfig::set('sf_web_debug',false);
$this->setLayout(false);
$this->getResponse()->setHttpHeader('Content-type', 'text/javascript');
$this->setTemplate($this->getRequestParameter('action').'/'.$this->getRequestParameter('filename'));
return '.'.$this->getRequestParameter('action');// ritorna il nome del template "$action.js.php"
}
public function executeCss(sfWebRequest $request)
{
sfConfig::set('sf_web_debug',false);
$this->setLayout(false);
$this->getResponse()->setHttpHeader('Content-type', 'text/css');
$this->setTemplate($this->getRequestParameter('action').'/'.$this->getRequestParameter('filename'));
return '.'.$this->getRequestParameter('action');// ritorna il nome del template "$action.js.php"
}
}