hanoi php day 2008 - 01.pham cong dinh - how.to.build.your.own.framework
TRANSCRIPT
- 1. How to learn to buildyour 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
2. 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 WorldVest 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)
3. 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
4. Agenda 40 slides
- Making judgments
- Top notch frameworks and their shortcomings
- A broader view on your framework
- Lessons to learn
5. 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
6. 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
7. Know the market
- CakePHP shortcomings
- Zend Framework shortcomings
- Third party frameworks shortcomings
8. 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
9. Know the market - CakePHP
- No elegant way to change media file (css, javascript, meta content) on each layout page, controlled by a Controller.
10. 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');
11. Know the market - CakePHP
- beforeFilter(), afterFilter() are coupled with a certain controller (controller is a heavy object. It should avoid being hit too soon)
12. Know the market - CakePHP
- Reuse of view via elements withrequestAction()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
13. Know the market - CakePHP
- Caching hits its hard time because there is no way to get generated view content
- 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)
14. 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
15. 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
16. 17. 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)
-
- classZend_View_Helper_FormResetextendsZend_View_Helper_FormElement
-
- {
-
- public functionformReset($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="disabled"';
-
- }
-
- // get closing tag
-
- $endTag = '>';
-
- if ($this->view->doctype()->isXhtml()) {
-
- $endTag = ' />';
-
- }
-
- // Render button
-
- $xhtml = '
-
- . ' name="' . $this->view->escape($name) . '"'
-
- . ' id="' . $this->view->escape($id) . '"'
-
- . $disabled;
-
- . . . . . . . . .
-
- }
-
- }
18. 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
-
19. 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
20. 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 =newZend_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 =newEZ_View_Smarty($config->smarty->toArray());
21. 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 =newZend_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 =newZend_Log_Writer_Stream(APP_DIR.'/data/log/default.log');
- $logger =newZend_Log($writer);
- // give easy access to the logger
- Zend_Registry::set('logger', $logger);
- try{
- $front->dispatch();
- }catch (Exception $e) {
- echonl2br($e->__toString());
- }
22. 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)
23. A broader view on your framework
- MVC Push or Pull
-
- MVC Push or Passive View
24. 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
25. A broader view on your framework 26. 27. A broader view on your framework
- MVC Push or Pull:HMVC implementation
-
- Master Controller
-
- /**
-
- * Show the home page
-
- *
-
- * @linkhttp://www.wvbresearch.com/home/index/index
-
- * @nameindex
-
- * @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'));
-
- }
28. A broader view on your framework
- MVC Push or Pull: HMVC implementation
-
- Group Controller
-
- classGroup_HomeIndexextendsPone_View_ElementGroup
-
- {
-
- /**
-
- * Elements in in this group
-
- *
-
- * @var array
-
- */
-
- protected$_elementsInGroup = array(
-
- 'homeTopNegativeEpsSurprises', 'homeTopPositiveEpsSurprises',
-
- 'homeIntroduction', 'brokerRatingsUpgrades', 'homeAnalystEstimatesSearchBox',
-
- 'homeResearchReportSearchBox', 'latestResearchReports'
-
- );
-
- protected$_templateFile = 'homeIndex';
-
- public functionsetup()
-
- {
-
- }
-
- }
29. A broader view on your framework
- MVC Push or Pull: HMVC implementation
-
- Element Controller
-
- classElement_LatestResearchReportsextendsPone_View_Element
-
- {
-
- protected$_templateFile = 'latestResearchReportsOnHome';
-
- /**
-
- * List of recent research reports
-
- *
-
- * @var Pone_DataSet
-
- */
-
- public$researchReports;
-
- public functionsetup()
-
- {
-
- $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);
-
- }
-
- }
30. A broader view on your framework
- MVC Push or Pull: HMVC implementation
-
- Element Controller template
-
- < div class =" featureBlockHeader ">
-
- < h2 >Latest reports h2 >
-
- div >
-
- < divclass=" textBox ">
-
- < divclass=" textBoxContent ">
-
- < ulclass=" imgList ">
-
- < li >get('report_id') ?>">
-
- div >
-
- div >
31. 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
32. 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
-
33. A broader view on your framework
- Core feature set
-
- Validation framework
-
- class Form_Signup extends Pone_Form_Input
-
- {
-
- . . . . . .
-
- public function onPost()
-
- {
-
- $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);
-
- }
-
- }
34. 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)
35. 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);
36. 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("INSERT INTO test2 VALUES (1007, 'pcdinh1007', 1)");
- $stmt ->addBatch("INSERT INTO test2 VALUES (1009, 'pcdinh1009', 1)");
- $stmt ->addBatch("INSERT INTO test2 VALUES (1010, 'pcdinh1010', 1)");
- $conn ->beginTransaction();
- $updateCounts= $stmt->executeBatch();
37. Lessons to learn
- Fluent interface/object chaining sometimes is a bad thing
-
- Law of Demeter
- $module = Pone::getContext()->getFront()->getRequest()->getModuleName();
38. Lessons to learn
- Dont think DAO or ActiveRecord, think Domain Respository
39. 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
40. 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
41. Design by Interface
- Rule: Dont call me, I will call you
- Template Method
- Convention over configuration
- Thats end for today
42. Thanks you
- Any question?