42
Copyright © Pham Cong Dinh. All rights un-reserved. 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

How to learn to build your own PHP framework

Embed Size (px)

DESCRIPTION

Bridging PHP, OOP, design patterns and software architecture

Citation preview

Page 1: How to learn to build your own PHP framework

Copyright © Pham Cong Dinh. All rights un-reserved.

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 2008Hanoi - Vietnam

Page 2: How to learn to build your own PHP framework

2

IntroductionHow to develop your own frameworkHow to develop your own framework

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)

Page 3: How to learn to build your own PHP framework

3

IntroductionHow to develop your own frameworkHow to develop your own framework

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

Page 4: How to learn to build your own PHP framework

4

IntroductionHow to develop your own frameworkHow to develop your own framework

Agenda – 40 slides

Making judgments Top notch frameworks and their shortcomings A broader view on your framework Lessons to learn

Page 5: How to learn to build your own PHP framework

5

How to develop your own frameworkHow to develop your own framework

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

Page 6: How to learn to build your own PHP framework

6

How to develop your own frameworkHow to develop your own framework

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

Page 7: How to learn to build your own PHP framework

7

How to develop your own frameworkHow to develop your own framework

Know the market

• CakePHP shortcomings• Zend Framework shortcomings• Third party frameworks shortcomings

Page 8: How to learn to build your own PHP framework

8

How to develop your own frameworkHow to develop your own framework

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

Page 9: How to learn to build your own PHP framework

9

How to develop your own frameworkHow to develop your own framework

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>

Page 10: How to learn to build your own PHP framework

10

How to develop your own frameworkHow to develop your own framework

Know the market - CakePHP

• loadModel(), loadController() are not about dependency injection

• E.x: You want to provide access to a model from a ComponentSay 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');

Page 11: How to learn to build your own PHP framework

11

How to develop your own frameworkHow to develop your own framework

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(){     } }

?>

Page 12: How to learn to build your own PHP framework

12

How to develop your own frameworkHow to develop your own framework

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

Page 13: How to learn to build your own PHP framework

13

How to develop your own frameworkHow to develop your own framework

Know the market - CakePHP

• Caching hits its hard time because there is no way to get generated view content

<?php

$this->element('helpbox', array("cache" => array('time'=> "+7 days",'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)

Page 14: How to learn to build your own PHP framework

14

How to develop your own frameworkHow to develop your own framework

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

Page 15: How to learn to build your own PHP framework

15

How to develop your own frameworkHow to develop your own framework

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

Page 16: How to learn to build your own PHP framework

16

How to develop your own frameworkHow to develop your own framework

Page 17: How to learn to build your own PHP framework

17

How to develop your own frameworkHow to develop your own framework

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="disabled"'; }

// get closing tag $endTag = '>'; if ($this->view->doctype()->isXhtml()) { $endTag = ' />'; }

// Render button $xhtml = '<input type="reset"' . ' name="' . $this->view->escape($name) . '"' . ' id="' . $this->view->escape($id) . '"' . $disabled; . . . . . . . . . }}

Page 18: How to learn to build your own PHP framework

18

How to develop your own frameworkHow to develop your own framework

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

Page 19: How to learn to build your own PHP framework

19

How to develop your own frameworkHow to develop your own framework

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

Page 20: How to learn to build your own PHP framework

20

How to develop your own frameworkHow to develop your own framework

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 requirerequire_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());

Page 21: How to learn to build your own PHP framework

21

How to develop your own frameworkHow to develop your own framework

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 helpersZend_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 loggerZend_Registry::set('logger', $logger); try { $front->dispatch();} catch(Exception $e) { echo nl2br($e->__toString());}

Page 22: How to learn to build your own PHP framework

22

How to develop your own frameworkHow to develop your own framework

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)

Page 23: How to learn to build your own PHP framework

23

How to develop your own frameworkHow to develop your own framework

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 = "Some Of My Favorite Books";

// 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');?>

Page 24: How to learn to build your own PHP framework

24

How to develop your own frameworkHow to develop your own framework

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

Page 25: How to learn to build your own PHP framework

25

How to develop your own frameworkHow to develop your own framework

A broader view on your framework

Page 26: How to learn to build your own PHP framework

26

How to develop your own frameworkHow to develop your own framework

Page 27: How to learn to build your own PHP framework

27

How to develop your own frameworkHow to develop your own 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')); }

Page 28: How to learn to build your own PHP framework

28

How to develop your own frameworkHow to develop your own framework

A broader view on your framework

• MVC Push or Pull: HMVC implementation– Group Controllerclass 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() {

}}

Page 29: How to learn to build your own PHP framework

29

How to develop your own frameworkHow to develop your own framework

A broader view on your framework

• MVC Push or Pull: HMVC implementation– Element Controllerclass 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); }}

Page 30: How to learn to build your own PHP framework

30

How to develop your own frameworkHow to develop your own framework

A broader view on your framework

• MVC Push or Pull: HMVC implementation– Element Controller template<div class="featureBlockHeader"> <h2>Latest reports</h2> </div> <div class="textBox"> <div class="textBoxContent"> <?php if (true === $this->researchReports->isReadable()): $iter = $this-

>researchReports->getIterator(); ?> <ul class="imgList"> <?php foreach ($iter as $report): ?> <li><a href="research/detail/view/rpid/<?php echo $report-

>get('report_id') ?>"><?php echo $report->get('title'); ?></a></li> <?php endforeach; ?> </ul> <?php else: echo $this->researchReports->getMessage(); endif;?> </div></div>

Page 31: How to learn to build your own PHP framework

31

How to develop your own frameworkHow to develop your own framework

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

Page 32: How to learn to build your own PHP framework

32

How to develop your own frameworkHow to develop your own framework

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

Page 33: How to learn to build your own PHP framework

33

How to develop your own frameworkHow to develop your own framework

A broader view on your framework

• Core feature set– Validation frameworkclass 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);

}}

Page 34: How to learn to build your own PHP framework

34

How to develop your own frameworkHow to develop your own framework

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)

Page 35: How to learn to build your own PHP framework

35

How to develop your own frameworkHow to develop your own framework

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);

Page 36: How to learn to build your own PHP framework

36

How to develop your own frameworkHow to develop your own framework

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();

Page 37: How to learn to build your own PHP framework

37

How to develop your own frameworkHow to develop your own framework

Lessons to learn

• Fluent interface/object chaining sometimes is a bad thing– Law of Demeter

$module = Pone::getContext()->getFront()->getRequest()->getModuleName();

Page 38: How to learn to build your own PHP framework

38

How to develop your own frameworkHow to develop your own framework

Lessons to learn

• Don’t think DAO or ActiveRecord, think Domain Respository

Page 39: How to learn to build your own PHP framework

39

How to develop your own frameworkHow to develop your own framework

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

Page 40: How to learn to build your own PHP framework

40

How to develop your own frameworkHow to develop your own framework

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

Page 41: How to learn to build your own PHP framework

41

How to develop your own frameworkHow to develop your own framework

Design by Interface

• Rule: Don’t call me, I will call you• Template Method• Convention over configuration

That’s end for today

Page 42: How to learn to build your own PHP framework

42

How to develop your own frameworkHow to develop your own framework

Thanks you

• Any question?