Advanced symfony Techniques

Post on 15-Jan-2015

12.726 views 2 download

Tags:

description

Go beyond the documentation and explore some of what's possible if you stretch symfony to its limits. We will look at a number of aspects of symfony 1.4 and Doctrine 1.2 and tease out some powerful functionality you may not have expected to find, but will doubtless be able to use. Topics covered will include routing, forms, the config cache and record listeners. If you're comfortable in symfony and wondering what's next, this session is for you.

Transcript of Advanced symfony Techniques

Advanced symfony TechniquesKris Wallsmith

@kriswallsmith

• Release Manager for symfony 1.3 & 1.4

• On Symfony and Doctrine teams

• Senior Software Engineer at

• 10 years experience with PHP and web development

• Open source evangelist and international speaker

• Hopeless plugin developer…

• DbFinderPlugin

• sfControlPanelPlugin

• sfDoctrineDynamicFormRelationsPlugin

• sfDoctrineMasterSlavePlugin

• sfFeed2Plugin

• sfFormYamlEnhancementsPlugin

• sfGoogleAnalyticsPlugin

• sfGoogleWebsiteOptimizerPlugin

• sfModerationPlugin

• sfPagerNavigationPlugin

• sfPropelActAsPolymorphicBehaviorPlugin

• sfSimpleBlogPlugin

• sfSimpleCMSPlugin

• sfSimpleForumPlugin

• sfSpyPlugin

• sfSslRequirementPlugin

• sfStatsPlugin

• sfTaskExtraPlugin

• sfWebBrowserPlugin

Please see me if you want tohelp with or take overa plugin's maintenance.

Lots to choose from!

#phpmatsuriOctober 2-3, 2010

Tokyo

• Around 90 attendees

• CakePHP, Symfony, & Lithiumwere represented

• Most folks were CakePHP users

• CakePHP documentation wastranslated early, so…

• Please help translate Symfony2 & Doctrine2 documentation!

#phpmatsuri

PSEUDO CODE AHEAD

CAUTION

Host Aware Routing

domain.com

foobar.domain.com

barfoo.domain.com

homepage: url: / param: { module: main, action: indexOrDash }

homepage: url: / param: { module: main, action: indexOrDash }

if (preg_match('/.../', $r->getHost(), $m))

• ->matchesUrl(...)Does the supplied URL match this route?

class sfRoute

GET / HTTP/1.0Host: foobar.domain.com

• ->matchesUrl(...)Does the supplied URL match this route?

• ->matchesParameters(...)Do the supplied parameters match this route?

class sfRoute

url_for('main/dashboard?username=foobar')

Very slow

• ->matchesUrl(...)Does the supplied URL match this route?

• ->matchesParameters(...)Do the supplied parameters match this route?

• ->generate(...)Generate a URL using this route and these parameters.

class sfRoute

url_for('@dashboard?username=foobar')

->matchesUrl(...)

• $urlThe current URI

• $contextAn array of contextual information, including the current host

• Returns false or an array of parameters extracted from the URI

->matchesParameters(...)

• $paramsAn associative array of parameter names and values

• $contextAn array of contextual information, including the current host

• Returns true or false

->generate(...)

• $paramsAn associative array of parameter names and values

• $contextAn array of contextual information, including the current host

• $absoluteWhether to generate an absolute URL

• Returns the generated URL

Process the host string with a second, internal route

public function __construct(...){ list($host, $pattern) = explode('/', $pattern, 2);

$hostRoute = $this->createHostRoute($host, ...);

parent::__construct(...);}

