You are on page 1of 135

Frameworks de desarrollo

Symfony
Clase 2

Javier Eguíluz
javier.eguiluz@gmail.com
Esta obra dispone de una licencia de tipo Creative
Commons Reconocimiento‐No comercial‐ Compartir 
bajo la misma licencia 3.0 

Se prohíbe explícitamente el uso de este material en 
actividades de formación comerciales

http://creativecommons.org/licenses/by‐nc‐sa/3.0/es/
This work is licensed under a Creative Commons
Attribution‐Noncommercial‐Share Alike 3.0 

The use of these slides in commercial courses or


trainings is explicitly prohibited

http://creativecommons.org/licenses/by‐nc‐sa/3.0/es/
Capítulo 6

Profundizando en 
el modelo
El objeto Criteria
de Propel
apps/frontend/modules/job/actions/actions.class.php

class jobActions extends sfActions


{
public function executeIndex(sfWebRequest $request)
{
$this‐>listado = JobeetJobPeer::doSelect(
new Criteria()
);
}
}
SELECT
[ALL | DISTINCT | DISTINCTROW ]
[HIGH_PRIORITY]
[STRAIGHT_JOIN]
[SQL_SMALL_RESULT] [SQL_BIG_RESULT] [SQL_BUFFER_RESULT]
[SQL_CACHE | SQL_NO_CACHE] [SQL_CALC_FOUND_ROWS]
select_expr [, select_expr ...]
[FROM table_references
[WHERE where_condition]
[GROUP BY {col_name | expr | position}
[ASC | DESC], ... [WITH ROLLUP]]
[HAVING where_condition]
[ORDER BY {col_name | expr | position}
[ASC | DESC], ...]
[LIMIT {[offset,] row_count | row_count OFFSET offset}]
[PROCEDURE procedure_name(argument_list)]
[INTO OUTFILE 'file_name' export_options
| INTO DUMPFILE 'file_name'
| INTO var_name [, var_name]]
[FOR UPDATE | LOCK IN SHARE MODE]]
SELECT *
FROM JobeetJob
WHERE created_at > time() ‐ 86400 * 30
apps/frontend/modules/job/actions/actions.class.php

public function executeIndex(sfWebRequest $request)


{
$criteria = new Criteria();
$criteria‐>add( WHERE
JobeetJobPeer::CREATED_AT,
time() ‐ 86400 * 30, condición
Criteria::GREATER_THAN
);
$this‐>listado = JobeetJobPeer::doSelect($criteria);
} FROM JobeetJob SELECT *
apps/frontend/modules/job/actions/actions.class.php

public function executeIndex(sfWebRequest $request)

{
$criteria = new Criteria();
$criteria‐>add(
JobeetJobPeer::COMPANY,
'Empresa ACME'
);
$this‐>listado = JobeetJobPeer::doSelect($criteria);
}
Depurando el código 
SQL generado
log/frontend_dev.log

Dec 6 15:47:12 symfony [debug] {sfPropelLogger} exec: SET NAMES 'utf8‘

Dec 6 15:47:12 symfony [debug] {sfPropelLogger} prepare: SELECT 
jobeet_job.ID, jobeet_job.CATEGORY_ID, jobeet_job.TYPE, 
jobeet_job.COMPANY, jobeet_job.LOGO, jobeet_job.URL, 
jobeet_job.POSITION, jobeet_job.LOCATION, jobeet_job.DESCRIPTION, 
jobeet_job.HOW_TO_APPLY, jobeet_job.TOKEN, jobeet_job.IS_PUBLIC, 
jobeet_job.CREATED_AT, jobeet_job.UPDATED_AT FROM ''jobeet_job'' 
WHERE jobeet_job.CREATED_AT>:p1
SQL Injection
Dec 6 15:47:12 symfony [debug] {sfPropelLogger} Binding '2008‐11‐06 
15:47:12' at position :p1 w/ PDO type PDO::PARAM_STR
Serializando objetos
lib/model/JobeetJob.php

