Hanoi php day 2008 - 01.pham cong dinh - how.to.build.your.own.framework
Upcoming SlideShare
Loading in...5
×
 

Hanoi php day 2008 - 01.pham cong dinh - how.to.build.your.own.framework

on

  • 2,857 views

 

Statistics

Views

Total Views
2,857
Views on SlideShare
2,856
Embed Views
1

Actions

Likes
1
Downloads
40
Comments
0

1 Embed 1

http://www.techgig.com 1

Accessibility

Categories

Upload Details

Uploaded via as Microsoft PowerPoint

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment
  • You need to think different. You can not see it in a 1999 manner. Java purist mindsets are obsolete

Hanoi php day 2008 - 01.pham cong dinh - how.to.build.your.own.framework Hanoi php day 2008 - 01.pham cong dinh - how.to.build.your.own.framework Presentation Transcript

  • How to learn to build your own PHP framework Bridging the gap between PHP, OOP and Software Architecture Pham Cong Dinh (a.k.a pcdinh) Software Developer Hanoi PHP Day - December 2008 Hanoi - Vietnam
  • Introduction
    • A foundation member of JavaVietnam since 2003 ( http://www.javavietnam.org )
    • A foundation member of PHPVietnam Discussion Group since 2004 ( http://groups.google.com/group/phpvietnam )
    • Lead web developer with World’Vest Base Inc.
    • Java is my first love since 1999
    • PHP is my real lover since 2003. I love the way PHP community works
    • Sometimes I works on Python, Ruby, Erlang
    • I am a strong believer in dynamic programming languages, JavaScript, web standards, convergence of web as a platform and Outsourcing 2.0
    • I spent 2 years to play with my framework named Pone (PHP One)
  • Objectives
    • Where is PHP now? It is changing.
      • Enterprise oriented: which is driven by Yahoo, Facebook, Zend, Sun/MySQL, Oracle, OmniTI … PHP is too big. It can not just be ignored
      • Object Oriented Programming adoption
      • Increased complexity of web applications
      • Web vs. Adobe Air, MS Silverlight, JavaFX, Google Native Client
      • Trends Will Move Applications to the Web: Consumer Innovation Setting the Pace, Rise of the Power Collaborator, New Economics of Scale for IT, Barriers to Adoption Are Falling
      • Scale-out wins
    • Understanding what framework designers think
    • Building up shared mindsets
    • Providing food for thought
  • Agenda – 40 slides
    • Making judgments
    • Top notch frameworks and their shortcomings
    • A broader view on your framework
    • Lessons to learn
  • Making judgments
    • Common wisdom: Reinventing the wheel
    • Good
      • You know it inside and out
      • You control its pace
      • It fits your needs. Sometimes, your need is unique
      • It teaches you how the world around you works
      • License: This is why GPL is sometime a bad thing
    • Bad
      • You may not as good as other ones
      • No community
      • No outside contributors
      • Reinventing the square wheel
  • Making judgments
    • To develop a framework is just like to set up a business
    • Think of your limitation: time, resources, knowledge to build/test/document/maintain your own mental baby
    • Know your team: how to train them
    • Know the market : known frameworks in the market. Sometimes your needs are satisfied to some extent in several little-known frameworks
    • Starts with pencil and paper: its components and how they interact
    • Starts with API: learn how to design an API first
    • You never do it right from day one
  • Know the market
    • CakePHP shortcomings
    • Zend Framework shortcomings
    • Third party frameworks shortcomings
  • Know the market - CakePHP
    • Misleading terms: plugin, model…
      • Plugin: CakePHP allows you to set up a combination of controllers, models, and views and release them as a packaged application
    • Too database centric: CakePHP naming convention is driven by table names, not dependency injection mechanism.
    • Admin routing sucks: why do we need one-and-only backend for the whole application/plugin/etc…?
    • Flat application structure: plugin/controller/action and no more.
    • Global space constants
  • Know the market - CakePHP
    • No elegant way to change media file (css, javascript, meta content) on each layout page, controlled by a Controller.
    • <head>    <?php echo $html->charset(); ?>    <title>         <?php echo $title_for_layout; ?>    </title>    <?php      echo $html->css('cake.generic');      echo $javascript->link('prototype-1.6.0.2');     echo $scripts_for_layout;      echo $html->meta('icon','/myapp/img/favicon.ico', array ('type' =>'icon'));    ?> </head>
  • Know the market - CakePHP
    • loadModel(), loadController() are not about dependency injection
    • E.x: You want to provide access to a model from a Component Say you have a model called FooBar in a file called foo_bar.php
    • loadModel ('FooBar');  
    • $this ->FooBar = & new  FooBar();
    • loadModel() maybe deprecated in favor of
    • App::import ('Model', 'ModelName');
  • Know the market - CakePHP
    • beforeFilter(), afterFilter() are coupled with a certain controller (controller is a heavy object. It should avoid being hit too soon)
    • <?php   class   AppController   extends   Controller  {      var   $beforeFilter  =  array ( 'checkAccess' );      var   $components  =  array ( 'Acl' );      function   checkAccess() {     } } ?>
  • Know the market - CakePHP
    • Reuse of view via elements with requestAction() is bad and expensive
      • The dispatcher is called for each call to a controller (routing, figures out what (Plugin)/Controller/Action is request, loops through all $paths->controllerPaths files, to figure out what Controller to load)
      • The controller is set up again
    • Behavior: controllerActAsModel
    • Controller is an interface to another tier
    • Controller is not designed to provide data for internal components
    • Cache unfriendly
  • Know the market - CakePHP
    • Caching hits its hard time because there is no way to get generated view content
      • <?php
      • $this->element('helpbox', array(&quot;cache&quot; => array('time'=> &quot;+7 days&quot;,'key'=>'unique value')));
      • ?>
    • What about URL-based caching, session/cookie-based caching, geo-based caching, date-based caching
      • (there are a lot of things to tell about CakePHP but it is all for today)
  • Know the market – Zend Framework
    • Zend Framework tries to be a better PEAR
      • Powered by a solid foundation
      • A solid and controllable licensing (CLA)
      • More strictly controlled development environment
      • Enterprise-oriented class library
      • A well-defined roadmap and versioning
    • Zend Framework is a glue framework or framework-oriented class library
  • Know the market – Zend Framework
    • Zend Framework is extremely big and bloated
      • Zend Framework 1.6.2: 1261 file, 267 folders
      • Zend_Mail: 33 files
      • Zend_Pdf: 89 files
      • Zend_Controller: 50 files
      • Zend_View: 57 files
      • Drupal includes folders: 33 files
    • Zend Framework is designed most like Java frameworks
      • Small class file
      • Lot of classes: object graph is hard (see next)
      • Atomic responsibility
      • Strongly embrace design patterns
  •  
  • Know the market – Zend Framework
    • Everything is an object, even a HTML button or checkbox. The same to Java (see Apache Wicket, Tapestry, JBoss Seam)
      • class Zend_View_Helper_FormReset extends Zend_View_Helper_FormElement
      • {
      • public function formReset($name = '', $value = 'Reset', $attribs = null)
      • {
      • $info = $this->_getInfo($name, $value, $attribs);
      • extract ($info); // name, value, attribs, options, listsep, disable
      • // check if disabled
      • $disabled = '';
      • if ($disable) {
      • $disabled = ' disabled=&quot;disabled&quot;';
      • }
      • // get closing tag
      • $endTag = '>';
      • if ($this->view->doctype()->isXhtml()) {
      • $endTag = ' />';
      • }
      • // Render button
      • $xhtml = '<input type=&quot;reset&quot;'
      • . ' name=&quot;' . $this->view->escape($name) . '&quot;'
      • . ' id=&quot;' . $this->view->escape($id) . '&quot;'
      • . $disabled;
      • . . . . . . . . .
      • }
      • }
  • Know the market – Zend Framework
    • What Zend Framework brings
      • Lot of files are loaded per request which is a bad thing for a dynamic, interpreted language and stateless platform like PHP
      • Much more memory usage
      • Bad thing for PHP memory management model in which memory is allocated in small chunks
      • Zend Framework code: There are lot of require_once() call inside an if statement which is bad for opcode caching mechanism
      • Zend Framework leaves shared hosting in the cold.
        • 700 sites per server are quite normal
        • No control over file system optimization
        • No control over memory
        • No control over opcode caching
  • Know the market – Zend Framework
    • A glue framework requires you to know every concrete class and how to use them in a application life cycle
    • A lot of things to consider means bootstrapping is a mess
  • Know the market – Zend Framework
    • define ('ROOT_DIR', dirname(dirname(dirname(__FILE__))));
    • define ('APP_DIR',dirname(dirname(__FILE__)));
    • set_include_path ('.' . PATH_SEPARATOR . APP_DIR . '/lib/' . PATH_SEPARATOR . APP_DIR . '/application/default/models/' . PATH_SEPARATOR . ROOT_DIR . '/shared/lib/' . PATH_SEPARATOR . get_include_path());
    • //This requires that your Zend library lies in ROOT_DIR/shared/lib/
    • //make classes autoload without doing require
    • require_once ('Zend/Loader.php');
    • Zend_Loader::registerAutoload();
    • if ( defined ('ENV') !== TRUE) {
    • define ('ENV','production'); //change staging to production to go to production settings
    • }
    • $config = new Zend_Config_Xml(APP_DIR . '/config/config.xml', ENV);
    • Zend_Registry::set('config',$config);
    • //init session
    • $session = new Zend_Session_Namespace($config->session_name);
    • Zend_Registry::set('session',$session);
    • Zend_Db_Table::setDefaultAdapter(Zend_Db::factory(Zend_Registry::get('config')->database));
    • /**
    • * Init the Smarty view wrapper and set smarty suffix to the view scripts.
    • */
    • $view = new EZ_View_Smarty($config->smarty->toArray());
  • Know the market – Zend Framework
    • // use the viewrenderer to keep the code DRY instantiate and add the helper in one go
    • $viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper('ViewRenderer');
    • $viewRenderer->setView($view);
    • $viewRenderer->setViewSuffix($config->smarty->suffix);
    • /**
    • * Set inflector for Zend_Layout
    • */
    • $inflector = new Zend_Filter_Inflector(':script.:suffix');
    • $inflector->addRules(array(':script' => array ('Word_CamelCaseToDash', 'StringToLower'), 'suffix' => $config->layout->suffix));
    • // Initialise Zend_Layout's MVC helpers
    • Zend_Layout::startMvc( array ('layoutPath' => ROOT_DIR.$config->layout->layoutPath,
    • 'view' => $view,
    • 'contentKey' => $config->layout->contentKey,
    • 'inflector' => $inflector));
    • $front = Zend_Controller_Front::getInstance();
    • $front->setControllerDirectory(array(
    • 'default' => '../application/default/controllers',
    • 'blog' => '../application/blog/controllers',
    • ));
    • $front->throwExceptions(true);
    • // enable logging to default.log
    • $writer = new Zend_Log_Writer_Stream(APP_DIR.'/data/log/default.log');
    • $logger = new Zend_Log($writer);
    • // give easy access to the logger
    • Zend_Registry::set('logger', $logger);
    • try {
    • $front->dispatch();
    • } catch (Exception $e) {
    • echo nl2br($e->__toString());
    • }
  • Know the market – Zend Framework
    • Zend Framework is different.
      • It is not a solid application framework like CakePHP, it is designed to be a platform on which other frameworks are built
    • Technical details should be mentioned in another talk (enough for today)
  • A broader view on your framework
    • MVC Push or Pull
      • MVC Push or Passive View
    • <?php // Load the Savant3 class file and create an instance. require_once 'Savant3.php'; $tpl = new Savant3(); // Create a title. $name = &quot;Some Of My Favorite Books&quot;; // Generate an array of book authors and titles. $booklist = array(     array(         'author' => 'Hernando de Soto',         'title' => 'The Mystery of Capitalism'     ),     array(         'author' => 'Neal Stephenson',         'title' => 'Cryptonomicon'     ),     array(         'author' => 'Milton Friedman',         'title' => 'Free to Choose'     ) ); // Assign values to the Savant instance. $tpl->title = $name; $tpl->books = $booklist; // Display a template using the assigned values. $tpl->display('books.tpl.php'); ?>
  • A broader view on your framework
    • MVC Push or Pull
      • MVC Pull or so-called HMVC (see next): break a big controller into small ones
  • A broader view on your framework
  •  
  • A broader view on your framework
    • MVC Push or Pull: HMVC implementation
      • Master Controller
      • /**
      • * Show the home page
      • *
      • * @link http://www.wvbresearch.com/home/index/index
      • * @name index
      • * @access public
      • */
      • public function indexAction()
      • {
      • // Attach placeholder: the name of ElementGroup
      • $this->_layout->registerBody('homeIndex');
      • // Set content for the response
      • $this->_response->setContent($this->_layout->render('index3col'));
      • }
  • A broader view on your framework
    • MVC Push or Pull: HMVC implementation
      • Group Controller
      • class Group_HomeIndex extends Pone_View_ElementGroup
      • {
      • /**
      • * Elements in in this group
      • *
      • * @var array
      • */
      • protected $_elementsInGroup = array(
      • 'homeTopNegativeEpsSurprises', 'homeTopPositiveEpsSurprises',
      • 'homeIntroduction', 'brokerRatingsUpgrades', 'homeAnalystEstimatesSearchBox',
      • 'homeResearchReportSearchBox', 'latestResearchReports'
      • );
      • protected $_templateFile = 'homeIndex';
      • public function setup()
      • {
      • }
      • }
  • A broader view on your framework
    • MVC Push or Pull: HMVC implementation
      • Element Controller
      • class Element_LatestResearchReports extends Pone_View_Element
      • {
      • protected $_templateFile = 'latestResearchReportsOnHome';
      • /**
      • * List of recent research reports
      • *
      • * @var Pone_DataSet
      • */
      • public $researchReports;
      • public function setup()
      • {
      • $module = Pone::getContext()->getFront()->getRequest()->getModuleName();
      • $numberOfItems = 7;
      • if ('home' !== $module)
      • {
      • $this->_templateFile = 'latestResearchReports';
      • $numberOfItems = 10;
      • }
      • $dbConn = Pone_Action_Helper_Database::getInstance()->getConnection('oracleweb', true);
      • $researchReportDs = ResearchReportDatabaseService::getInstance($dbConn);
      • $this->researchReports = $researchReportDs->findRecentList($numberOfItems);
      • }
      • }
  • A broader view on your framework
    • MVC Push or Pull: HMVC implementation
      • Element Controller template
      • < div class =&quot; featureBlockHeader &quot;>
      • < h2 >Latest reports</ h2 >
      • </ div >
      • < div class=&quot; textBox &quot;>
      • < div class=&quot; textBoxContent &quot;>
      • <?php if ( true === $this->researchReports->isReadable()): $iter = $this->researchReports->getIterator(); ?>
      • < ul class=&quot; imgList &quot;>
      • <?php foreach ($iter as $report): ?>
      • < li ><a href=&quot;research/detail/view/rpid/<?php echo $report->get('report_id') ?>&quot;><?php echo $report->get('title'); ?></a></li>
      • <?php endforeach; ?>
      • </ul>
      • <?php else: echo $this->researchReports->getMessage(); endif; ?>
      • </ div >
      • </ div >
  • A broader view on your framework
    • IDE support
      • Code completion rocks
      • MVC Push is bad for view data documentation
      • Zend_Registry is bad for code completion
        • Zend_Registry :: set('logger', $logger);
      • Think of interface because implementing a way to load dynamic class from a variable or an array element.
      • Learn how to write DocBlock
  • A broader view on your framework
    • Core feature set
      • MVC framework
        • Model layer: DBO, File handling/transformation, business rules, workflows, search, messaging, memory, remote resource access …
      • Validation framework instead of form handling
      • Unified directory structure: model classes, controllers, views (page fragments, layouts), plugins, filters, custom exceptions, helpers
      • Session
      • Authentication and ACL: Abstract and extensible
        • HTTP Digest
        • Database backed
        • SAML/SSO
        • Serializable Unified Session User Object
  • A broader view on your framework
    • Core feature set
      • Validation framework
      • class Form_Signup extends Pone_Form_Input
      • {
      • . . . . . .
      • public function onPost()
      • {
      • // Email
      • $emailRules = array(
      • Pone_Form_Rule::EMAIL => array('feedback' => _t('common.error.email.notvalid'))
      • );
      • $this->setValidationRule('email', $emailRules);
      • // Email 2
      • $email2Rules = array(
      • Pone_Form_Rule::STRING_EQUAL => array('feedback' => _t('common.error.reemail.not_match'),
      • 'reference' => 'email')
      • );
      • $this->setValidationRule('email2', $email2Rules);
      • // password
      • $passwordRules = array(
      • Pone_Form_Rule::NOT_EMPTY => array('feedback' => _t('common.error.password.empty'))
      • );
      • $this->setValidationRule('password', $passwordRules);
      • // password 2
      • $password2Rules = array(
      • Pone_Form_Rule::STRING_EQUAL => array('feedback' => _t('common.error.repassword.not_match'),
      • 'reference' => 'password')
      • );
      • $this->setValidationRule('password2', $password2Rules);
      • }
      • }
  • A broader view on your framework
    • Much more things that need to take into account
      • Behavior layer
      • Caching
        • Distributed caching
        • Local caching
      • Dependency Injection framework
      • Internationalization
      • (enough for today)
  • Lessons to learn
    • Take your hand dirty please.
    • Singleton is bad thing when dependency injection and unit testing are taken into consideration
      • can't replace it with an interface
      • Factory allows for both discovery and instance management of the service providers
      • Final classes should keep singleton objects
      • $dbConn = Pone_Action_Helper_Database :: getInstance ()->getConnection('oracleweb', true);
      • $researchReportDs = ResearchReportDatabaseService :: getInstance ($dbConn);
      • $this->researchReports = $researchReportDs->findRecentList($numberOfItems);
  • Lessons to learn
    • Factory and interface make good things
      • Factory and Adapter are good for service providers
    • $conn = Pone_Database_ConnectionFactory::getConnection($config);
    • $stmt = $conn->createStatement();
    • $stmt ->addBatch(&quot;INSERT INTO test2 VALUES (1007, 'pcdinh1007', 1)&quot;);
    • $stmt ->addBatch(&quot;INSERT INTO test2 VALUES (1009, 'pcdinh1009', 1)&quot;);
    • $stmt ->addBatch(&quot;INSERT INTO test2 VALUES (1010, 'pcdinh1010', 1)&quot;);
    • $conn ->beginTransaction();
    • $updateCounts = $stmt->executeBatch();
  • Lessons to learn
    • Fluent interface/object chaining sometimes is a bad thing
      • Law of Demeter
    • $module = Pone::getContext()->getFront()->getRequest()->getModuleName();
  • Lessons to learn
    • Don’t think DAO or ActiveRecord, think Domain Respository
  • Lessons to learn
    • An interface between Model and Controller must be defined
      • Model class returns an array: bad thing. How to catch errors and deal with them in the view template
  • Lessons to learn
    • Dependency Injection
      • Does all injection through the constructor
      • $libBasePath = $basePath.'/libs';
      • $appBasePath = $basePath.'/apps';
      • Pone::executeContext( new BenchmarkContext() , $basePath, $appBasePath, $libBasePath);
      • OR
      • $front->setRequest(new Pone_Request( new Pone_Request_SimpleUrlParser() ));
      • Use Template Method design pattern
        • Seam
        • if (session_id() === '' && PHP_SAPI != 'cli')
        • {
        • Pone::getContext()->loadSessionUserClass();
        • $started = session_start(); // PHP 5.3: returns false or true
        • $this->_started = true;
        • }
      • Use XML/YAML like in Spring, Symfony which is somewhat heavy in an interpreted language like PHP
  • Design by Interface
    • Rule: Don’t call me, I will call you
    • Template Method
    • Convention over configuration
    • That’s end for today
  • Thanks you
    • Any question?