public function matchesUrl($url, $c){ // check parent::matchesUrl() first

$hp = $hostRoute->matchesUrl('/'.$c['host'], $c);

// include host parameters in return}

public function matchesParameters($p, $c){ $hp = $this->extractHostParams($p);

return parent::matchesParameters($p, $c) && $hostRoute->matchesParameters($hp, $c);}

public function generate($p, $c, $abs){ $hp = $this->extractHostParams($p);

// protocol, prefix...

$host = $hostRoute->generate($hp, $c, false); $uri = parent::generate($p, $c, false);

return $protocol.':/'.$host.$prefix.$uri;}

homepage: url: / param: { module: main, action: indexOrDash }

Hardcoded FTL :(homepage: url: domain.com/ class: sfHostAwareRoute param: { module: main, action: index }

dashboard: url: :username.domain.com/ class: sfHostAwareRoute param: { module: main, action: dashboard }

homepage: url: %APP_HOST%/ class: sfHostAwareRoute param: { module: main, action: index }

dashboard: url: :username.%APP_HOST%/ class: sfHostAwareRoute param: { module: main, action: dashboard }

Custom Config Handler

class sfHostAwareRoutingConfigHandler extends sfRoutingConfigHandler{ protected function parse($configFiles) { return array_map( array($this, 'filterRoute'), parent::parse($configFiles) ); }

// ...}

protected function filterRoute($route){ list($class, $args) = $route;

$args[0] = $this->replaceConstants($args[0]);

return array($class, $args);}

Free FTW!

# config_handlers.ymlconfig/routing.yml: class: sfHostAwareRoutingConfigHandler file: %SF_LIB_DIR%/sfHostAwareRout...

sfHostAwareRoutingPluginAdd subdomains to your routing rules.

Graceful POST Authentication

An example…

CENSORED

CENSORED

Where's my blog post!?!

#FAIL

Extend the security filter

class GracefulSecurityFilter extends sfBasicSecurityFilter{ protected function forwardToLoginAction() { // stash the interrupted request $attr->add(array( 'module' => $context->getActionName(), 'action' => $context->getModuleName(), 'method' => $request->getMethod(), 'params' => $requestParams->getAll(), ), 'stash');

parent::forwardToLoginAction(); }}

# filters.ymlsecurity: class: GracefulSecurityFilter

Replay the stashed requestafter login

// called after authenticationprotected function replayStashedRequest(){ if ($s = $attr->removeNamespace('stash')) { $request->setMethod($s['method']);

$params->clear(); $params->add($s['params']);

$this->forward($s['module'], $s['action']); }}

Extra Security

An example…

# security.ymlacceptInvitation: is_secure: true extra_credentials: account: { lifetime: 300 }

Events to the rescue!

controller.change_action

// connect to the event$ed->connect('controller.change_action', $cb)

// check security.yml$action->getSecurityValue('extra_credentials')

// check current user$u->getAttribute('extra_credentials', array())

// remove any expired credentials$now = time();foreach ($creds as $name => $attr){ if ($now > $attr['expires_at']) { unset($creds[$name]); }}

// stash credentials and referer$u->setAttribute('challenge_credentials', ...)$u->setAttribute('challenge_referer', ...)

// forward to challenge form$controller->forward('security', 'challenge')throw new sfStopException();

// add the granted credentials$now = time();foreach ($new as $name => $attr){ $creds[$name] = array( 'expires_at' => $now + $attr['lifetime'], );}$u->setAttribute('extra_credentials', $creds);

// send them on their way$this->redirect($referer);

sfExtraSecurityPluginRe-prompt your users for authentication.

Javascript Compression

<script src="http://domain.com/widget.js"></script>

class jsActions extends sfActions{ public function executeWidget(sfWebRequest $req) { $this->lightbox = $req->hasParameter('lb'); $this->debug = $req->hasParameter('debug'); }}

<?php if ($debug): ?>console.log("embedding mootools");<?php endif; ?>

var e = document.createElement("script");e.src = "<?php echo public_path('js/moo.js', true) ?>";e.async = true;document.body.appendChild(e);

// etc...

Custom View Class

# module.ymlall: view_class: Javascript

JavascriptView

class JavascriptView extends sfPHPView{ public function render() { return $this->compress(parent::render()); }

protected function compress() { // ... }}

$i = tempnam(sys_get_temp_dir(), __CLASS__);$o = tempnam(sys_get_temp_dir(), __CLASS__);

file_put_contents($i, $content);

shell_exec(vsprintf( 'java -jar %s --type js -o %s %s', array_map('escapeshellarg', array($yui, $o, $i))));

return file_get_contents($o);

Standard Caching

# cache.ymlwidget: enabled: true with_layout: true

developer.yahoo.com/yui/compressor/

A Few Apache Tricks

rm web/.htaccess

AllowOverride None

<Directory /path/to/web> Include /path/to/.htaccess</Directory>

Core Assets

Missing Assets #FAIL :(

AliasMatch /sf/(.*) /path/to/symfony/data/web/sf/$1

AliasMatch /sfDoctrinePlugin/(.*) /path/to/sfDoctrinePlugin/web/$1

NameVirtualHost *:80<VirtualHost _default_:80> # ...

Assets Found FTW!

The Dreaded Trailing Slash…

#FAIL

RewriteEngine OnRewriteRule ^(.*)/$ /$1 [R=301,L]

GET /about/ HTTP/1.1Host: domain.com

HTTP/1.1 301 Moved PermanentlyLocation: http://domain.com/about

Embedded Forms

Person

One book has many authors,one author has many books.

Book

Authors

Book: columns: title: string(255) relations: authors: { class: Person, refClass: BookAuthor }BookAuthor: columns: book_id: integer author_id: integer relations: book: { local: book_id } author: { class: Person, local: author_id }Person: columns: name: string(255)

// embed related forms$this->embedRelation('authors');unset($this['authors_list']);

// embed related forms dynamically!$this->embedDynamicRelation('authors');

form.method_not_found

form.filter_values

// called when a form is configuredpublic function embedDynamicRelation($name){ $rel = $table->getRelation($name); $this->rels[] = $rel;

$this->doEmbed($name, $obj->get($rel->getAlias()));}

// called when a form is boundpublic function filterValues(sfEvent $event, $values){ foreach ($this->rels as $rel) { $name = $rel->getName(); $this->doEmbed($name, $values[$name]); }

$obj->addListener(new DeleteListener($form));}

$parent = new BaseForm();foreach ($values as $i => $value) { if (is_object($value)) { // create form with object } elseif ($value['id']) { // find previously embedded form } else { // create a new form }

$parent->embedForm($i, $child);}

$form->embedForm($rel->getName(), $parent);

// extract existing objects from embedded forms// and compare to the current object collectionpublic function preSave(Doctrine_Event $event){ foreach ($coll as $i => $obj) { $pos = array_search($obj, $existing, true); if (false === $pos) $coll->remove($i);

if ($column['notnull']) $obj->delete(); }}

sfDoctrineDynamicFormRelationsPluginCommon sense embedded forms.

Questions?

• Host aware routing

• Graceful POST authentication

• Extra security

• Javascript compression

• Apache tricks

• Embedded forms

OpenSky is Hiring!http://engineering.shopopensky.com

Please contact me if you're interested.

Thank you!