class JobeetJob extends BaseJobeetJob


{
public function save(PropelPDO $con = null)
{
if ($this‐>isNew() && !$this‐>getExpiresAt())
{
$now = $this‐>getCreatedAt() ?
$this‐>getCreatedAt('U') :
time();
$this‐>setExpiresAt($now + 86400 * 30); 
}

return parent::save($con);
}
}
apps/frontend/modules/job/actions/actions.class.php

public function executeIndex(sfWebRequest $request)


{
$criteria = new Criteria();
$criteria‐>add(
JobeetJobPeer::CREATED_AT EXPIRES_AT,
time(),
Criteria::GREATER_THAN
);

$this‐>listado = JobeetJobPeer::doSelect(
$criteria
);
}
Personalizando la 
configuración
¿Dónde está el problema?
public function executeIndex(sfWebRequest $request)
{
$criteria = new Criteria();
$criteria‐>add(
JobeetJobPeer::CREATED_AT,
time() ‐ 86400 * 30,
mejor como opción 
de configuración
Criteria::GREATER_THAN
);
$this‐>listado = JobeetJobPeer::doSelect($criteria);
}
apps/frontend/config/app.yml
all:
dias_activa:  30

sfConfig::get('app_dias_activa')
Refactorización
¿Dónde está el problema?
Controlador (MVC)
public function executeIndex(sfWebRequest $request)
{
$criteria = new Criteria();
$criteria‐>add(
JobeetJobPeer::CREATED_AT, Modelo (MVC)
time() ‐ 86400 * 30,
Criteria::GREATER_THAN
);
$this‐>listado = JobeetJobPeer::doSelect($criteria);
}
Modelo lib/model/JobeetJobPeer.php
class JobeetJobPeer extends BaseJobeetJobPeer {
static public function getActiveJobs() {
$criteria = new Criteria();
$criteria‐>add(
self::EXPIRES_AT,
time(),
Criteria::GREATER_THAN
);

return self::doSelect($criteria);
}
}

Controlador apps/frontend/modules/job/actions/ actions.class.php


public function executeIndex(sfWebRequest $request) {
$this‐>jobeet_job_list = JobeetJobPeer::getActiveJobs();
}
static public function getActiveJobs()
{
$criteria = new Criteria();
$criteria‐>add(
self::EXPIRES_AT,
time(),
Criteria::GREATER_THAN
);
$criteria‐>addDescendingOrderByColumn(
self::EXPIRES_AT ORDER BY ___ DESC
);
return self::doSelect($criteria);
}
Mostrando las 
categorías en la 
portada
lib/model/JobeetCategoryPeer.php

class JobeetCategoryPeer extends BaseJobeetCategoryPeer


{
static public function getWithJobs()
{
$criteria = new Criteria();
$criteria‐>addJoin(self::ID, JobeetJobPeer::CATEGORY_ID);
$criteria‐>add(
JobeetJobPeer::EXPIRES_AT,
time(),
Criteria::GREATER_THAN
);
$criteria‐>setDistinct(); SELECT DISTINCT
return self::doSelect($criteria);
}
}
apps/frontend/modules/job/actions/actions.class.php

public function executeIndex(sfWebRequest $request) {


$this‐>categories = JobeetCategoryPeer::getWithJobs();
}

apps/frontend/modules/job/templates/indexSuccess.php
<?php foreach ($categories as $category): ?>
...
<h1><?php echo $category ?></h1>
...
<?php foreach ($category‐>getActiveJobs() as $i => $job): ?>
...
<?php endforeach; ?>
...
<?php endforeach; ?> 
Controlador      Vista             Modelo
getWithJobs()
1
JobeetCategoryPeer.php
getActiveJobs()
3

executeIndex() indexSuccess.php JobeetCategory.php

