building Simple
PHP components
and why you should
Been using PHP since 1997 (PHP 2.0)
Build a few small libraries
and one humongous platform
Ariadne ( http://www.ariadne-cms.org/ )
The problem:
PHP is fun for small prototypes and hacks.
But building large software systems is a problem
reliability, security, maintainability... fun
Not PHP specific
This is not a problem only for PHP software
It is a problem of failing software design
So why does it fail?
architecture
"Whatever we [in computing] do is more like what the Egyptians did. Building pyramids, piling things on top of each other."
Alan Kay - Programming and Scaling
http://www.tele-task.de/archive/video/flash/14029/
Complexity
"Complexity is the single major difficulty in the successfull development of large-scale software system"
- 'Out of the tar pit' - B. Moseley, P. Marks
Complexity
"... Condition of having many diverse and autonomous but interrelated and interdependent components or parts linked through many (dense) interconnections. ..."- businessdictionary.com
what makes software complex
Rich Hickey - clojure
intertwining multiple concerns
Complect:
To join by weaving or twining together; interweave.
Simple made easy - Rich Hickey
http://www.infoq.com/presentations/Simple-Made-Easy
Knitted castle or lego castle?
Out of the tar pit
Software complexity:
- Shared State
- Control
- Code Volume
Ben Moseley / Peter Marks - 2006
http://shaffner.us/cs/papers/tarpit.pdf
shared state
global variables, closures,
objects, iterators
Functional Programming
no shared state
there are no variables
every function call will return exactly the same result given the same arguments
there are no side effects
... in a pure functional programming language anyway
Alan kay
Object Oriented Programming
"OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late binding of all things."
"The big idea is messaging..."
PHP
anything goes, but:
- share nothing architecture ( per request )
- shared state is explicitly global
no other shared scopes
( except for closures - which are at least explicit ) - all variables are passed by value
( except for objects ) - almost complete message passing for objects
( __call, __get, etc. )
control
if/then/else, while/for, try/catch
recipe style: first do this, then do that
functional programming
declarative style: specify what, not how
recursion as the only loop mechanism
loops explicitly depend on characteristics of the data
Rich Hickey
complex vs. simple
- inheritance/switch/match vs. polymorphism
- syntax vs. data
- loops vs. set functions
- ORM vs. declarative data manipulation
- conditionals vs. rules
PHP
- traditionally imperative, recipe based
- but contains many FP style constructs
- array_map, array_reduce, foreach
- expressive arrays ( hash )
zend form
class Application_Form_Guestbook extends Zend_Form { public function init() { // Set the method for the display form to POST $this->setMethod('post'); // Add an email element $this->addElement('text', 'email', array( 'label' => 'Your email address:', 'required' => true, 'filters' => array('StringTrim'), 'validators' => array( 'EmailAddress', ) )); ...
symfony 2 form
$form = $this->createFormBuilder($task) ->add('task', 'text') ->add('dueDate', 'date', array(
'required' => true
) ) ->getForm();
data only
$form = [ 'fields' => [ 'name' => [ 'type' => 'text', 'required' => true ] ] ];
now it is trivial to add support for any form code
- zend or symfony or ...
simple component
$form = new MyForm([ 'fields' => [ 'name' => [ 'type' => 'text', 'required' => true ] ] ]); if ( $form->isSubmitted() && $form->isValid() ) { echo 'Thank you'; } else { echo $form; }
code volume
tr -cs A-Za-z '\n' |
tr A-Z a-z |
sort |
uniq -c |
sort -rn |
sed ${1}q
Knuth: literate programming - few hundred lines
McIlroy: shell script - 6 lines
http://www.leancrew.com/all-this/2011/12/more-shell-less-egg/
"the defect density of code, bugs per hundred lines, tends to be a constant independent of implementation language"
- Eric S. Raymond
"Program testing can convincingly show the presence of bugs but it is hopelessly inadequate to show their absence."
- E. Dijkstra
so writing less code is more important
than writing more unit tests
alan kay
domain specific languages can drastically reduce code size and complexity
cairo: graphics library written in c
code size in 100.000's lines of code
jitblt/gezira: hundreds lines of code in specific dsl, performance similar to cairo.
PHP
has the potential to write low volume code
- the no-framework MVC framework, Rasmus Lehrdorf -
it is already a DSL for websites
but...
framework hell
or library hell happened
example: ezPublish 5
- developer created optimization to speed up PHP's autoloading.
why?
- ezPublish 5 contains > 2600 classes
partially based on Symfony 2
templates
php is a template language
but: Smarty, Twig, Moustache, ... or:
function parseTemplate( $template, $arguments ) { $regex = '\{\{(.*)\}\}'; return preg_replace_callback( '/'.$regex.'/', function( $matches ) use ( $arguments ) { return isset( $arguments[ $matches[1] ] ) ? $arguments[ $matches[1] ] : ''; }, $template ); }
http requests
Guzzle, Buzz, Zend, Symfony, or:
function httpRequest( $url, $options ) { $options += [ 'method' => 'GET' ]; $context = stream_context_create( array( 'http' => $options ) ); $response = @file_get_contents( $url, false, $context ); return [ 'response' => $response, 'headers' => $http_response_header ]; }
conclusion
- define minimal abstract interface (facade)
- implement minimal version
- switch to seperate library when complexity is really needed
- embrace existing code, but be selective
yagni - you ain't gonna need it
scale out in abstraction rather than verbosity.
so now what
"Composing simple components is the key to robust software" - Rich Hickey
PHP is embracing components
Composer, Packagist, Laravel, Aura, etc.
Components
- DSL: PHP-Peg, Parsec
- DI: Pimple
- HTTP: Guzzle, Buzz
- Image: Imagine
see: https://packagist.org/explore/popular
but: quality and complexity vary wildly
arc
Ariadne Components - but also "Architecture"
a set of simple components for the most common web application related tasks
url, http, html, xml, configuration, events, grants
why arc?
short answer: we needed a way forward for Ariadne
Ariadne stores content in a virtual filesystem
so all components need to understand filesystem-like hierarchies
most currently available components don't
long answer
ARC tries to embrace 'simple'
uses a few well-defined abstractions:
path, url, tree
each with functional programming style operations
ARC uses very loosely coupled components
- almost no reliance on other components
- DI and composition instead of inheritance
- no type checking but duck-typing
ARC tries to be easy
- uses basic input types and result types
- defines static factory methods
- uses a simple dependency injection container
(which you can ignore)
- that is also stackable
ARC is small
biggest files are
- path - 12 methods - 300 lines
- tree - 9 methods - 230 lines
classes
- 18 static factory classes
- 40 component classes
ARC is not a framework
doesn't try to be
- no routing
- no mvc
- no orm
just the bricks, none of the knitting
EXAMPLES
html writer
function menu( $list ) { return \arc\html::ul( [ 'class' => 'menu' ], array_map( function( $child ) { return \arc\html::li( \arc\html::a( [ 'href' => $child['url'] ] , $child['name'] ) ) }, $list ) ); }
Lambda objects
$view = \arc\lambda::prototype( [
'class' => 'menu', 'menu' => function( $children ) { return \arc\html::ul( ['class' => $this->class], array_map( $this->menuitem, (array) $children ) ); }, 'menuitem' => function( $input ) { return \arc\html::li(
\arc\html::a(
[ 'href' => $input['url'] ],
$input['name']
), ( isset( $input['children'] ) ? $this->menu( $input['children'] ) : null ) ); }, ] ); echo $view->menu( $menulist );
path
$result = \arc\path::parents( '/a/b/c/' );
// => '/a/', '/a/b/', '/a/b/c/' $result = \arc\path::reduce( '/a/b/c/', function( $result, $item ) { return $result . '[' . $item . ']'; }, '' ); // => '[a][b][c]' $result = \arc\path::collapse( '/a/../b/' ); // => '/b/'
tree
$tree = [ '/a/' => 'Item A', '/a/b/c/' => 'Item C', '/d/e/' => Item D' ]; $root = \arc\tree::expand( $tree ); $result = \arc\tree::walk( $root, function( $node ) { if ( strpos( 'C', $node->nodeValue ) !== false ) { return $node; } }); // => node( 'Item C' ) $result->nodeValue = 'Item C changed'; $tree = \arc\tree::collapse( $root ); // => [ // '/a/' => 'Item A', // '/a/b/c/' => 'Item C changed', // '/d/e/' => 'Item D' // ]
events
$listener = \arc\events::cd( '/test/' ) ->listen( 'testEvent', function($event) { $event->data['seen'] = true; } ); $result = \arc\events::cd( '/test/child/' )
->fire( 'testEvent', array( 'seen' => false ) ); if ( $result ) {
// event not cancelled
}
cache proxy
$proxy = \arc\cache::proxy( new ComplexObject(), 3600 ); //first call $result = $proxy->takesTooLong(); //second call is cached $result2 = $proxy->takesTooLong();
Because of ducktyping arc accepts proxies wherever any other object is expected.
grants
$user = 'test'; $userGrants = \arc\grants::user( $user ); $userGrants->cd( '/' )->setUserGrants('read edit add >delete'); ... if ( $userGrants->check('read') ) { // has read grant at the current path. }
STATE of arc
still experimental
lots of ideas have been tried, many rejected:
- php-option
- null objects
- duck typing by __implements
ease of use also important
state of arc
- path abstraction 100%
- events 90%
- tree abstraction 90%
- url abstraction 90%
- grants mgmnt 80%
- stackable di container 80%
- configuration mgmnt 80%
- cache 80%
- html and xml writer 90%
- html and xml parser 10%
- documentation... 5%?
state of ARC
http://ariadne-cms.github.com/arc/
https://packagist.org/packages/arc/arc
(somewhat stable version)
https://github.com/poef/arc
( more experimental version )
https://joind.in/8098
http://www.rvl.io/poefke/simple-php-components/
simple php components
By Auke van Slooten
simple php components
- 5,217