Getting started

Let’s begin with trivial blog posts in SQLite database. This tutorial will get you started with a basic CRUD entity and a minimal Smalldb setup. Keep in mind the Smalldb framework implements only a model layer in an MVC application, so after this introduction, you will likely wish to use your favorite framework to implement everything else.

Setup

Step 1: Composer.json

For the start we will need two components of the Smalldb Framework: libsmalldb and Flupdo. The libsmalldb is the heart of the framework, which implements the statemachines infrastructure. Flupdo is simple SQL query builder used by the libsmalldb.

Create the following composer.json and run composer update. It should download libsmalldb and its dependencies. Flupdo is an optional dependency which we will need to access a database.

{
    "name": "smalldb/hello-world",
    "description": "Hello world example",
    "require": {
        "smalldb/libsmalldb": ">=0.5",
        "smalldb/flupdo": "*"
    }
}

Now all libraries and Composer’s autoloader should be in place.

Symfony Bundle (optional)

You may also use Symfony bundle to integrate Smalldb into your Symfony application. See bundle documentation for details.

Step 2: Database setup

To keep this tutorial as simple as possible, we will use trivial SQLite database. However, with few adjustments, this will work on MySQL too.

For now we will create a simple blogpost table in a new data/database.sqlite file. Run mkdir data, sqlite3 data/database.sqlite and copy–paste the following SQL query (then type .quit to terminate the SQLite client):

-- SQLite syntax
CREATE TABLE blogpost (
    id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
    title TEXT NOT NULL UNIQUE,
    publishTime INTEGER NOT NULL, -- MySQL: use TIMESTAMP
    isDeleted INTEGER NOT NULL DEFAULT 0
);

Make sure both the data directory and data/database.sqlite are writable by your HTTP server.

Now we should have a place to store our blog posts.

Step 3: Initialize Smalldb backend

Smalldb backend is a starting point of Smalldb API. It acts as both a factory and a proxy, and it maintains context resources like a database connection.

We will use JsonDirBackend class which load state machine definitions from a single directory of JSON files. So create the directory:

mkdir statemachine

Also, the Smalldb backend needs a Flupdo instance to access the SQLite database. Flupdo is a query builder built on top of PDO.

Create the following index.php:

<?php
require __DIR__.'/vendor/autoload.php';

// Make errors visible in this sandbox
ini_set('display_errors', true);

// Initialize Flupdo, parameters are the same as for PDO
$flupdo = new \Smalldb\Flupdo\Flupdo('sqlite:data/database.sqlite',
	null, null, [ \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION ]);

// Initialize Smalldb backend
$smalldb = new \Smalldb\StateMachine\JsonDirBackend(
	[ 'base_dir' => __DIR__.'/statemachine' ],
	[ 'database' => $flupdo, 'auth' => new \Smalldb\StateMachine\Auth\AllowAllAuth() ],
	'smalldb');

The AllowAllAuth class tells individual state machines that everything is allowed for everyone. We will return to this on Security page.

Running the index.php now should produce no output and throw no exceptions.

Now we should have Smalldb backend ready.

Step 4: Define the blog post state machine

The Smalldb represents each entity using a state machine. It may sound like overkill for trivial entities, but it is very useful when an entity grows complicated, and it is better to have unified API for all entities. For the trivial entities, the Smalldb has FlupdoCrudMachine class, which implements common CRUD operations, so the definition of such entity is trivial as well.

Create the following statemachine/blogpost.json file to define the blog post state machine:

{
    "class": "\\Smalldb\\StateMachine\\FlupdoCrudMachine",
    "table": "blogpost"
}

Now we should have the state machine defined and ready for use.

Usage

Step 5: Render state diagrams

Smalldb can visualize defined state machines using Graphviz. Smalldb machine generates description of the diagram and Graphviz render it to a picture.

State diagram of the blog post state machine is defined within the FlupdoCrudMachine class. To obtain the state diagram, add following line to index.php:

<?php
// Initialize as before
// ...

echo $smalldb->getMachine('blogpost')->exportDot();

Now the index.php should generate state diagram in Graphviz Dot syntax. Use php index.php | dot -Tsvg -o blogpost.svg to render it to SVG file (you can get PNG or PDF too):

blog post state diagram

Use the following PHP code to make visualization automatic for all defined state machines. It is useful to keep this aside of your experiments to see whether the state machine definitions are correct.

<?php
// Initialize as before
// ...

// Minimal HTML5 document
echo "<!doctype html><meta charset=utf-8><title>Smalldb</title>\n";

