PHP Content Repository

Porting a Java standard to PHP

Content Repository

The JCR model

Hierarchical document store

Repository Content

<jcr:root>
  <cms>
    <pages>
      <home title="Hello">
        <block title="News"
          content="Today: PHPCR presentation"/>
      </home>
      <contact title="Contact"
        content="phpcr-users@groups.google.com"/>
    </pages>
  </cms>
</jcr:root>

PHPCR class interaction diagrammSource: phpcr.github.com

Nodes

Properties

Primary Node Types

Mixin Node Types

JCR / PHPCR Features


* Not yet implemented in any PHP implementation

From Java to PHP

PHP = Java

PHP != Java

Simplifications

Conclusions

Not all data fits well in PHPCR/JCR

like fitting a square into a circle

Quality

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

Quality

Test results using Jackalope with the Jackrabbit backend.

......................................S.........S.....II.....   61 / 1222 (  4%)
...........S.S...............................................  122 / 1222 (  9%)
...........SS.......SS.S.......S.............................  183 / 1222 ( 14%)
.............................................................  244 / 1222 ( 19%)
.............................................................  305 / 1222 ( 24%)
.............................................................  366 / 1222 ( 29%)
.............................................................  427 / 1222 ( 34%)
..............................S.........SSS..............I.I.  488 / 1222 ( 39%)
I.I.........................I...................I..I........S  549 / 1222 ( 44%)
I............................................................  610 / 1222 ( 49%)
.....S.......I.....................S..S......................  671 / 1222 ( 54%)
.................SS...............SSSS..S....................  732 / 1222 ( 59%)
.......................................................SSSSS.  793 / 1222 ( 64%)
..........................S.............I......S...........I.  854 / 1222 ( 69%)
.............................SS..................S...........  915 / 1222 ( 74%)
.............................................................  976 / 1222 ( 79%)
............................................................. 1037 / 1222 ( 84%)
...............................S............................. 1098 / 1222 ( 89%)
...........SSSS...........SSS................................ 1159 / 1222 ( 94%)
........................................SSSSSS............... 1220 / 1222 ( 99%)
.

Time: 03:23, Memory: 93.50Mb

OK, but incomplete or skipped tests!
Tests: 1212, Assertions: 6797, Incomplete: 17, Skipped: 49.
            

Play with it today!

PHPCR Tutorial



See it in action!

Symfony2 CMF sandbox

PHPCR in real live

Many individuals contribute to the effort


  • 0x616469 (Adrian Schlegel)
  • adou600 (Adrien Nicolet)
  • beberlei (Benjamin Eberlei)
  • bergie (Henri Bergius)
  • bmatzner (Bernd Matzner)
  • brki (Brian King)
  • chirimoya (Thomas Schedler)
  • chregu (Christian Stocker)
  • cordoval (Luis Cordova)
  • craigmarvelley (Craig Marvelley)
  • cryptocompress
  • damz (Damien Tournoud)
  • dbojdo (Daniel Bojdo)
  • dbu (David Buchmann)
  • dotZoki (Zoran)
  • ebi (Tobias Ebnöther)
  • fabian (Fabian Vogler)
  • flojon (Jonas Flodén)
  • iambrosi (Ismael Ambrosi)
  • jakuza (Jacopo Romei)
  • justinrainbow (Justin Rainbow)
  • k-fish/kdambekalns (Karsten Dambekalns)
  • krizon (Kristian Zondervan)
  • lapistano (Bastian Feder)
  • lsmith77 (Lukas K. Smith)
  • mdekrijger
  • micheleorselli (Michele Orselli)
  • nacmartin (Nacho Martín)
  • nicam (Pascal Helfenstein)
  • Ocramius (Marco Pivetta)
  • ornicar (Thibault Duplessis)
  • pajooh (arash)
  • petesiss (Pete Sisson)
  • piotras
  • pitpit (Damien Pitard)
  • rande (Thomas Rabaix)
  • richardmiller (Richard Miller)
  • rndstr (Roland Schilter)
  • robertlemke (Robert Lemke)
  • sebastien-roch (Sébastien Roch)
  • Seldaek (Jordi Boggiano)
  • simensen (Beau Simensen)
  • sixty-nine (Daniel Barsotti)
  • sjopet (Sjoerd Peters)
  • starkj (Johannes Stark)
  • stof (Christophe Coevoet)
  • uwej711 (Uwe Jäger)
  • vedranzgela (Vedran Zgela)
  • videlalvaro (Alvaro Videla)
  • ...

Doctrine PHPCR ODM

Thank you

Please give me feedback:

@dbu on Twitter, or david@liip.ch

Github projects

Resources

PHPCR code examples

Creating a session

use PHPCR\SimpleCredentials;

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

$creds = new SimpleCredentials('admin','admin');
$session = $repository->login($creds, 'default');
            

CRUD operations

$root = $session->getRootNode();

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

// New node is immediately available to this session
$node = $session->getNode('/test');

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

// New node is now available for all sessions
$session->save();

// Delete the node and all its children
$node->remove();

// Fails if a concurrent session also changed '/test'
$session->save();
            

Tree Traversal 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();
// create initial version
$node->setProperty('title', 'About');
$session->save();

// check-in (create version)
// and check-out (prepare for further updates)
// persisted immediately without a save() call
$vm = $session->getWorkspace()->getVersionManager();
$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();
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
// LIMIT 10 OFFSET 10
$qb = new QueryBuilder($factory);
$qb->select($factory->selector('nt:unstructured'))
   ->where($factory->propertyExistence('name'))
   ->setFirstResult(10)
   ->setMaxResults(10)
   ->execute();
            

Concurrent write operations

$test = $session->getNode('/test');
$test1 = $session1->getNode('/test');

$test->remove();
$test1->setProperty('prop', 'value');

$session->save();

// this will fail as the node is gone
$session1->save();
            

Locking

// prepare
$test = $session->getNode('/test');
$test->addMixin('mix:lockable');
$session->save();

// use
$lm = $session->getWorkspace()->getLockManager();
// lock node and children for 5 seconds
$lock = $lm->lock('/test', true, true, 5);

$test1 = $session1->getNode('/test');

// here you will get an exception that node is locked
$test1->setProperty('prop', 'value');