2 4 getActiveJobs()
$categories
JobeetJobPeer.php
Limitando los 
resultados
lib/model/JobeetJobPeer.php

class JobeetJobPeer extends BaseJobeetJobPeer {


static public function getActiveJobs($max = 10) {
$criteria = new Criteria();
$criteria‐>add( mejor en un archivo 
self::EXPIRES_AT, de configuración
time(),
Criteria::GREATER_THAN
);
$criteria‐>setLimit($max); 

return self::doSelect($criteria);
}
}
apps/frontend/modules/job/templates/indexSuccess.php

<?php foreach ($category‐>getActiveJobs(


sfConfig::get('app_max_jobs_on_homepage')
) as $i => $job): ?> 

apps/frontend/config/app.yml

all:
active_days:          30
max_jobs_on_homepage: 10
Archivos de datos 
dinámicos
data/fixtures/020_jobs.yml
JobeetJob:
<?php for ($i = 100; $i <= 130; $i++): ?>
job_<?php echo $i ?>:
category_id: programming
company: Company <?php echo $i."\n" ?>
position: Web Developer
location: Paris, France
description: |
Lorem ipsum dolor sit amet,
consectetur adipisicing elit.
how_to_apply: |
Send your resume to lorem.ipsum
[at] company_<?php echo $i ?>.sit
is_public: true
is_activated: true
token: job_<?php echo $i."\n" ?>
email: job@example.com
<?php endfor; ?>
Restringir el acceso a 
una oferta de trabajo
apps/frontend/config/routing.yml

job_show_user:
url: /job/:company_slug/:location_slug/:id/:position_slug
class: sfPropelRoute
options:
model: JobeetJob
type:  object
method_for_criteria: doSelectActive
param: { module: job, action: show }
requirements:
id: \d+
sf_method: [get]
lib/model/JobeetJobPeer.php

class JobeetJobPeer extends BaseJobeetJobPeer


{
static public function doSelectActive(Criteria $criteria)
{
$criteria‐>add(
JobeetJobPeer::EXPIRES_AT,
time(),
Criteria::GREATER_THAN
);
return self::doSelectOne($criteria);
}
}
Capítulo 7

La página de cada 
categoría
La ruta de la 
categoría
apps/frontend/config/routing.yml
category:
url:      /category/:slug
class:    sfPropelRoute
param:    { module: category, action: show }
options:  { model: JobeetCategory, type: object }

lib/model/JobeetCategory.php

public function getSlug() {


return Jobeet::slugify($this‐>getName());
}
El enlace a la página 
de cada categoría
apps/frontend/modules/job/templates/indexSuccess.php
<h1>
<?php echo link_to($category,
'category',
$category) ?>
</h1>

$total = $category‐>countActiveJobs() ‐
sfConfig::get('app_max_jobs_on_homepage')

<?php if ($total > 0): ?>


...
echo link_to($count, 'category', $category)
...
<?php endif; ?>
DRY (Don’t Repeat Yourself)
A process philosophy aimed at reducing
duplication, particularly in computing.
[…]
When the DRY principle is applied successfully, a modification of 
any single element of a system does not change other logically‐
unrelated elements. Additionally, elements that are logically 
related all change predictably and uniformly, and are thus kept 
in sync.
xxxPeer::doSelect()
xxxPeer::doSelectOne()
xxxPeer::doSelectRS()
xxxPeer::retrieveByPK()
xxxPeer::retrieveByPKs()
xxxPeer::doCount()
xxxPeer::doInsert()
xxxPeer::doDelete()
xxxPeer::doUpdate()
Creando el módulo 
de las categorías
$ ./symfony propel:generate‐module frontend category
actions/ templates/
index edit indexSuccess
show update editSuccess
new delete newSuccess
create

$ ./symfony generate:module frontend category


actions/ templates/
index indexSuccess
nombre slug
Actualizando la base 
de datos
config/schema.yml

