PHP Content Repository Specification

phpcr.github.com

Data in a CMS is mostly unstructured

RDBMS are not a good fit, hurray for NoSQL

like fitting a square into a circle

CMS often organize content as a tree/graph

Most NoSQL not a good fit, hurray for Graph DBs

content graph

CMS should be able to store content versions

multiple versions

Complexity shouldn't overwhelm developers

Need a solution that can scale both from small to large projects and we rather not reinvent the wheel!

Enter PHPCR


PHPCR provides a standardized API that can be used by any PHP content management system to interface with any content repository.

About PHPCR


PHPCR implementations

PHPCR Features


(*) Defined by the API but not yet implemented for PHP

PHPCR concepts

Hierarchical document store

Workspaces

Nodes

Properties

Primary Node Types

Mixin Node Types

Mixin Node Type Examples

PHPCR

PHPCR class interaction diagramm Source: phpcr.github.com

PHPCR code examples

Connecting via PHPCR

                // start of implementation specific configuration //
                $factory = "\Jackalope\RepositoryFactoryJackrabbit";
                $parameters = array(
                    'jackalope.jackrabbit_uri'
                        => 'http://localhost:8080/server'
                );
                // end of implementation specific configuration //

                $repository = $factory::getRepository($parameters);
                $creds = new \PHPCR\SimpleCredentials('user','pw');
                $session = $repository->login($creds, 'default');
            

CRUD operations

                $root = $session->getRootNode();

                // Node always added as child of another node
                $node = $root->addNode('test', 'nt:unstructured');

                // Create or update a property
                $node->setProperty('prop', 'value');

                // Persist the changes
                $session->save();

                // Delete a node and all its children
                $node->remove();
                $session->save();
            

Tree API

                $node = $session->getNode('/site/content');

                foreach ($node->getNodes() as $child) {
                    var_dump($child->getName());
                }

                // or in short
                foreach ($node as $child) {
                    var_dump($child->getName());
                }

                // filter on node names
                foreach ($node->getNodes('di*') as $child) {
                    var_dump($child->getName());
                }
            

Versioning API

                // make versionable
                $node = $session->getNode('/site/content/about');
                $node->addMixin('mix:versionable');
                $session->save();

                $vm = $session->getWorkspace()->getVersionManager();

                // create initial version
                $node->setProperty('title', 'About');
                $session->save();
                // check-in (create version) and check-out (prepare for further updates)
                // this is persisted immediately without a save() call
                $vm->checkpoint($node->getPath());
            

Versioning API

                // update node with some changes
                $node->setProperty('title', 'Ups');
                $session->save();

                // create another version, leave in read only state
                $vm->checkin($node->getPath());

                $base = $vm->getBaseVersion($node->getPath());
                $current = $base->getLinearPredecessor();
                $previous = $current->getLinearPredecessor();

                // get snapshot of old version to look around
                $frozenNode = $previous->getFrozenNode();
                // the frozen node has the same fields and values as
                // the node had at that time.
                echo $frozenNode->getProperty('title'); // About

                // set the live data back to what is in this version
                $vm->restore(true, $previous);

                $node = $session->getNode('/site/content/about');
                echo $node->getProperty('title'); // About
            

Search via SQL2 API

                $qm = $workspace->getQueryManager();

                // unlike SQL, in SQL2 "*" does not return all columns
                // but at least the path and match score
                $sql = "SELECT * FROM [nt:unstructured]
                    WHERE [nt:unstructured].type = 'nav'
                    AND ISDESCENDANTNODE('/some/path')
                    ORDER BY score, [nt:unstructured].title";
                $query = $qm->createQuery($sql, 'JCR-SQL2');
                $query->setLimit($limit);
                $query->setOffset($offset);
                $queryResult = $query->execute();

                foreach ($queryResult->getNodes() as $node) {
                    var_dump($node->getPath());
                }
            

Search via QOM API

                $qm = $workspace->getQueryManager();
                $factory = $qm->getQOMFactory();

                // SELECT * FROM nt:file INNER JOIN nt:folder ON ISCHILDNODE(child, parent)
                $factory->createQuery(
                    $factory->join(
                        $factory->selector('nt:file'),
                        $factory->selector('nt:folder'),
                        Constants::JCR_JOIN_TYPE_INNER,
                        $factory->childNodeJoinCondition('child', 'parent')),
                    null,
                    array(),
                    array());
            

Search via Fluent Query API

(With phpcr-utils)

                $qm = $workspace->getQueryManager();
                $factory = $qm->getQOMFactory();

                // SELECT * FROM nt:unstructured WHERE name NOT IS NULL
                $qb = new QueryBuilder($factory);
                $qb->select($factory->selector('nt:unstructured'))
                   ->where($factory->propertyExistence('name'))
                   ->setFirstResult(10)
                   ->setMaxResults(10)
                   ->execute();
            

Quality

A test suite for PHPCR makes sure all implementations interpret the specification the same way.

david@wintermute:~/liip/jackalope> phpunit -c tests/
PHPUnit 3.5.14 by Sebastian Bergmann.

S..II..................................SSSSSSSSSSSSSSSS........  63 / 813 (  7%)
...............................S......S.................SSSSSSS 126 / 813 ( 15%)
...SS.S.......SSS.........S...S......................SSS....... 189 / 813 ( 23%)
......I.I.I.I.................................................. 252 / 813 ( 30%)
......S..SS..SSS..S............................................ 315 / 813 ( 38%)
.............S..................I...............I..I........SI. 378 / 813 ( 46%)
.................S.......I....................S................ 441 / 813 ( 54%)
............................................................... 504 / 813 ( 61%)
..SSSS....I..S...........................I....................S 567 / 813 ( 69%)
............................................................... 630 / 813 ( 77%)
............................................................... 693 / 813 ( 85%)
............................................................... 756 / 813 ( 92%)
.................I.....I......S.......S..........SSS...

Time: 01:19, Memory: 74.25Mb

OK, but incomplete or skipped tests!
Tests: 808, Assertions: 5598, Incomplete: 15, Skipped: 59.
            

Doctrine PHPCR ODM

Conclusions

Not all data fits well in PHPCR/JCR

Door swings both ways, so remember

like fitting a square into a circle

Play with it today!

PHPCR Tutorial


See it in action!

Symfony2 CMF sandbox

Next steps

Many individuals contribute to the effort


  • beberlei (Benjamin Eberlei)
  • bergie (Henri Bergius)
  • brki (Brian King)
  • chirimoya (Thomas Schedler)
  • chregu (Christian Stocker)
  • dbu (David Buchmann)
  • ebi (Tobias Ebnöther)
  • jakuza (Jacopo Romei)
  • justinrainbow (Justin Rainbow)
  • k-fish (Karsten Dambekalns)
  • lapistano (Bastian Feder)
  • lsmith77 (Lukas K. Smith)
  • micheleorselli (Michele Orselli)
  • nacmartin (Nacho Martín)
  • nicam (Pascal Helfenstein)
  • ornicar (Thibault Duplessis)
  • piotras
  • robertlemke (Robert Lemke)
  • rndstr (Roland Schilter)
  • Seldaek (Jordi Boggiano)
  • sixty-nine (Daniel Barsotti)
  • uwej711 (Uwe Jäger)
  • vedranzgela (Vedran Zgela)

Several companies and organisations are investing into the effort

Liip, Ideato, Nemein, IKS

Many projects have expressed interest

Symfony2 CMF, Midgard, Typo3, Nooku, ez Publish, Drupal

Github projects

Resources