Creating a schematic CMS from scratch with only PHPCR

In just 3 hours...

About us

Willem-Jan Zijderveld

  • developer @
  • @willemjanz

Daniel Leech

  • senior developer @massiveart
  • @dantleech

What to expect

  • Introduction on PHPCR
    • History
    • Basic idea
    • Schemaless structure
  • The workshop
      1. Using PHPCR-Shell to create content
      2. Show some of the content
    1. Mixins and handling files
    2. Using referencing to createa menu
    3. Implement versioning
    4. Playing around

History of PHPCR

A port from JSR-283
and JSR-333

TYPO3 started early 2008

Basic idea

PHP Content Repository

A content repository is a store of digital content with an associated set of data management, search and access methods allowing application-independent access to the content with the ability to store and modify content in addition to searching and retrieving.

Or: A bunch of data that is easy accessible

A set of interfaces

No implementation

Defines a API developers can write code against

Implementations can be tested against the API tests

Storage independent

Comes with a set of utilities

  • SQL2
  • CND
  • Console commands
  • Various helpers (f.e. UUID)

Hierarchical navigation structures

A big tree of nodes

Compound data

For example: a webpage with images or documents as child nodes


Structure with NodeTypes

  • nt:file
  • mix:versionable
  • nt:unstructured

Almost time to write code!

Any questions so far?

Jackalope with Jackrabbit

Jackalope is a PHPCR implementation

Jackrabbit is the storage

jackalope-jackrabbit is the transport layer

Where do we start?

Part 1

Goal: Creating your first content with PHPCR-Shell
and serving it based on the REQUEST_URI

Part 1

Starting Jackrabbit and playing with PHPCR-shell

$ cd /var/www/html/workshops/phpcr
$ ./util/ start
# Wait for it....
$ phpcrsh -t jackrabbit

Part 1

Basic commands to remember

  • node:list
  • node:create
  • node:property:set
  • file:import
  • session:save !!

aliases shows you a useful list with shortcuts

list shows all available commands

Create some content, getting familiar with nodes and with PHPCR-Shell

  • /cms
    node:create /cms
    • /homepage
      • title
        node:property:set /cms/homepage/title <title>
      • content
    • /press
      • title
      • content
      • /media (also with title + media)
        • /image1 - files/anvil.gif
          file:import /cms/press/media/image1 files/anvil.gif
        • /image2 - files/explosive-tennisballs.jpg
    • /policy.pdf - policy.pdf

Don't forget to session:save!

Part 1.2

Bootstrapping your application

Documentroot is set at /var/www/html/workshops/phpcr/cms/web

$factory = new Jackalope\RepositoryFactoryJackrabbit();
$repository = $factory->getRepository(array(
  'jackalope.jackrabbit_uri' => 'http://localhost:8080/server'
$credentials = new \PHPCR\SimpleCredentials('admin', 'admin');
$session = $repository->login($credentials, 'default');

Disclaimer: We are not here to write pretty code :-)

Part 1.2

Retrieving a node based on the path of a node


$rootNode = $session->getRootNode(); // Jackalope\Node
$cmsNode = $rootNode->getNode('cms'); // Jackalope\Node
$currentNode = $cmsNode->getNode($path); // Jackalope\Node
$cmsNode->hasNode($path); // true / false
$currentNode->isNodeType('nt:file'); // true / false

Make sure to handle unexisting nodes

Part 2

Goal: Creating and using mixins and access files from our browser


Stuck on part 1? Run git checkout -f part2 && ./util/ part2

Creating a mixin with CND

  • Name: mix:simple_page
  • Required properties
    • title
    • content
$ export EDITOR=<vim|nano>
PHPCRSH > node-type:edit mix:simple_page
<mix = ''>
[mix:simple_page] > nt:unstructured
orderable query mixin
- title (string)
- content (string)

Add the mixin to our nodes

PHPCRSH > node:mixin:add /cms/homepage mix:simple_page

When you haven't created title and content properties yet, you will get an error when saving the session

Updating your code

All nodes with mix:simple_page now
have a title and content property


Serving files

$resource = $node->getNode('jcr:resource');
$content = $resource->getProperty('jcr:data')->getString();
$mimeType = $resource->getPropertyValue('jcr:mimeType');
header('Content-Type: ' . $mimeType);
echo $content;


Part 3

Goal: Using references to create a custom URL and create a menu

  • Add the mixin mix:referenceable to /cms/press/media
  • Create a new node under the root: /routes
  • Add a node /routes/gallery
  • Add property "node" to /routes/gallery.
    Value: UUID of /cms/press/media.
    Type: reference
    PHPCRSH > node:property:set --type=reference /routes/gallery/node <UUID>

Resolving the node

$routesNode = $rootNode->getNode('routes');
// ..
if ($routesNode->hasNode($path)) {
  $route = $routesNode->getNode($path);
  $node = $route->getPropertyValue('node'); // Jackalope\Node
} elseif ($cmsNode->hasNode($path))  {
  // .. load node from $cmsNode

Creating a menu

Create a new mixin: mix:menuItem

node-type:edit mix:menuItem
<mix = ''>
[mix:menuItem] > nt:unstructured
orderable query mixin
- label (string)
- path (string)
+ nt:unstructured (mix:menuItem)
$menuNode = $rootNode->getNode('menu');
function renderMenu($menuNode) {
  foreach ($menuNode as $childNode) {
    $path = $childeNode->getPropertyValue('path');
    $label = $childNode->getPropertyValue('label');
    echo '<a href="' . $path .'">' . $label . '</a>';
    if ($childNode->hasNodes()) renderMenu($childNode);

Part 4

Goal: Implement versioning and switch between versions with a dropdown

Save a version

  • Add the mixin mix:versionable to the nodes you want to version
  • session:save
  • version:checkout /cms/homepage
  • Change the title/content of your node
  • session:save
  • version:checkin /cms/homepage

Retrieving the versions

// Get the version manager
$versionManager = $session->getWorkspace()
// retrieve version history for a node
$versionHistory = $versionManager
// Retrieve the latest version
$currentVersion = $versionManager
// retrieve all versions
$versions = $versionHistory->getAllVersions();
// retrieve a specific version
$specificVersion = $versionHistory->getVersion('1.0');
// Retrieve the node of the version
$node = $version->getFrozenNode();

Switch between versions

<form method="GET">
 <select name="version" onchange="this.form.submit();">
 <?php foreach ($versions as $version): ?>
  <option value="<?php echo $version->getName(); ?>">
   <?php echo $version->getName(); ?>

Everybody still awake?

  • Maybe try to implement a simple admin?
  • Or just play around

That's all folks!

Don't forget to vote to increase your chances at the raffle!