propel:
jobeet_category:
id:        ~
name: { type: varchar(255), required: true }
slug:
type: varchar(255)
required: true
index: unique
getSlug()
lib/model/JobeetCategory.php

public function setName($name)


{
parent::setName($name);

$this‐>setSlug(Jobeet::slugify($name));
}

$ ./symfony propel:build‐all‐load
apps/frontend/modules/category/actions/actions.class.php
class categoryActions extends sfActions
{
public function executeShow(sfWebRequest $request)
{
$this‐>category = $this‐>getRoute()‐>getObject();
}
}

apps/frontend/modules/category/templates/showSuccess.php

<h1><?php echo $category ?></h1>


...
<?php foreach ($category‐>getActiveJobs() as $i => $job): ?>
...
Elementos parciales
elementos parciales

“trozos de código de plantilla que se 
pueden reutilizar en varias plantillas”

Son iguales que las plantillas en todo salvo que 
su nombre empieza por un guión bajo (_)
apps/frontend/modules/job/templates/_list.php
<table class="jobs">
<?php foreach ($jobs as $i => $job): ?>
<tr class="<?php echo fmod($i, 2) ? 'even' : 'odd' ?>">
<td class="location">
<?php echo $job‐>getLocation() ?>
</td>
<td class="position">
<?php echo link_to($job‐>getPosition(),
'job_show_user',
$job) ?>
</td>
<td class="company">
<?php echo $job‐>getCompany() ?>
</td>
</tr>
<?php endforeach; ?>
</table>
apps/frontend/modules/job/templates/indexSuccess.php
<?php include_partial(
'job/list',
array('jobs' => $category‐>getActiveJobs(
sfConfig::get('app_max_jobs_on_homepage')
))
) ?>

apps/frontend/modules/job/templates/showSuccess.php
<?php include_partial(
'job/list',
array('jobs' => $category‐>getActiveJobs())
) ?> 
Paginación
apps/frontend/modules/category/actions/actions.class.php

public function executeShow(sfWebRequest $request)


{
$this‐>category = $this‐>getRoute()‐>getObject();

$this‐>pager = new sfPropelPager(


'JobeetJob', app.yml
sfConfig::get('app_max_jobs_on_category')
);
$this‐>pager‐>setCriteria(
$this‐>category‐>getActiveJobsCriteria()
);
$this‐>pager‐>setPage($request‐>getParameter('page', 1)); 
$this‐>pager‐>init();
}
getResults()
getNbResults()
haveToPaginate()
getLinks()
getPage()
getPreviousPage()
getNextPage()
getLastPage()
Capítulo 8

Pruebas unitarias
test/
unit/
Prueban funciones y 
métodos individualmente

functional/
Prueban la aplicación en 
su conjunto
El framework de 
pruebas lime
require_once dirname(__FILE__).'/../bootstrap/unit.php';
$t = new lime_test(1, new lime_output_color());

número de pruebas 
esperadas

ok($condicion)
is($valor1, $valor2)
isnt($valor1, $valor2)
like($cadena, $expresionRegular)
unlike($cadena, $expresionRegular)
is_deeply($array1, $array2)
Ejecutando pruebas 
unitarias
test/unit/JobeetTest.php
require_once dirname(__FILE__).'/../bootstrap/unit.php';

$t = new lime_test(1, new lime_output_color());


$t‐>pass('This test always passes.');

$ ./symfony test:unit Jobeet
Probando el método 
slugify()
Sensio Labs sensio‐labs
Paris, France paris‐france

test/unit/JobeetTest.php
require_once dirname(__FILE__).'/../bootstrap/unit.php'; 

$t = new lime_test(6, new lime_output_color()); 

