Advanced symfony Techniques
          Kris Wallsmith
@kriswallsmith
•   Release Manager for symfony 1.3 & 1.4

•   On Symfony and Doctrine teams

•   Senior Software Engineer ...
•   DbFinderPlugin                         •   sfPropelActAsPolymorphicBehaviorPlugin

•   sfControlPanelPlugin           ...
Please see me if you want to
   help with or take over
   a plugin's maintenance.

Lots to choose from!
#phpmatsuri
  October 2-3, 2010
       Tokyo
#phpmatsuri
•   Around 90 attendees


•   CakePHP, Symfony, & Lithium
    were represented


•   Most folks were CakePHP u...
CAUTION

PSEUDO CODE
   AHEAD
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))
class sfRoute

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


    GET / HTTP/1.0
    Host: foobar.dom...
class sfRoute
                                                   Ver y slow

•   ->matchesUrl(...)
    url_for('main/dashb...
class sfRoute

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

 • ->matchesParameters(...)
url_for('@...
->matchesUrl(...)

•   $url
    The current URI

•   $context
    An array of contextual information, including the curren...
->matchesParameters(...)

•   $params
    An associative array of parameter names and values

•   $context
    An array of...
->generate(...)
•   $params
    An associative array of parameter names and values

•   $context
    An array of contextua...
Process the host string with a
   second, internal route
public function __construct(...)
{
  list($host, $pattern) = explode('/', $pattern, 2);

    $hostRoute = $this->createHos...
public function matchesUrl($url, $c)
{
  // check parent::matchesUrl() first

    $hp = $hostRoute->matchesUrl('/'.$c['hos...
public function matchesParameters($p, $c)
{
  $hp = $this->extractHostParams($p);

    return
      parent::matchesParamet...
public function generate($p, $c, $abs)
{
  $hp = $this->extractHostParams($p);

    // protocol, prefix...

    $host = $h...
homepage:
  url:   /
  param: { module: main, action: indexOrDash }
Hard
                         co d
homepage:                     e
                              d F
                     ...
homepage:
  url:   %APP_HOST%/
  class: sfHostAwareRoute
  param: { module: main, action: index }

dashboard:
  url:   :us...
Custom Config Handler
class sfHostAwareRoutingConfigHandler
    extends sfRoutingConfigHandler
{
  protected function parse($configFiles)
  {
  ...
FTW!
                                         Free

protected function filterRoute($route)
{
  list($class, $args) = $rout...
# config_handlers.yml
config/routing.yml:
  class: sfHostAwareRoutingConfigHandler
  file: %SF_LIB_DIR%/sfHostAwareRout...
sfHostAwareRoutingPlugin
    Add subdomains to your routing rules.
Graceful POST Authentication
An example…
CENSORED




CENSORED
W here's my
            blog pos t!?
                        !




 AIL
F
Extend the security filter
class GracefulSecurityFilter
    extends sfBasicSecurityFilter
{
  protected function forwardToLoginAction()
  {
    // st...
# filters.yml
security:
  class: GracefulSecurityFilter
Replay the stashed request
        after login
// called after authentication
protected function replayStashedRequest()
{
  if ($s = $attr->removeNamespace('stash'))
  {...
Extra Security
An example…
# security.yml
acceptInvitation:
  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'])
  ...
// 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'...
// send them on their way
$this->redirect($referer);
sfExtraSecurityPlugin
 Re-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->has...
<?php if ($debug): ?>
console.log("embedding mootools");
<?php endif; ?>

var e = document.createElement("script");
e.src ...
Custom View Class
# module.yml
all:
  view_class: Javascript

        JavascriptView
class JavascriptView extends sfPHPView
{
  public function render()
  {
    return $this->compress(parent::render());
  }
...
$i = tempnam(sys_get_temp_dir(), __CLASS__);
$o = tempnam(sys_get_temp_dir(), __CLASS__);

file_put_contents($i, $content)...
Standard Caching
# cache.yml
widget:
  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 Asset
              s #FAIL :(
AliasMatch /sf/(.*)
  /path/to/symfony/data/web/sf/$1

AliasMatch /sfDoctrinePlugin/(.*)
  /path/to/sfDoctrinePlugin/web/$...
Assets Fo u nd FTW!
The Dreaded Trailing Slash…
AIL
#F
RewriteEngine On
RewriteRule ^(.*)/$ /$1 [R=301,L]
GET /about/ HTTP/1.1
Host: domain.com

HTTP/1.1 301 Moved Permanently
Location: http://domain.com/about
Embedded Forms
Book


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


Person
Book:
  columns:
    title:       string(255)
  relations:
    authors:     { class: Person, refClass: BookAuthor }
BookAu...
// 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 configured
public function embedDynamicRelation($name)
{
  $rel = $table->getRelation($name);
  $...
// called when a form is bound
public function filterValues(sfEvent $event, $values)
{
  foreach ($this->rels as $rel)
  {...
$parent = new BaseForm();
foreach ($values as $i => $value) {
  if (is_object($value)) {
    // create form with object
  ...
// extract existing objects from embedded forms
// and compare to the current object collection
public function preSave(Do...
sfDoctrineDynamicFormRelationsPlugin
          Common sense embedded forms.
Questions?
•   Host aware routing

•   Graceful POST authentication

•   Extra security

•   Javascript compression

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

 Please contact me if you're interested.
Thank you!
Advanced symfony Techniques
Advanced symfony Techniques
Advanced symfony Techniques
Advanced symfony Techniques
Advanced symfony Techniques
Advanced symfony Techniques
Advanced symfony Techniques
Advanced symfony Techniques
Advanced symfony Techniques
Advanced symfony Techniques
Advanced symfony Techniques
Advanced symfony Techniques
Advanced symfony Techniques
Advanced symfony Techniques
Upcoming SlideShare
Loading in...5
×

Advanced symfony Techniques

13,264

Published on

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.

Published in: Technology

Advanced symfony Techniques

  1. 1. Advanced symfony Techniques Kris Wallsmith
  2. 2. @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…
  3. 3. • DbFinderPlugin • sfPropelActAsPolymorphicBehaviorPlugin • sfControlPanelPlugin • sfSimpleBlogPlugin • sfDoctrineDynamicFormRelationsPlugin • sfSimpleCMSPlugin • sfDoctrineMasterSlavePlugin • sfSimpleForumPlugin • sfFeed2Plugin • sfSpyPlugin • sfFormYamlEnhancementsPlugin • sfSslRequirementPlugin • sfGoogleAnalyticsPlugin • sfStatsPlugin • sfGoogleWebsiteOptimizerPlugin • sfTaskExtraPlugin • sfModerationPlugin • sfWebBrowserPlugin • sfPagerNavigationPlugin
  4. 4. Please see me if you want to help with or take over a plugin's maintenance. Lots to choose from!
  5. 5. #phpmatsuri October 2-3, 2010 Tokyo
  6. 6. #phpmatsuri • Around 90 attendees • CakePHP, Symfony, & Lithium were represented • Most folks were CakePHP users • CakePHP documentation was translated early, so… • Please help translate Symfony2 & Doctrine2 documentation!
  7. 7. CAUTION PSEUDO CODE AHEAD
  8. 8. Host Aware Routing
  9. 9. domain.com
  10. 10. foobar.domain.com
  11. 11. barfoo.domain.com
  12. 12. homepage: url: / param: { module: main, action: indexOrDash }
  13. 13. homepage: url: / param: { module: main, action: indexOrDash } if (preg_match('/.../', $r->getHost(), $m))
  14. 14. class sfRoute • ->matchesUrl(...) Does the supplied URL match this route? GET / HTTP/1.0 Host: foobar.domain.com
  15. 15. class sfRoute Ver y slow • ->matchesUrl(...) url_for('main/dashboard?username=foobar') Does the supplied URL match this route? • ->matchesParameters(...) Do the supplied parameters match this route?
  16. 16. class sfRoute • ->matchesUrl(...) Does the supplied URL match this route? • ->matchesParameters(...) url_for('@dashboard?username=foobar') Do the supplied parameters match this route? • ->generate(...) Generate a URL using this route and these parameters.
  17. 17. ->matchesUrl(...) • $url The current URI • $context An array of contextual information, including the current host • Returns false or an array of parameters extracted from the URI
  18. 18. ->matchesParameters(...) • $params An associative array of parameter names and values • $context An array of contextual information, including the current host • Returns true or false
  19. 19. ->generate(...) • $params An associative array of parameter names and values • $context An array of contextual information, including the current host • $absolute Whether to generate an absolute URL • Returns the generated URL
  20. 20. Process the host string with a second, internal route
  21. 21. public function __construct(...) { list($host, $pattern) = explode('/', $pattern, 2); $hostRoute = $this->createHostRoute($host, ...); parent::__construct(...); }
  22. 22. public function matchesUrl($url, $c) { // check parent::matchesUrl() first $hp = $hostRoute->matchesUrl('/'.$c['host'], $c); // include host parameters in return }
  23. 23. public function matchesParameters($p, $c) { $hp = $this->extractHostParams($p); return parent::matchesParameters($p, $c) && $hostRoute->matchesParameters($hp, $c); }
  24. 24. 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; }
  25. 25. homepage: url: / param: { module: main, action: indexOrDash }
  26. 26. Hard co d homepage: e d F TL url: domain.com/ :( class: sfHostAwareRoute param: { module: main, action: index } dashboard: url: :username.domain.com/ class: sfHostAwareRoute param: { module: main, action: dashboard }
  27. 27. homepage: url: %APP_HOST%/ class: sfHostAwareRoute param: { module: main, action: index } dashboard: url: :username.%APP_HOST%/ class: sfHostAwareRoute param: { module: main, action: dashboard }
  28. 28. Custom Config Handler
  29. 29. class sfHostAwareRoutingConfigHandler extends sfRoutingConfigHandler { protected function parse($configFiles) { return array_map( array($this, 'filterRoute'), parent::parse($configFiles) ); } // ... }
  30. 30. FTW! Free protected function filterRoute($route) { list($class, $args) = $route; $args[0] = $this->replaceConstants($args[0]); return array($class, $args); }
  31. 31. # config_handlers.yml config/routing.yml: class: sfHostAwareRoutingConfigHandler file: %SF_LIB_DIR%/sfHostAwareRout...
  32. 32. sfHostAwareRoutingPlugin Add subdomains to your routing rules.
  33. 33. Graceful POST Authentication
  34. 34. An example…
  35. 35. CENSORED CENSORED
  36. 36. W here's my blog pos t!? ! AIL F
  37. 37. Extend the security filter
  38. 38. 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(); } }
  39. 39. # filters.yml security: class: GracefulSecurityFilter
  40. 40. Replay the stashed request after login
  41. 41. // called after authentication protected function replayStashedRequest() { if ($s = $attr->removeNamespace('stash')) { $request->setMethod($s['method']); $params->clear(); $params->add($s['params']); $this->forward($s['module'], $s['action']); } }
  42. 42. Extra Security
  43. 43. An example…
  44. 44. # security.yml acceptInvitation: is_secure: true extra_credentials: account: { lifetime: 300 }
  45. 45. Events to the rescue!
  46. 46. controller.change_action
  47. 47. // connect to the event $ed->connect('controller.change_action', $cb)
  48. 48. // check security.yml $action->getSecurityValue('extra_credentials')
  49. 49. // check current user $u->getAttribute('extra_credentials', array())
  50. 50. // remove any expired credentials $now = time(); foreach ($creds as $name => $attr) { if ($now > $attr['expires_at']) { unset($creds[$name]); } }
  51. 51. // stash credentials and referer $u->setAttribute('challenge_credentials', ...) $u->setAttribute('challenge_referer', ...)
  52. 52. // forward to challenge form $controller->forward('security', 'challenge') throw new sfStopException();
  53. 53. // add the granted credentials $now = time(); foreach ($new as $name => $attr) { $creds[$name] = array( 'expires_at' => $now + $attr['lifetime'], ); } $u->setAttribute('extra_credentials', $creds);
  54. 54. // send them on their way $this->redirect($referer);
  55. 55. sfExtraSecurityPlugin Re-prompt your users for authentication.
  56. 56. Javascript Compression
  57. 57. <script src="http://domain.com/widget.js"></script>
  58. 58. class jsActions extends sfActions { public function executeWidget(sfWebRequest $req) { $this->lightbox = $req->hasParameter('lb'); $this->debug = $req->hasParameter('debug'); } }
  59. 59. <?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...
  60. 60. Custom View Class
  61. 61. # module.yml all: view_class: Javascript JavascriptView
  62. 62. class JavascriptView extends sfPHPView { public function render() { return $this->compress(parent::render()); } protected function compress() { // ... } }
  63. 63. $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);
  64. 64. Standard Caching
  65. 65. # cache.yml widget: enabled: true with_layout: true
  66. 66. developer.yahoo.com/yui/compressor/
  67. 67. A Few Apache Tricks
  68. 68. rm web/.htaccess
  69. 69. AllowOverride None
  70. 70. <Directory /path/to/web> Include /path/to/.htaccess </Directory>
  71. 71. Core Assets
  72. 72. Missing Asset s #FAIL :(
  73. 73. AliasMatch /sf/(.*) /path/to/symfony/data/web/sf/$1 AliasMatch /sfDoctrinePlugin/(.*) /path/to/sfDoctrinePlugin/web/$1 NameVirtualHost *:80 <VirtualHost _default_:80> # ...
  74. 74. Assets Fo u nd FTW!
  75. 75. The Dreaded Trailing Slash…
  76. 76. AIL #F
  77. 77. RewriteEngine On RewriteRule ^(.*)/$ /$1 [R=301,L]
  78. 78. GET /about/ HTTP/1.1 Host: domain.com HTTP/1.1 301 Moved Permanently Location: http://domain.com/about
  79. 79. Embedded Forms
  80. 80. Book One book has many authors, Authors one author has many books. Person
  81. 81. 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)
  82. 82. // embed related forms $this->embedRelation('authors'); unset($this['authors_list']);
  83. 83. // embed related forms dynamically! $this->embedDynamicRelation('authors');
  84. 84. form.method_not_found form.filter_values
  85. 85. // called when a form is configured public function embedDynamicRelation($name) { $rel = $table->getRelation($name); $this->rels[] = $rel; $this->doEmbed($name, $obj->get($rel->getAlias())); }
  86. 86. // called when a form is bound public function filterValues(sfEvent $event, $values) { foreach ($this->rels as $rel) { $name = $rel->getName(); $this->doEmbed($name, $values[$name]); } $obj->addListener(new DeleteListener($form)); }
  87. 87. $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);
  88. 88. // extract existing objects from embedded forms // and compare to the current object collection public 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(); } }
  89. 89. sfDoctrineDynamicFormRelationsPlugin Common sense embedded forms.
  90. 90. Questions? • Host aware routing • Graceful POST authentication • Extra security • Javascript compression • Apache tricks • Embedded forms
  91. 91. OpenSky is Hiring! http://engineering.shopopensky.com Please contact me if you're interested.
  92. 92. Thank you!
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×