building Simple

PHP components

and why you should

My name is Auke van Slooten

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



Donald Knuth vs. Doug McIlroy
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,068