$t‐>is(Jobeet::slugify('Sensio'), 'sensio'); 
$t‐>is(Jobeet::slugify('sensio labs'), 'sensio‐labs'); 
$t‐>is(Jobeet::slugify('sensio labs'), 'sensio‐labs'); 
$t‐>is(Jobeet::slugify('paris,france'), 'paris‐france'); 
$t‐>is(Jobeet::slugify(' sensio'), 'sensio'); 
$t‐>is(Jobeet::slugify('sensio '), 'sensio');
test/unit/JobeetTest.php
require_once dirname(__FILE__).'/../bootstrap/unit.php'; 

$t = new lime_test(6, new lime_output_color()); 

$t‐>comment('::slugify()');
$t‐>is(Jobeet::slugify('Sensio'), 'sensio',
'::slugify() pasa la cadena de texto a minúsculas');
$t‐>is(Jobeet::slugify('sensio labs'), 'sensio‐labs',
'::slugify() sustituye los espacios en blanco por ‐');
...
Pruebas unitarias 
para Propel
$ mysqladmin ‐uroot ‐p create jobeet_test

$ symfony configure:database ‐‐env=test
"mysql:host=localhost;dbname=jobeet_test"
root ConTraSenA

config/databases.yml
config/databases.yml
dev:
propel:
class: sfPropelDatabase
param:
classname: DebugPDO

test:
propel:
class: sfPropelDatabase
param:
classname: DebugPDO
dsn:       'mysql:host=localhost;dbname=jobeet_test'

all:
propel:
class: sfPropelDatabase
param:
dsn:      'mysql:host=localhost;dbname=jobeet'
username: root
password: null
test/bootstrap/propel.php

include(dirname(__FILE__).'/unit.php');

$configuration = ProjectConfiguration::getApplicationConfiguration(
'frontend',
'test',
true
);

new sfDatabaseManager($configuration);

$loader = new sfPropelData();


$loader‐>loadData(sfConfig::get('sf_test_dir').'/fixtures');
test/unit/model/JobeetJobTest.php

include(dirname(__FILE__).'/../../bootstrap/propel.php');

$t = new lime_test(1, new lime_output_color());

$t‐>comment('‐>getCompanySlug()');
$job = JobeetJobPeer::doSelectOne(new Criteria());
$t‐>is(
$job‐>getCompanySlug(),
Jobeet::slugify($job‐>getCompany()),
'‐>getCompanySlug() devuelve el slug del nombre de la empresa'
);
Conjuntos de 
pruebas unitarias
$ ./symfony test:unit
Capítulo 9

Pruebas 
funcionales
La clase sfBrowser
sfBrowser

servidor web

aplicación  aplicación 
Symfony Symfony
get() reload() setHttpHeader()
post() click() setAuth()
call() select() setCookie()
back() deselect() removecookie()
forward() restart() clearCookie()
followRedirect()
$browser = new sfBrowser();

$browser‐>
get('/')‐>
click('Design')‐>
get('/category/programming?page=2')‐>
get('/category/programming', array('page' => 2))‐>
post('search', array('keywords' => 'php'))
;
La clase 
sfTestFunctional
sfBrowser

sfTestFunctional

request user response


test/functional/frontend/ categoryActionsTest.php
include(dirname(__FILE__).'/../../bootstrap/functional.php');
$browser = new sfTestFunctional(new sfBrowser());

$browser‐>
get('/category/index')‐>

with('request')‐>begin()‐>
isParameter('module', 'category')‐>
isParameter('action', 'index')‐>
end()‐>

with('response')‐>begin()‐>
isStatusCode(200)‐>
checkElement('body', '!/This is a temporary page/')‐>
end()
;
interfaz fluída

___() ___() ___() ___()


request response
isParameter() checkElement()
isFormat() isHeader()
isMethod() isStatusCode()
hasCookie() isRedirected()
isCookie()
Ejecutando pruebas 
funcionales
$ ./symfony test:functional frontend categoryActions
Datos de prueba
lib/test/JobeetTestFunctional.class.php

class JobeetTestFunctional extends sfTestFunctional


