OrientDB ODM beta 5: repositories, compatibility with 1.2.0 and more stability

It has almost been a baby’s delivery, but we eventually made it: the PHP ODM for OrientDB has finally reached its 5th beta.

Thanks to the huge effort of David Funaro and the push from Marco Pivetta we have just released the beta-5 version of this library, which lets you work with the infamous GraphDB in PHP: there is a plethora of changes and some news about the future of the library, so I’ll try to recap a bit what we’ve done so far in almost one year of active development.

Composer

The entire library (query builder, HTTP binding and ODM) is now composerified (have a look at the dependencies): this was an important step since we wanted to completely get rid of git submodules and embrace this new and – sorry PEAR – finally decent packaging system for PHP.

Symfony2: gimme MOAR

We hate the NIH approach, so whenever there is a library which is tested, decoupled and does what we need, we tend to use it instead of rewriting from scratch some new userland code.

This has been the case for our filesystem classes, that – as they were first very simple but tended to grow – have now been replaced with the Symfony2 finder.

At the same time we also added the ClassLoader component, which replaces our old PSR-0 compatible basic autoloader.

Compatibility with the stable OrientDB 1.2.0

OrientDB is stable since months, we couldn’t release a version of our library without upgrading the compatibility to OrientDB (we were still at version 1.0-rc6): we are now compatible with OrientDB 1.2.0.

The move has been quite easy thanks to the test suite that we have built so far, but we are still probably missing a few features introduced in 1.1 and 1.2: as soon as we will go on with the library we will map what the OrientDB team has added to the DB – for example, functions.

Fetchplans integrated in the Manager

Fetchplans specify the way OrientDB should lazy-load records: we have now added support to them, meaning that if you dont want to lazy-load linked records (*:-1), the ODM is able to read the entire result from OrientDB and build linked records as PHP objects (or array of objects).

In the example, you see that $post->comments:

by just using the correct fetchplan:

1
2
3
4
5
<?php

$post = $this->manager->find('27:0', '*:-1');

var_dump($post->getComments(); // an array of objects, no lazy-loading

Repositories

We implemented the repository pattern – as Doctrine 2 does: you are now able to access virtual collections and retrieve records through them:

1
2
3
4
5
6
7
8
<?php

use Congow\Orient\ODM\Manager;

$manager    = new Manager(...);
$repository = $manager->getRepository('Users');

$user = $repository->find($id);

Doctrine persistence

Since one of our aims is to be as compatible as possible with Doctrine’s ODMs, we integrated the Persistence interfaces from Doctrine 2: most of the methods are not implemented yet (throw new Exception()), as actual persistence should come in beta-6/rc-1, but the good news is that when retrieving objects from the DB you can still use the same APIs that the Doctrine ODMs provide you.

Integration tests

This release was mainly delayed because of integration tests1: we promised a fully-tested hydration mechanisms (converting DB records in POPOs) for beta-5 and this has been, slowly, accomplished.

Repositories, hydration and data types are now covered by integration tests.

Refactoring proxies

The way we generate proxies is one of the most interesting parts of the library: with this release we changed the way we do it in order to provide a more flexible and straightforward mechanism for doing lazy-loading.

Usually when you retrieve a record in OrientDB you won’t have related records:

SELECT FROM Address LIMIT 1
1
2
3
4
5
6
7
8
9
10
11
12
13
{
  "result": [{
      "@type": "d",
      "@rid": "#19:0",
      "@version": 6,
      "@class": "Address",
      "type": "Residence",
      "street": "Piazza Navona, 1",
      "city": "#21:0",
      "nick": "Luca2"
    }
  ]
}

as you see, by default OrientDB doesn’t retrieve the related record (city), but provides a pointer to that record (the RID).

When you retrieve a record via the Manager class, the ODM doesn’t return you a POPO, but a proxy class that overrides the POPO, allowing lazy-loading.

Proxy classes, basically, just call parent methods, and if the parent method has something to return:

Code example to understand lazy-loading
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
<?php

# A proxy class is returned

namespace Congow\Orient\Proxy\test\Integration\Document;

class Address extends \test\Integration\Document\Address
{
      public function getCity() {
        $parent = parent::getCity();

        if (!is_null($parent)) {
            if ($parent instanceOf \Congow\Orient\ODM\Proxy\AbstractProxy) {
                return $parent();
            }

            return $parent;
        }
    }
    public function setCity($city) {
        $parent = parent::setCity($city);

        if (!is_null($parent)) {
            if ($parent instanceOf \Congow\Orient\ODM\Proxy\AbstractProxy) {
                return $parent();
            }

            return $parent;
        }
    }

}

# When calling $address->getCity(), we will actually
# call the __invoke() method of a Proxy object

namespace Congow\Orient\ODM;

use Congow\Orient\ODM\Mapper;
use Congow\Orient\ODM\Proxy\AbstractProxy;

class Proxy extends AbstractProxy
{
    protected $manager;
    protected $rid;
    protected $record;

    /**
     * Istantiates a new Proxy.
     *
     * @param Mapper $manager
     * @param string $rid
     */
    public function __construct(Manager $manager, $rid)
    {
        $this->manager = $manager;
        $this->rid = $rid;
    }

    /**
     * Returns the record loaded with the Mapper.
     *
     * @return object
     */
    public function __invoke()
    {
        if (!$this->record) {
            $this->record = $this->getManager()->find($this->getRid());
        }

        return $this->record;
    }
}

As you see, calling the __invoke() method of a proxied object will make the manager do an extra-query to retrieve the lazy-loaded record.

Support of sessions in the HTTP client

Daniele Alessandri took his time to add native support for cookies in the HTTP client which is used in the native HTTP binding: thanks to this we can decide whether to re-use an existing session while querying the DB.

Simplified requirements

We have now removed APC as a requirement for the library: since it was an easy fix we thought it makes sense not to force everyone to have APC installed everywhere2.

Contributions

I’ve been pretty busy over the last months, but the efforts of the already-mentioned Daniele and David have been huge to release beta-5: I virtually clap my hands for them, as they are the main reason behind all of this progress.

Tests and CI

As always, green tests: the build is handled by Travis-CI. Also that one is green.

Doctrine and beta-6

David is already working on refactoring the namespaces to ask the Doctrine team to integrate the library into their organization: as agreed months ago, there shouldn’t be a big problem in doing so.

As this will be the first ODM for a GraphDB, everyone is pretty excited about it:

After that, we will face the biggest challenges so far:

I’m pretty sure the next months will be productive, intense and full of changes, but I’d realy like to suggest you one thing before leaving you: use this library.

Even though the ODM is not finished yet, HTTP binding and Query Builder are already at a stable stage: the first one is already faster than the binary-protocol implementation, while the second one is a very convenient library to help you saving a lot of time when writing OrientDB’s SQL+.

Again, their level of maturity is pretty high, and we accept and review bugs/feature requests pretty fastly.

So, what? Now, there are no more excuses.

Notes
  1. We have a test suite that runs “on paper”, meaning that those are tests running based on the OrientDB documentation. Integration tests are done, instead, with a real OrientDB instance
  2. It is used to provide a basic caching layer for annotations

In the mood for some more reading?

...or check the archives.