Download - Lean Php Presentation
Lean PHP PracticesAlan Pinstein & Jason Ardell
Atlanta PHP Users Group
April 1, 2009
What is Leverage?
More work the first time, usually involves:
Learning a new library or tool
Setting up infrastructure
Easier to do it 2..∞
Easier to maintain
Types of Leverage
Project Leverage
Testing Leverage
Community Leverage
Project Leverage
It hurts when I...
Set up my project on a new machine
Deploy new versions of my project
Stop doing that!
Project Leverage - Tools
mp - Migrations for PHP (presentation)
rake/cap => pantr is php
gitflow
user_management
Dependency management
runit - daemons / process supervision
config-magic
runit
Process Supervisor
Don’t write daemons - you’ll do it wrong
Write scripts, and runit keeps them alive in the background
Gracefully handles errors, failures, logging, upgrades
runit in action
// graceful upgrade2010-02-10_03:53:55 Stop requested for worker process on queue: inbox2010-02-10_03:53:55 Stopping worker process on queue: inbox2010-02-10_03:53:56 Starting worker process on queue: inbox
// graceful error handling2010-04-01_17:55:08 [Job: 383820 RUNNING] Process InboxImage 60629 (Carol 011_fi.jpg)2010-04-01_17:55:08 [Job: 383820 COMPLETED]2010-04-01_17:55:08 JQWorker doesn't have enough memory for next job (4194304).2010-04-01_17:55:08 Starting worker process on queue: inbox 2010-04-01_17:55:09 [Job: 383821 RUNNING] Process InboxImage 60622 (Carol 015_fi.jpg)2010-04-01_17:55:09 [Job: 383821 COMPLETED]
config-magic
N config files * M environments = Too Many Config Files
Config-Magic applies
“profiles”: dev-alan, dev-jason, staging, production
to
“templates”: framework, orm, rake, cap, shell scripts, webserver
# config.ini[templates]httpd.configFileTemplate = ##TEMPLATES_DIR##/##CONFIG##.confhttpd.configFile = ##OUTPUT_DIR##/##CONFIG##.confpropel-conf.configFileTemplate = ##TEMPLATES_DIR##/virtualtour-conf.phppropel-conf.configFile = ##OUTPUT_DIR##/virtualtour-conf.phppropel-build-properties.configFileTemplate = ##TEMPLATES_DIR##/build.propertiespropel-build-properties.configFile = ##OUTPUT_DIR##/../propel-build/build.propertiespropel-conf-xml.configFileTemplate = ##TEMPLATES_DIR##/runtime-conf.xmlpropel-conf-xml.configFile = ##OUTPUT_DIR##/../propel-build/runtime-conf.xmlwebapp.configFileTemplate = ##TEMPLATES_DIR##/##CONFIG##.confwebapp.configFile = ##OUTPUT_DIR##/##CONFIG##.confsh.configFileTemplate = ##TEMPLATES_DIR##/##CONFIG##.confsh.configFile = ##OUTPUT_DIR##/##CONFIG##.conf
[data]; your default data here. any settings here will be overridden by values in the profile's ini file on a setting-by-setting basisisProduction = false
path.psql = /usr/bin/psqlpath.convert = /usr/bin/convertpath.propel-gen = externals/pear/propel-genpath.phocoa = ##path.project.appDir##/externals/phocoa/phocoa
path.php.include_path = ##path.phocoa##:##path.project.appDir##:##path.project.appDir##/classes:##path.project.appDir##/externals:##path.project.appDir##/externals/pear/phppath.php = "/usr/bin/php -d include_path=##path.php.include_path##"
app.group = showcase_webapp.user = tourbuzzhttpd.user = _www
# profile - dev.inipath.php = "/opt/local/bin/php -d include_path=##path.php.include_path##"path.project.containerDir = /Users/ardell/Documents/workspace/tourbuzzpath.project.appDir = ##path.project.containerDir##/tourbuzzpath.awstats = /opt/showcase/awstats/wwwroot/cgi-bin/awstats.plpath.convert = /opt/local/bin/convertpath.psql = /opt/local/lib/postgresql83/bin/psql
host.name = ardell.dev.tourbuzz.nethost.aliases = host.ip = 127.0.0.1host.port = 8080
db.host = localhostdb.name = virtualtour_devdb.user = virtualtourdb.pass =
app.user = ardellapp.group = tourbuzz_web
runit.system_service_dir = /opt/local/var/service
# template#!/bin/shPHP="##path.php##"export PSQL=##path.psql##export IS_PRODUCTION=<?php print ($profileData['##isProduction##'] ? 1 : 0); ?>
# db settingsexport DB_ROOT_USER=##db.root.user##export DB_USER=##db.user##export DB_NAME=##db.name##export DB_HOST=##db.host##export PROJECT_DIR=##path.project.containerDir##export LOG_DIR=$PROJECT_DIR/logexport APP_DIR=##path.project.appDir##export APP_USER=##app.user##export APP_GROUP=##app.group##
#!/bin/shPHP="/opt/local/bin/php -d include_path=/Users/alanpinstein/dev/sandbox/virtualtour/tourbuzz/externals/phocoa/phocoa:/Users/alanpinstein/dev/sandbox/virtualtour/tourbuzz:/Users/alanpinstein/dev/sandbox/virtualtour/tourbuzz/classes:/Users/alanpinstein/dev/sandbox/virtualtour/tourbuzz/externals:/Users/alanpinstein/dev/sandbox/virtualtour/tourbuzz/externals/pear/php"export PSQL=/opt/local/lib/postgresql83/bin/psqlexport IS_PRODUCTION=0# db settingsexport DB_ROOT_USER=postgresexport DB_USER=virtualtourexport DB_NAME=virtualtour_devexport DB_HOST=localhostexport PROJECT_DIR=/Users/alanpinstein/dev/sandbox/virtualtourexport LOG_DIR=$PROJECT_DIR/logexport APP_DIR=/Users/alanpinstein/dev/sandbox/virtualtour/tourbuzzexport APP_USER=alanpinsteinexport APP_GROUP=showcase_web
$ cfg dev
Testing Leverage
It hurts when I...
Refactor
Introduce regression bugs
Don’t have confidence that my code works
Have to test my web pages in N browsers
Testing Leverage -Tools
Write Testable Code - Miško Hevery
This is the hard part - there are good tools for the rest
Writing Tests (PHPUnit)
Mocking Collaborators - $this->getMock(‘MyClass’)
Scenario-based Tests - @dataProvider
Bootstrapping Data (Fixturenator)
Selenium RC - SauceLabs Hosted Browsers
fixturenator
Test Data doesn’t always scale
Makes it easy to decouple tests from test data
Prototypal Test Data Generation
Inspired by factory_girl
fixturenator - Define Objects// Static data for each instance Fixturenator::define('User', array('username' => 'joe'));Fixturenator::define('User', function($factory) { $factory->username = 'joe';});Fixturenator::define('User', create_function('$factory', ' $factory->username = 'joe';'});
// Dynamism$user = Fixturenator::create('User', array('password' => 'return rand(1000,9999);'));$user = Fixturenator::create('User', array('email' => 'return "{$o->username}@domain.com";'));
// SequencesFixturenator::createSequence('username', 'return "username{$n}";');Fixturenator::getSequence('username')->next(); // => username1Fixturenator::getSequence('username')->next(); // => username2
fixturenator - Build Object Graphs
// Factory.php
Fixturenator::define('ValidUser', array('username' => 'joe'));
// Test.php
// Returns an unsaved User instance$user = Fixturenator::create('ValidUser');
// Customize the generated object$user = Fixturenator::create('ValidUser', array('password' => '1234'));
$this->assertTrue($user->validate());$user->setPassword(NULL); // User requires password$this->assertFalse($user->validate());
Community Leverage
It hurts when I....
Try to share my PHP libraries with other people
Re-write code that I know already exists
Try to fix bugs in projects that aren’t my own
Community Leverage - Tools
Pearfarm (Gemcutter for PHP)
A Community PEAR Server
Trivially Easy to Make PEAR Packages
“App Store” Effect
GitHub
Social Coding Infrastructure
Easy to contribute / accept contributions
“App Store” Effect
pearfarm
$spec = Pearfarm_PackageSpec::create(array(Pearfarm_PackageSpec::OPT_BASEDIR => dirname(__FILE__))) ->setName('fixturenator') ->setChannel('apinstein.pearfarm.org') ->setSummary('A factory-based fixture generator.') ->setDescription('AWESOME.') ->setReleaseVersion('0.0.3') ->setReleaseStability('alpha') ->setApiVersion('0.0.3') ->setApiStability('alpha') ->setLicense(Pearfarm_PackageSpec::LICENSE_MIT) ->setNotes('http://github.com/apinstein/fixturenator') ->addMaintainer('lead', 'Alan Pinstein', 'apinstein', '[email protected]') ->addFilesSimple('Fixturenator.php');
PHP really needs...CLI Framework (for building good tools)
Project Management (rake/cap)
PEAR/Dependency Management
Efficient PEAR install/upgrades
Non-PEAR code: php/lib/exec
Better Community
More Quality Contributions
More Cooperation
More Use of Modern Language Features