{
public function loadData()
{
$loader = new sfPropelData();
$loader‐>loadData(
sfConfig::get('sf_test_dir').'/fixtures'
);
return $this;
}
}
Conjuntos de 
pruebas funcionales
$ ./symfony test:functional frontend
Conjuntos de 
pruebas
$ symfony test:unit
$ symfony test:functional frontend
$ symfony test:functional backend
+ $ symfony test:functional ......

$ symfony test:all
Capítulo 10

Los formularios
1. Crear código HTML del formulario
2. Definir reglas de validación para los datos
3. Procesar valores enviados por el usuario
4. Guardar la información en la base de datos
5. Mostrar posibles mensajes de error
6. Volver a mostrar los datos en el formulario
Symfony ya incluye...

• Validación (para cada campo)

• Widgets (campos del formulario)

• Formularios (widgets + validación)


Formularios
class ContactForm extends sfForm
{
public function configure()
{
$this‐>setWidgets(array(
'email' => new sfWidgetFormInput(),
'message' => new sfWidgetFormTextarea(),
));

$this‐>setValidators(array(
'email' => new sfValidatorEmail(),
'message' => new sfValidatorString(array(
'max_length' => 255)
),
));
}
}
sfWidgetFormChoice sfWidgetFormInputHidden
sfWidgetFormChoiceMany sfWidgetFormInputPassword
sfWidgetFormDate sfWidgetFormPropelChoice
sfWidgetFormDateRange sfWidgetFormPropelChoiceMany
sfWidgetFormDateTime sfWidgetFormPropelSelect
sfWidgetFormFilterDate sfWidgetFormPropelSelectMany
sfWidgetFormFilterInput sfWidgetFormSchema
sfWidgetFormI18nDate sfWidgetFormSchemaDecorator
sfWidgetFormI18nDateTime sfWidgetFormSchemaForEach
sfWidgetFormI18nSelectCountry sfWidgetFormSchemaFormatter
sfWidgetFormI18nSelectCurrency sfWidgetFormSelect
sfWidgetFormI18nSelectLanguage sfWidgetFormSelectCheckbox
sfWidgetFormI18nTime sfWidgetFormSelectMany
sfWidgetFormInput sfWidgetFormSelectRadio
sfWidgetFormInputCheckbox sfWidgetFormTextarea
sfWidgetFormInputFile sfWidgetFormTime
sfWidgetFormInputFileEditable
$this‐>mergeForm(new OtroForm());
$this‐>embedForm('name', new OtroForm());
Formularios de 
Propel
schema.yml

$ ./symfony propel:build‐forms

JobeetJobForm JobeetCategoryForm JobeetAffiliateForm

lib/form/
class JobeetJobForm extends BaseJobeetJobForm
{
public function configure()
{
unset(
$this['created_at'],
$this['updated_at'],
$this['expires_at'],
$this['is_activated']
);
}
}
class JobeetJobForm extends BaseJobeetJobForm
{
public function configure()
{
...
$this‐>validatorSchema['email'] = 
new sfValidatorEmail(); 
}
}
class JobeetJobForm extends BaseJobeetJobForm
{
public function configure()
{
...
$this‐>widgetSchema['type'] =
new sfWidgetFormChoice(array(
'choices' => JobeetJobPeer::$types,
'expanded' => true,
));
} class JobeetJobPeer extends BaseJobeetJobPeer {

} static public $types = array(


'full‐time' => 'Full time',
'part‐time' => 'Part time',
'freelance' => 'Freelance',
);
// ...
}
sfWidgetFormChoice
multiple expanded widget
false false

true false

false true

