Advanced symfony Techniques
-
Upload
kris-wallsmith -
Category
Technology
-
view
12.725 -
download
2
description
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!