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 '/' charactersdata 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 dataarc/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,777