The Wonderful World of Symfony Components

F5dfeeef276fcfd4751f4063487a5a3f?s=47 weaverryan
September 28, 2012

The Wonderful World of Symfony Components

Wow, Symfony Components!

In this talk, we'll look at the history of PHP, and the struggles as a community to create shared libraries between our large community. Find out the significance of PSR-0 and Composer in *your* life and how you can leverage libraries from all of PHP in your projects.

We'll also look at the most fundamental Symfony2 components - HttpFoundation, HttpKernel, EventDispatcher, & Routing - including those that have been adopted by Drupal 8. We'll also check out a bunch of the other interesting Symfony2 components that can be used as tools in any PHP project.

The goal of this talk is to show you just how easy finding and using high quality libraries has become in PHP. By the end, you'll be excited and ready to high-five all of your PHP friends.

F5dfeeef276fcfd4751f4063487a5a3f?s=128

weaverryan

September 28, 2012
Tweet

Transcript

  1. The Wonderful World of the Symfony Components by your friend:

    Ryan Weaver @weaverryan Friday, September 28, 12
  2. Who is this dude? • That “Docs” guy • KnpLabs

    US - Symfony consulting, training, Kumbaya • Writer for KnpUniversity.com screencasts • Husband of the much more talented @leannapelham knplabs.com github.com/weaverryan @weaverryan Friday, September 28, 12
  3. Intro Life before components (The Desert of the Real) Friday,

    September 28, 12
  4. Symfony consists of 23 individual components Friday, September 28, 12

  5. Components are available via Composer Friday, September 28, 12

  6. Snooze... Friday, September 28, 12

  7. Who Cares? Friday, September 28, 12

  8. We Suck at Sharing and that sucks for you @weaverryan

    1 Friday, September 28, 12
  9. Including External Libraries is Depressing Friday, September 28, 12

  10. The Big Bummer :( @weaverryan • How do I autoload

    their files? • Does their library depend on anything else? • How do I even store their files in my project? Friday, September 28, 12
  11. Include a Zend Framework 1 component in symfony1 Friday, September

    28, 12
  12. Friday, September 28, 12

  13. manually download the library and commit it into your project

    Friday, September 28, 12
  14. if you don’t want the WHOLE library, carefully delete everything

    except the dependent components Friday, September 28, 12
  15. autoloading is completely custom ... blah gross! Friday, September 28,

    12
  16. Components are the key to mastering your framework @weaverryan 2

    Friday, September 28, 12
  17. If PHP is big, we’ll thrive If PHP is small,

    we’ll die @weaverryan 3 Friday, September 28, 12
  18. Communities @weaverryan PHP is Huge! Right? http://www.flickr.com/photos/kitty-kat/ Friday, September 28,

    12
  19. @weaverryan PHP > Ruby Friday, September 28, 12

  20. Fragmentation @weaverryan But fragmentation makes us tiny, isolated, and misguided

    trend-setters http://www.flickr.com/photos/slpunk99/7329609744 Friday, September 28, 12
  21. PHP frameworks < Rails @weaverryan Friday, September 28, 12

  22. I don’t want a damned CakePHP Plugin! @weaverryan CakePHP CodeIgniter

    Friday, September 28, 12
  23. Fragmentation @weaverryan • More information we have to know •

    Difficult to hire • Disjointed forums, StackOverflow • Interoperability? What’s that? Friday, September 28, 12
  24. Components are shareable across all of PHP Friday, September 28,

    12
  25. Act 1 Making Sharing Sexy Friday, September 28, 12

  26. PHP Framework Interoperability Group http://www.php-fig.org/ Friday, September 28, 12

  27. The United Nations of PHP Friday, September 28, 12

  28. (A) The Problem of Autoloading Friday, September 28, 12

  29. My Autoloader doesn’t like your PHPs @weaverryan • The symfony1

    autoloader doesn’t know where ZF1 classes live. The Zf1 autoloader doesn’t know where symfony1 classes live • Each library has its own autoloader that you must discover, configure and use Friday, September 28, 12
  30. like a town where every store has its own currency

    Friday, September 28, 12
  31. From The Mountain: PSR-0 Class Naming Conventions @weaverryan “Thou shalt

    name your classes by following a predictable pattern” Friday, September 28, 12
  32. class: sfRequest path: lib/vendor/symfony/???idk Friday, September 28, 12

  33. class: Symfony\Component\HttpFoundation\Request path: vendor/symfony/src/Symfony/Component/ HttpFoundation/Request.php Friday, September 28, 12

  34. use Symfony\Component\ClassLoader\UniversalClassLoader; $loader = new UniversalClassLoader(); $loader->registerNamespaces(array( 'Zend' => __DIR__.'/path/to/zf'

    )); $loader->register(); Friday, September 28, 12
  35. (B) Managing Vendors in your project Friday, September 28, 12

  36. Composer Friday, September 28, 12

  37. { "require": { "zendframework/ldap": "2.0.x-dev" } } 1) Composer.json Friday,

    September 28, 12
  38. $ curl -s https://getcomposer.org/installer | php $ php composer.phar install

    2) Composer.phar Friday, September 28, 12
  39. require 'vendor/autoload.php'; use Zend\Ldap\Ldap; $options = array(...); $ldap = new

    Ldap($options); 3) Use It Friday, September 28, 12
  40. Friday, September 28, 12

  41. Act 2 Your friendly, neighborhood Symfony Components Friday, September 28,

    12
  42. The Symfony Components • HttpFoundation • HttpKernel • Event Dispatcher

    • Routing • CSSSelector • DomCrawler • BrowserKit • Config • Yaml • DependencyInjection • Security @weaverryan • OptionsResolver • Console • Filesystem • Finder • Locale • Process • Serializer • Templating • Form • Translation • Validator Friday, September 28, 12
  43. The Symfony Components • HttpFoundation • HttpKernel • Event Dispatcher

    • Routing • CSSSelector • DomCrawler • BrowserKit • Config • Yaml • DependencyInjection • Security @weaverryan • OptionsResolver • Console • Filesystem • Finder • Locale • Process • Serializer • Templating • Form • Translation • Validator The “Framework” Components Friday, September 28, 12
  44. Level 1 HttpFoundation symfony/http-foundation Friday, September 28, 12

  45. @weaverryan { "require": { "symfony/http-foundation": "2.1.x-dev" } } php composer.phar

    install <?php // your app require 'vendor/autoload.php'; 1 2 3 Fun to Install! Friday, September 28, 12
  46. @weaverryan Request /foo Response <h1>Hi!</h1> ? ? ? ? ?

    ? Mystery PHP Code Friday, September 28, 12
  47. The Request Friday, September 28, 12

  48. GET /foo?p=v1 HTTP/1.1 Host: knplabs.com Accept: text/html User-Agent: Mozilla/5.0 //

    /foo?p=v1 $uri = $_SERVER['REQUEST_URI']; $p = isset($_GET['p']) ? $_GET['p'] : ''; $host = $_SERVER['HTTP_HOST']; $accept = $_SERVER['HTTP_ACCEPT']; $ua = $_SERVER['HTTP_USER_AGENT']; Friday, September 28, 12
  49. use Symfony\Component\HttpFoundation\Request; $request = Request::createFromGlobals(); $uri = $request->getPathInfo(); /foo $p

    = $request->query->get('p'); $host = $request->getHost(); $accept = $request->headers->get('Accept'); GET /foo?p=v1 HTTP/1.1 Host: knplabs.com Accept: text/html User-Agent: Mozilla/5.0 Friday, September 28, 12
  50. The Response Friday, September 28, 12

  51. HTTP/1.1 200 OK Date: Tue, 04 Jun 2011 21:05:05 GMT

    Content-Type: text/html Set-Cookie:foo=fooval; path=/; httponly <h1>Foo!</h1> header('Content-Type', 'text/html'); setcookie('foo', 'fooval'); echo '<h1>Foo!</h1>'; Friday, September 28, 12
  52. HTTP/1.1 200 OK Date: Tue, 04 Jun 2011 21:05:05 GMT

    Content-Type: text/html Set-Cookie:foo=fooval; path=/; httponly <h1>Foo!</h1> use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Cookie; $response = new Response('<h1>Foo!</h1>'); $cookie = new Cookie('foo', 'fooval'); $response->headers->setCookie($cookie); $response->send(); Friday, September 28, 12
  53. A Mini Framework Friday, September 28, 12

  54. // not dependable! $uri = $_SERVER['REQUEST_URI']; ob_start(); if ($uri ==

    '/') { echo 'Homepage! \o/'; } else { echo 'sad missing page :/'; } header('X-TOKEN: Foo'); ob_end_flush(); Friday, September 28, 12
  55. $req = Request::createFromGlobals(); if ($req->getPathInfo() == '/') { $res =

    new Response('Homepage! \o/'); } else { $res = new Response('sad missing page :/'); } $res->headers->set('X-SECRET', 'Foo'); $res->send(); Symfony3 Friday, September 28, 12
  56. Get some learning about HttpFoundation @weaverryan • Docs: http://bit.ly/sf2-http-foundation •

    Code: http://bit.ly/sf2-http-foundation-code • API: http://bit.ly/sf2-http-foundation-api Friday, September 28, 12
  57. Level 2 HttpKernel symfony/http-kernel Friday, September 28, 12

  58. @weaverryan Framework Request Determine the controller ? ? ? Execute

    it Flush the Response Response Friday, September 28, 12
  59. Framework: A pattern for converting a request into a response

    Friday, September 28, 12
  60. @weaverryan Framework Request Determine the controller ? ? ? Execute

    it Flush the Response Response Friday, September 28, 12
  61. @weaverryan HttpKernel Request Determine the controller ? ? ? Execute

    it Flush the Response Response Friday, September 28, 12
  62. $kernel = new HttpKernel(...); $request = Request::createFromGlobals(); $response = $kernel->handle($request);

    $response->send(); There be magic inside! Friday, September 28, 12
  63. These lines execute your Symfony application (hint, see - web/app.php)

    Friday, September 28, 12
  64. ... and Drupal 8 too ... Friday, September 28, 12

  65. Yea, this is abstract Stay Tuned... Friday, September 28, 12

  66. Level 3 EventDispatcher symfony/event-dispatcher Friday, September 28, 12

  67. @weaverryan Event Dispatcher A classic “observer pattern” Listener Listener Tell

    me when Liz gets to work Tell me when Liz gets to work Friday, September 28, 12
  68. @weaverryan Event Dispatcher • Event Dispatcher: a classic “observer pattern”

    Listener Listener Yo! I’m at work! Friday, September 28, 12
  69. @weaverryan Event Dispatcher • Event Dispatcher: a classic “observer pattern”

    Listener Listener Liz is at work! Liz is at work! Friday, September 28, 12
  70. Event Dispatcher • Event Dispatcher: a classic “observer pattern” Listener

    Listener She’s the best around! I’m totally going to go talk with her!! Friday, September 28, 12
  71. Level 4 Routing symfony/routing Friday, September 28, 12

  72. @weaverryan Router Url Match against a route Return info about

    the route Route Route Friday, September 28, 12
  73. $loader = new ClosureLoader(); $router = new Router($loader, function() {

    $collection = new RouteCollection(); $route = new Route('/blog/{slug}'); $collection->add('blog', $route); return $collection; }); $results = $router->match('/blog/sflive'); array( 'slug' => 'sflive', '_route' => 'blog', ) Friday, September 28, 12
  74. $loader = new ClosureLoader(); $router = new Router($loader, function() {

    $collection = new RouteCollection(); $route = new Route('/blog/{slug}', array( '_controller' => 'SomeBundle:Blog:show' )); $collection->add('blog', $route); return $collection; }); $results = $router->match('/blog/sflive'); array( '_controller' => 'SomeBundle:Blog:show', 'slug' => 'sflive', '_route' => 'blog' ) Friday, September 28, 12
  75. Input: Request Information Output: An array of info Friday, September

    28, 12
  76. Level 5 A “Framework” Friday, September 28, 12

  77. $kernel = new HttpKernel(...); $request = Request::createFromGlobals(); $response = $kernel->handle($request);

    $response->send(); Friday, September 28, 12
  78. @weaverryan HttpKernel::handle() HttpFoundation + EventDispatcher + Routing (optional) = Framework

    Friday, September 28, 12
  79. @weaverryan HttpKernel::handle() Request + Determine Controller + Controller Returns a

    Response = Response Friday, September 28, 12
  80. Homework: Hack your Framework ... or do Karaoke later... your

    call Friday, September 28, 12
  81. @weaverryan HttpKernel::handleRaw() private function handleRaw(Request $request, $type = self::MASTER_REQUEST) {

    // request $event = new GetResponseEvent($this, $request, $type); $this->dispatcher->dispatch(KernelEvents::REQUEST, $event); if ($event->hasResponse()) { return $this->filterResponse($event->getResponse(), $request, $type); } // load controller if (false === $controller = $this->resolver->getController($request)) { throw new NotFoundHttpException('Unable to find the controller); } $event = new FilterControllerEvent($this, $controller, $request, $type); $this->dispatcher->dispatch(KernelEvents::CONTROLLER, $event); $controller = $event->getController(); // ... returns a response } The kewlest code I know about... seriously Friday, September 28, 12
  82. @weaverryan • Symfony\Component\HttpKernel\HttpKernel::handle() http://bit.ly/sf2-http-kernel-code • Symfony Framework Important Classes Symfony\Component\HttpKernel\EventListener\RouterListener

    - Executes the routing Symfomy\Bundle\FrameworkBundle\Controller\ControllerResolver - Parses _controller and instantiates the controller object step/var_dump()die; the core Friday, September 28, 12
  83. Timeline of events in the Symfony Framework kernel.request kernel.controller kernel.response

    Friday, September 28, 12
  84. Profile events in the Symfony Framework Friday, September 28, 12

  85. The Symfony Components • HttpFoundation • HttpKernel • Event Dispatcher

    • Routing • CSSSelector • DomCrawler • BrowserKit • Config • Yaml • DependencyInjection • Security @weaverryan • OptionsResolver • Console • Filesystem • Finder • Locale • Process • Serializer • Templating • Form • Translation • Validator Awesome Tools Friday, September 28, 12
  86. Process symfony/process Now with more Windows Love! Friday, September 28,

    12
  87. @weaverryan { "require": { "symfony/process": "2.1.x-dev" } } Find Symfony’s

    Process on Packagist.org php composer.phar install 1 2 3 <?php // your app require 'vendor/autoload.php'; Friday, September 28, 12
  88. <?php // dinner.php for ($i = 1; $i <= 3;

    $i++) { sleep(1); echo sprintf("Cooking... %s\n", $i); } echo "DING! \n"; External Script to Cook Dinner Friday, September 28, 12
  89. Run the script synchronously Friday, September 28, 12

  90. // app.php require 'vendor/autoload.php'; use Symfony\Component\Process\Process; $process = new Process('php

    dinner.php'); // if dinner takes longer than 5 seconds // to cook, freak out! $process->setTimeout(5); $process->run(function($type, $buffer) { echo $buffer; }); // executed after the command finishes if (!$process->isSuccessful()) { throw new \Exception('Process misbehaved') } Friday, September 28, 12
  91. Friday, September 28, 12

  92. Run the script asynchronously Friday, September 28, 12

  93. $process = new Process('php dinner.php'); $process->start(); // do more work

    here echo "WORKING!!! \n"; while ($process->isRunning()) { // wait for the process to finish }; echo $process->getOutput(); Friday, September 28, 12
  94. Interacting via Stdin Friday, September 28, 12

  95. $process = new Process('php dinner.php'); $process->setStdin('turkey'); “turkey” is read from

    Stdin Friday, September 28, 12
  96. $process = new Process('php dinner.php'); $process->run(function($type, $buffer) { if ($type

    == 'err') { // perhaps do some logging? echo $buffer; } }); Handling things that blow up! if (!$process->isSuccessful()) { echo sprintf( "Process failed! Exit code %s, '%s'\n", $process->getExitCode(), $process->getExitCodeText() ); } Friday, September 28, 12
  97. Find out more about Process @weaverryan • Docs: http://bit.ly/sf2-process •

    Code: http://bit.ly/sf2-process-code • API: http://bit.ly/sf2-process-api Friday, September 28, 12
  98. SPORK! @kriswallsmith $manager = new Spork\ProcessManager(); $manager->fork(function() { // do

    something in another process! return 'Hello from '.getmypid(); })->then(function(Spork\Fork $fork) { // do something in the parent process afterwards echo "{$fork->getPid()} says '{$fork->getResult()}'"; }); Fork yourself some more PHP https://github.com/kriswallsmith/spork Friday, September 28, 12
  99. Finder symfony/finder Friday, September 28, 12

  100. From the Fablog http://fabien.potencier.org/article/44/php-iterators-and- streams-are-awesome use Symfony\Components\Finder\Finder; $s3 = new

    \Zend_Service_Amazon_S3($key, $secret); $s3->registerStreamWrapper("s3"); $finder = new Finder(); $finder->name('photos*') ->size('< 100K') ->date('since 1 hour ago'); foreach ($finder->in('s3://bucket-name') as $file) { // do something print $file->getFilename()."\n"; } Friday, September 28, 12
  101. Find(er) out more about Finder @weaverryan • Docs: http://bit.ly/sf2-finder •

    Code: http://bit.ly/sf2-finder-code • API: http://bit.ly/sf2-finder-api Friday, September 28, 12
  102. Filesystem symfony/filesystem New in 2.1! Friday, September 28, 12

  103. @weaverryan { "require": { "symfony/filesystem": "2.1.x-dev" } } Find Symfony’s

    Filesystem on Packagist.org php composer.phar update symfony/filesystem 1 2 3 <?php // your app require 'vendor/autoload.php'; Friday, September 28, 12
  104. // ... use Symfony\Component\Filesystem\Filesystem; $filesystem = new Filesystem(); $filesystem->copy( 'cowboy.jpg',

    's3://my-bucket/cowboy.jpg' ); $filesystem->copy( 's3://my-bucket/rodeo.mov', 'rodeo.mov' ); $filesystem->mirror( 'thumbnails', 's3://my-bucket/thumbnails' ); Friday, September 28, 12
  105. // ... use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\Finder\Finder; $finder = new Finder();

    $finder->name('*.jpg') ->date('since 1 hour ago'); ->in('thumbs'); $filesystem = new Filesystem(); $filesystem->chmod($finder, 0777); Friday, September 28, 12
  106. // ... use Symfony\Component\Filesystem\Filesystem; $filesystem = new Filesystem(); $filesystem->touch('foo.txt'); $filesystem->chmod('foo.txt',

    0777); $filesystem->symlink('/some/dir', '/symlink/target') Friday, September 28, 12
  107. Find out more about Filesystem @weaverryan • Docs: http://bit.ly/sf2-filesystem •

    Code: http://bit.ly/sf2-filesystem-code • API: http://bit.ly/sf2-filesystem-api Friday, September 28, 12
  108. Console symfony/console Friday, September 28, 12

  109. Friday, September 28, 12

  110. <?php // command.php $loader = require 'vendor/autoload.php'; $loader->add('App', __DIR__); use

    Symfony\Component\Console\Application; use App\Command\EchoCommand; $app = new Application(); $app->add(new EchoCommand()); $app->run(); Friday, September 28, 12
  111. namespace App\Command; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; class EchoCommand

    extends Command { protected function configure() { $this->setName('echo')->addArgument('msg'); } public function execute( InputInterface $input, OutputInterface $output) { $output->writeln('Yo '.$input->getArgument('msg')); } } Friday, September 28, 12
  112. Output with pretty colors Friday, September 28, 12

  113. Find out more about Command @weaverryan • Docs http://bit.ly/sf2-console •

    Code http://bit.ly/sf2-console-code • API http://api.symfony.com/master/namespaces.html -> and search for “Console” Friday, September 28, 12
  114. If you download Composer in the next 5 minutes, we’ll

    throw in a special gift! Friday, September 28, 12
  115. CssSelector + DomCrawler symfony/css-selector symfony/dom-crawler Friday, September 28, 12

  116. require 'vendor/autoload.php'; use Symfony\Component\DomCrawler\Crawler; $html = <<<'HTML' <!DOCTYPE html> <html>

    <body> <p class="message">Hello World!</p> <p>Hello Crawler!</p> </body> </html> HTML; $crawler = new Crawler($html); $crawler->filter('p')->eq(0)->attr('class'); $crawler->filter('p')->eq(1)->text(); Friday, September 28, 12
  117. Goutte! Friday, September 28, 12

  118. Config symfony/config Friday, September 28, 12

  119. Config @weaverryan • Load configuration from many formats (YAML, PHP

    files, XML) • Config Validation • Caching for parsed config Friday, September 28, 12
  120. Validator symfony/validator Friday, September 28, 12

  121. Shh... a little setup Friday, September 28, 12

  122. require 'vendor/autoload.php'; use Symfony\Component\Validator\Validator; use Symfony\Component\Validator\Mapping\ClassMetadataFactory; use Symfony\Component\Validator\ConstraintValidatorFactory; // a

    little setup $metadata = new ClassMetadataFactory(); $constraintFactory = new ConstraintValidatorFactory(); $validator = new Validator($metadata, $constraintFactory); Friday, September 28, 12
  123. use Symfony\Component\Validator\Constraints\Email; $value = 'ryan[AT]knplabs.com'; $email = new Email(); $email->message

    = 'Invalid! You entered {{ value }}'; $errors = $validator->validateValue($value, $email); if (count($errors) > 0) { echo strtr( $errors[0]->getMessageTemplate(), $errors[0]->getMessageParameters() ); } Friday, September 28, 12
  124. 34 Core Validators and counting • String length • Regular

    expressions • Ip addresses • Dates • Collections • File size, mime-type, etc ... and the brand new Luhn validator for credit card numbers thanks to @merk -----------------------------------> @weaverryan See the “Reference” section of the docs Friday, September 28, 12
  125. But wait there’s more! Friday, September 28, 12

  126. More Components • DependencyInjection • Security • OptionsResolver • Locale

    • Serializer • Templating • Form • Translation @weaverryan Friday, September 28, 12
  127. Many components are easy Friday, September 28, 12

  128. Some are still wtf-hard Friday, September 28, 12

  129. (cough) Security (cough) Friday, September 28, 12

  130. Documentation Documentation Documentation Friday, September 28, 12

  131. The Security Component https://github.com/symfony/symfony-docs/pull/1604 WIP by @matthiasnoback Friday, September 28,

    12
  132. Act 3 Kicking ass in the new PHP eco-system Friday,

    September 28, 12
  133. The PHP Ecosphere • Symfony • Zend Framework • EZ

    Components • Drupal • ... • Individuals @weaverryan Symfony is only one part of the picture What other stuff exists? Friday, September 28, 12
  134. How do we find good libraries? Friday, September 28, 12

  135. Friday, September 28, 12

  136. Friday, September 28, 12

  137. Some Favorites Friday, September 28, 12

  138. Mink behat/mink http://mink.behat.org/ By @everzet, written on Fabien’s childhood computer

    Friday, September 28, 12
  139. Command a browser, find elements, click them, and fill out

    forms $driver = new \Behat\Mink\Driver\SahiDriver('firefox'); $session->visit('http://my_project.dev/some_page.php'); $page = $session->getPage(); $anchor = $page->find('css', '.something'); $anchor->click(); // get the content of the new page echo $page->getContent(); Friday, September 28, 12
  140. Monolog monolog/monolog https://github.com/Seldaek/monolog Your Friend Jordi Friday, September 28, 12

  141. A Logger, where bells and whistles come standard use Monolog\Logger;

    use Monolog\Handler\StreamHandler; use Monolog\Handler\FirePHPHandler; // Create the logger $logger = new Logger('my_logger'); $logger->pushHandler(new StreamHandler( __DIR__.'/my_app.log' )); $logger->pushHandler(new FirePHPHandler()); $logger->addInfo('My logger is now ready'); Friday, September 28, 12
  142. Zend Framework 2 They have some stuff Friday, September 28,

    12
  143. Yay Find More! Friday, September 28, 12

  144. Epilogue 4 Reasons to get silly-excited about the Future Friday,

    September 28, 12
  145. 1) Look for Participation & Consolidation Friday, September 28, 12

  146. Shared Building-blocks • Drupal • phpBB • Midgard • Zikula

    • ez Publish 5 • ... @weaverryan Friday, September 28, 12
  147. Less Library Duplication? • Zend/Form • Zend/Serializer • Zend/Http •

    Zend/EventManager • Zend/Log • Zend/Navigation • ... @weaverryan • Symfony/Form • Symfony/Serializer • Symfony/HttpFoundation • Symfony/EventDispatcher • Monlog • KnpMenu • ... Friday, September 28, 12
  148. 2) More high quality, community-grown libraries Friday, September 28, 12

  149. Solving 1 specific problem is a low barrier to entry

    Friday, September 28, 12
  150. Find them, fork them. Friday, September 28, 12

  151. Write their docs :) Friday, September 28, 12

  152. Programmers that write docs get hugs Friday, September 28, 12

  153. 3) Easier upgrades Friday, September 28, 12

  154. 4) Grow your Community Friday, September 28, 12

  155. Solutions exist outside of your framework Friday, September 28, 12

  156. Fabien will not come to your house and tell you

    about them Friday, September 28, 12
  157. Cast your vote by using the best libraries and improving

    them Friday, September 28, 12
  158. and realize the power of the entire PHP community Friday,

    September 28, 12
  159. Thanks... Ryan Weaver @weaverryan Friday, September 28, 12

  160. ... and we love you! Ryan Weaver @weaverryan Ryan Weaver

    @weaverryan https://joind.in/7218 Friday, September 28, 12