// Render all state machines
foreach ($smalldb->getKnownTypes() as $m) {
	$dot_file = __DIR__.'/statemachine/'.$m.'.dot';
	$img_file = __DIR__.'/statemachine/'.$m.'.svg';
	$img_url  = 'statemachine/'.$m.'.svg';

	// Get state machine implementation
	$machine = $smalldb->getMachine($m);

	// Render state diagram if not up-to-date
	if (!file_exists($img_file)
		|| filemtime($img_file) < $machine->getMachineImplementationMTime())
	{
		file_put_contents($dot_file, $machine->exportDot());
		exec(sprintf('dot -Tsvg %s -o %s',
			escapeshellarg($dot_file), escapeshellarg($img_file)));
	}

	// Show diagram in HTML page
	echo "<h2>", htmlspecialchars($m), "</h2>\n";
	echo "<img src=\"", htmlspecialchars($img_url), "\">\n";
}

This code creates *.dot and *.svg files next to your JSON definitions in the statemachine directory. Make sure your HTTP server can write to the statemachine directory and dot is installed in $PATH.

Note: Smalldb-REST contains ready-to-use state diagram renderer.

Tip: Smalldb has a self check mechanism to quickly verify basic aspects of defined state machines.

Step 6: Reference

Smalldb provides Reference objects to manipulate the state machines. The Reference should be understood like a pointer to a state machine rather than an instance of the state machine, because the Smalldb state machines are abstract entities which live independently of your running application.

The Reference is similar to active record concept (but the semantics is different) and provides syntactic sugar for comfortable use.

To identify state machines a application-wide global ID is used. It is typically composed of state machine type and primary key of relevant table. In case the primary key is not known in advance (typically when using autoincrement ID), Smalldb has concept of null reference. Such reference points to a random state machine of given type which is in non-existing state. Once the state machine leaves the non-existent state, ID is automatically updated.

The Reference API is rather simple. A method call is converted to transition invocation. Array access is used to read properties of the machine. Reading member variables is used to access ID, state, machine type and other informations.

See the following example:

<?php
// Initialize as before
// ...

// Show state of the referenced state machine
function print_ref($ref) {
	printf("<p>ID = %s, state = '%s'</p>\n", $ref->id, $ref->state);
	if ($ref->state) {
		var_dump($ref->properties);
	}
}

// Get null reference to a blogpost
$ref = $smalldb->ref('blogpost');
print_ref($ref);

// Create the blog post
$ref->create([ 'title' => 'Hello', 'publishTime' => strftime('%Y-%m-%d') ]);
print_ref($ref);

// Edit the blogpost
$ref->edit(['title' => 'Hello world!']);
print_ref($ref);

// Delete the blogpost
$ref->delete();
print_ref($ref);

It should produce output similar to this:

ID = , state = ''

ID = 38, state = 'exists'

array (size=5)
  'id' => string '38' (length=2)
  'title' => string 'Hello' (length=5)
  'publishTime' => string '2016-07-26' (length=10)
  'isDeleted' => string '0' (length=1)
  'state' => string 'exists' (length=6)

ID = 38, state = 'exists'

array (size=5)
  'id' => string '38' (length=2)
  'title' => string 'Hello world!' (length=12)
  'publishTime' => string '2016-07-26' (length=10)
  'isDeleted' => string '0' (length=1)
  'state' => string 'exists' (length=6)

ID = 38, state = ''

Step 7: Listing

Smalldb provides Listing API to see what state machines exist. The listing API takes “filters” and provides list of state machine references.

<?php
// Initialize as before
// ...

// Populate database
try {
	$smalldb->ref('blogpost')->create(['title' => 'First',  'publishTime' => '2016-01-01']);
	$smalldb->ref('blogpost')->create(['title' => 'Second', 'publishTime' => '2016-02-02']);
	$smalldb->ref('blogpost')->create(['title' => 'Third',  'publishTime' => '2016-03-03']);
}
catch (\Smalldb\Flupdo\FlupdoException $ex) {
	// Ignore duplicates
	echo "<p>", htmlspecialchars($ex->getMessage()), "</p>\n";
}

// Create listing
$list = $smalldb->createListing(['type' => 'blogpost', 'publishTime<' => '2016-02-15']);

// Get listing items
$items = $list->fetchAll();

// Render the list
echo "<ul>\n";
foreach ($items as $ref) {
	echo "<li>", htmlspecialchars($ref['title']), "</li>";
}
echo "</ul>\n";

Note that the third blog post is missing from the list, because of the publishTime< filter, which requires publishTime to be less than 2016-02-15. See FlupdoGenericListing class for more details on pre-defined filters.

Also note ArrayAcces interface (the [ ]) to access state the machine properties (title in this case) — see Reference.


See also …