true true
class JobeetJobForm extends BaseJobeetJobForm
{
public function configure()
{
...
$this‐>validatorSchema['type'] = 
new sfValidatorChoice(array(
'choices' => array_keys(JobeetJobPeer::$types),
));
}
}
class JobeetJobForm extends BaseJobeetJobForm
{
public function configure()
{
...
$this‐>widgetSchema‐>setLabels(array(
'category_id' => 'Category',
'is_public' => 'Public?',
'how_to_apply' => 'How to apply?',
));
}
}
class JobeetJobForm extends BaseJobeetJobForm
{
public function configure()
{
...
$this‐>widgetSchema['logo'] = 
new sfWidgetFormInputFile(array(
'label' => 'Company logo',
));

$this‐>validatorSchema['logo'] = 
new sfValidatorFile(array(
'required' => false,
'label' => sfConfig::get('sf_upload_dir').'/jobs',
'mime_types' => 'web_images',
));
}
}
sfValidatorFile

1. Valida que el archivo subido sea una 
imagen
2. Cambia el nombre del archivo por un 
valor único
3. Guarda el archivo en la ruta indicada
4. Actualiza el valor de la columna logo
class JobeetJobForm extends BaseJobeetJobForm
{
public function configure()
{
...
$this‐>widgetSchema‐>setHelp(
'is_public',
'Indica si la oferta de trabajo se puede
publicar en sitios web de afiliados'
);
}
}
apps/frontend/modules/job/templates/ newSuccess.php

<?php use_stylesheet('job.css') ?>

<h1>Post a Job</h1>

<?php
include_partial('form', array('form' => $form))
?>

parcial  _form
apps/frontend/modules/job/templates/ _form.php
<?php include_stylesheets_for_form($form) ?>
<?php include_javascripts_for_form($form) ?>
method, enctype
<?php echo form_tag_for($form, '@job') ?>
<table id="job_form">
<tfoot><tr><td colspan="2">
<input type="submit" value="Preview job" /> 
</td></tr></tfoot>
<tbody>
<?php echo $form ?>
</tbody>
</table>
</form>
Formulario Widgets
render() renderRow()
renderHiddenFields() render()
hasErrors() renderLabel()
hasGlobalErrors() renderError()
getGlobalErrors() renderHelp()
renderGlobalErrors()
<?php echo $form ?>

<?php foreach ($form as $widget): ?>


<?php echo $widget‐>renderRow() ?>
<?php endforeach; ?> 
apps/frontend/modules/job/actions/ actions.class.php
public function executeNew(sfWebRequest $request) {
...
}
public function executeCreate(sfWebRequest $request) {
...
}
public function executeEdit(sfWebRequest $request) {
...
}
public function executeUpdate(sfWebRequest $request) {
...
}
public function executeDelete(sfWebRequest $request) {
...
}
protected function processForm(sfWebRequest $request, sfForm $form)

...
}
apps/frontend/modules/job/actions/ actions.class.php

public function executeNew(sfWebRequest $request)


{
$job = new JobeetJob();
$job‐>setType('full‐time');

$this‐>form = new JobeetJobForm($job);


}
lib/model/ JobeetJob.php
public function save(PropelPDO $con = null)
{
// ...
if (!$this‐>getToken()) {
$this‐>setToken(
sha1($this‐>getEmail().rand(11111, 99999))
);
}

return parent::save($con);
}
lib/form/ JobeetJobForm.class.php
class JobeetJobForm extends BaseJobeetJobForm {
public function configure() {
unset( $this['token'] );
}
}
apps/frontend/config/ routing.yml
job:
class:        sfPropelRouteCollection
options:      { model: JobeetJob, column: token }
requirements: { token: \w+ }

http://localhost.jobeet/job/TOKEN/edit
La página de 
previsualización
apps/frontend/modules/category/templates/showSuccess.php

<?php if($sf_request‐>getParameter('token') == $job‐>getToken()): ?>


<?php include_partial('job/admin', array('job' => $job)) ?>
<?php endif; ?>

