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

ARC

Ariadne Components: a set of simple components for the most common web application related tasks



ARC Components


  • arc/base
        path, tree, prototype, context
  • arc/cache
        cache store and cache proxy
  • arc/xml
        xml writer and parser
  • arc/web
        url and http client
  • arc/config
  • arc/events
  • arc/grants
        user and group rights management


ARC uses very loosely coupled components

  • no reliance on other components

  • small code size
    ...which means less bugs

  • dependency injection and composition
    ...instead of inheritance

  • no type checking but duck-typing
    ...for flexibility


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

data types first

common abstractions across arc

functional approach where possible


  • arc\path
  • arc\tree


arc\path

any string seperated by '/' characters
data type not defined by class, but by its functions
$path = \arc\path::collapse( '..\\foo/./bar', '/baz/' );
// -> '/foo/bar/'

$parent = \arc\path::parent( $path );
// -> '/foo/'

$head = \arc\path::head( '/foo/bar/baz/' );
// -> 'foo'

$tail = \arc\path::tail( '/foo/bar/baz/' );
// -> /bar/baz/

$diff = \arc\path::diff( '/foo/bar/baz/', '/foo/baz/bar/' );
// -> ../baz/bar/

$path = \arc\path::map( $path, function($part) { return strrev($part); } );
// -> /oof/rab/

$path = \arc\path::reduce( $path, function( $prev, $part ) { return $prev++; } );
// -> 2

arc\tree

class with array notation for interop

$tree = [
    '/foo/'     => 'Foo',
    '/foo/bar/' => 'Foo Bar',
    '/foo/baz/' => 'Foo Baz',
    '/bar/baz/' => 'Bar Baz'
];
$root = \arc\tree::expand( $tree );
/*
    $root->nodeValue = null;
         ->childNodes = [
             'foo' => node->nodeValue = 'Foo', 
                          ->childNodes = [
                              'bar' => node->nodeValue = 'Foo Bar'
                          ]
                          ->parentNode = $root
             'bar' => node ... etc      
*/

$treeCopy = \arc\tree::collapse($root);
// -> [ '/foo/' => 'Foo', '/foo/bar/' => 'Foo Bar', ... ]
defines only the tree structure, not the data

arc/tree

still (mostly) defined by functions not methods

$bar = $root->cd('/foo/bar/');

$result = \arc\tree::dive( 
    $bar, 
    function( $node ) { // going 'down'
        return ( $node->nodeName == 'foo' ) ? $node->nodeValue : null;
    },
    function( $node, $previous ) { // going back 'up'
        return $previous . ' ' . $node->nodeValue;
    }
);
// -> 'Foo Foo Bar'

arc/tree

other functions

$parents = \arc\tree::parents( $bar, function( $node ) {
    return $node->nodeValue;
});
// -> [ '/' => null, '/foo/' => 'Foo', '/foo/bar/' => 'Foo Bar' ]

$list = \arc\tree::ls( $root, function( $node ) {
    return $node->nodeValue;
});
// -> [ '/foo/bar/' => 'Foo Bar', '/foo/baz/' => 'Foo Baz'

$searchResults = \arc\tree::search( $roo, function( $node ) {
    if ( strpos( $node->nodeValue, 'Bar' ) !== false ) {
        return $node->getPath()
    }
);
// -> '/foo/bar/'

$tree2 = \arc\tree::map( $root, function($node) {
    return (isset($node->nodeValue) ? strtolower($node->nodeValue) : null);});
// [ '/foo/' => 'foo', '/foo/bar/' => 'foo bar', ... ]

arc/tree

other functions continued

$filtered = \arc\tree::filter( $root, function($node) {
    return ( strpos( $node->nodeValue, 'Foo' )!== false );
});
// -> [ '/foo/' => 'Foo', '/foo/bar' => 'Foo Bar', '/foo/baz/' => 'Foo Baz' ]


$reduced = \arc\tree::reduce( 
    $root, 
    function( $node, $reduced ) {
        return (isset($node->nodeValue) ? $node->nodeValue . ' ' : '');
    }, 
    ''
);
// -> 'Foo Foo Bar Foo Baz Bar Baz '

$sorted = \arc\tree::sort( $root, function( $a, $b ) {
    return ( $a->nodeValue < $b->nodeValue )
});
// -> [ '/bar/baz/' => 'Bar Baz', '/foo/' => 'Foo', '/foo/bar/' => 'Foo Bar', ... ]

Why these abstractions?

let's take a look at arc/grants


This component allows you to keep track of user rights ( grants )
in a tree based data structure - say a filesystem


so user 'public' has the right to see

all contents in /foo/, but not in /bar/


$grants = \arc\grants::cd('foo')->switchUser('public')->setUserGrants('read');

$hasRead = $grants->cd('/foo/bar/')->check('read');
// -> true

$hasRead = $grants->cd('/bar/baz/')->check('read');
// -> false

The Code

    public function switchUser( $user, $groups = array() )
    {
        return new GrantsTree( $this->tree, $user, $groups );
    }

    public function setUserGrants($grants = null)
    {
        if ( isset( $grants ) ) {
            $this->tree->nodeValue['user.'.$this->user ] = ' ' . trim( $grants ) . ' ';
        } else {
            unset( $this->tree->nodeValue['user.'.$this->user ] );
        }
    }

    public function check($grant)
    {
        $grants = $this->fetchGrants();
        return ( strpos( $grants, ' '.$grant.' ')!==false );
    }

fetchGrants

private function fetchGrants()
{
return (string) \arc\tree::dive(
$this->tree,
function ($node) {
if ( isset( $node->nodeValue['user.'.$this->user] ) ) {
return $node->nodeValue['user.'.$this->user];
}
}
);
}


Ok, I lied


in reality there is a bit more code
the grants component allows you to specify grants like this:


  • '=read' : only allow read access on this node, not the children
  • '>read' : only allow read access on child nodes


In addition, it supports groups.




Still:

  • /arc/grants.php
    53 lines, 4 methods
  • /arc/grants/GrantsTree.php
    121 lines, 8 methods

More Important:


  • Almost no control structures:
    if, while, for, foreach

  • Very low cyclomatic complexity

  • Very easy to unit-test

EXAMPLES

xml writer

    function menu( $list ) {
        return \arc\xml::ul( 
            [ 'class' => 'menu' ], 
            array_map( function( $child ) {
                return \arc\xml::li( 
                    \arc\xml::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 );

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.

Caching HTTP Client

$client = \arc\http::client();

$proxy = \arc\cache::proxy( 
    \arc\http::client(),
    function($params) {
        $headers = $params['target']->responseHeaders;
        $cacheTime = getCacheTime( $headers ); // left as an exercise for the reader
        return $cacheTime;
    }
);

STATE of arc

still somewhat experimental


1.* focuses on small and clean code

not optimized for speed for now


ease of use also important

state of arc

  • arc/base 90%
  • arc/cache 90%
  • arc/xml 60%
       parser needs ArrayAccess and DomElement methods
  • arc/web 50%
  •    needs html writer and parser and http utility functions
  • arc/events 90%
  • arc/grants 80%
       misses owner grants and must be more extendable
  • arc/config 80%

  • documentation... 10%?


state of ARC


http://ariadne-cms.github.io/arc/

https://packagist.org/packages/arc/arc


http://github.com/ariadne-cms/arc-*/



http://slides.com/poefke/arc/

ARC - modern php components

By Auke van Slooten

ARC - modern php components

ARC is a set of modern php components geared for web applications with hierarchical (tree-based) data. ARC uses DI, composition over inheritance, duck-typing and favours small and simple code size over features.

  • 1,673