<h3>Admin</h3> apps/frontend/modules/job/templates/_admin.php
<ul>
<?php if (!$job‐>getIsActivated()): ?>
<li><?php echo link_to('Edit', 'job_edit', $job) ?></li> 
<li><?php echo link_to('Publish', 'job_edit', $job) ?></li>
<?php endif; ?>
...
<?php if ($job‐>isExpired()): ?>
Expired
<?php else: ?>
Expires in <strong>
<?php echo $job‐>getDaysBeforeExpires() ?></strong>
days
<?php endif; ?>
...
Activando y 
publicando las 
ofertas
apps/frontend/config/ routing.yml
job:
class:   sfPropelRouteCollection
options:
model:          JobeetJob
column:         token
object_actions: { publish: put }
requirements:
token: \w+
apps/frontend/modules/job/actions/actions.class.php

public function executePublish(sfWebRequest $request)


{
$request‐>checkCSRFProtection();

$job = $this‐>getRoute()‐>getObject();
$job‐>publish();

$this‐>getUser()‐>setFlash(
'notice',
sprintf('Your job is now online for %s days.', 
sfConfig::get('app_active_days'))
);

$this‐>redirect($this‐>generateUrl('job_show_user', $job));
}
Capítulo 11

Probando los 
formularios
Enviando un 
formulario
test/functional/frontend/jobActionsTest.php
$browser‐>info('3 ‐ Post a Job page')‐>
info(' 3.1 ‐ Submit a Job')‐>

get('/job/new')‐>
with('request')‐>begin()‐>
isParameter('module', 'job')‐>
isParameter('action', 'new')‐>
end()‐> Preview your job
click('Preview your job', array('job' => array(
'company' => 'Sensio Labs',
'url' => 'http://www.sensio.com/',
'logo' => sfConfig::get('sf_upload_dir').'/jobs/sensio‐labs.gif', 
'position' => 'Developer',
'location' => 'Atlanta, USA',
'is_public' => false,
)))‐>

with('request')‐>begin()‐>
isParameter('module', 'job')‐>
isParameter('action', 'create')‐>
end()‐>
;
Seguridad
$ symfony generate:app jobeet ‐‐escaping‐strategy=on ‐‐csrf‐secret=secreto frontend

XSS
&lt;p&gt;Soy un 
<p>Soy un usuario 
usuario 
malvado</p>
malvado&lt;/p&gt;
y voy a meter JS 
y voy a meter JS
<script 
&lt;script
type="text/javascript
type=&quot;text/javas
">document.write("Hol
cript&quot;&gt;docume
a!")</script>
nt.write(&quot;Hola!&
quot;)&lt;/script&gt; 
$ symfony generate:app jobeet ‐‐escaping‐strategy=on ‐‐csrf‐secret=secreto frontend

CSRF
<form>
<input type="hidden" 
<form>
name="_csrf_token" 
<input type="text" .../>
value="..." /> 
<input type="text" .../>
<input type="text" .../>
...
<input type="text" .../>
</form>
...
</form>
Tareas de 
mantenimiento
$ php lib/vendor/symfony/data/bin/symfony
lib/task/JobeetCleanupTask.class.php
class JobeetCleanupTask extends sfBaseTask {
protected function configure() {
$this‐>addOptions(array(
new sfCommandOption('env', null, sfCommandOption::PARAMETER_REQUIRED, 
'The environement', 'prod'),
new sfCommandOption('days', null, sfCommandOption::PARAMETER_REQUIRED, 
'', 90), ));

$this‐>namespace = 'jobeet';
$this‐>name = 'cleanup';
$this‐>briefDescription = 'Cleanup Jobeet database';

$this‐>detailedDescription = <<<EOF
The [jobeet:cleanup|INFO] task cleans up the Jobeet database:  [./symfony 
jobeet:cleanup ‐‐env=prod ‐‐days=90|INFO]
EOF;
}

protected function execute($arguments = array(), $options = array()) {


$databaseManager = new sfDatabaseManager($this‐>configuration);
$nb = JobeetJobPeer::cleanup($options['days']);
$this‐>logSection('propel', sprintf('Removed %d stale jobs', $nb));
}
}

You might also like