ramblings on PHP, SQL, the web, politics, ultimate frisbee and what else is on in my life
back

Un-informed.org beta launched

In the past few weeks I have been working like a mad man on putting together a frontend in time for the UN-connected.org conference. I have joined the Un-informed.org team as the sole IT guy some time in September last year. Since then I have posted to find people to help me, so far only Dennis Riedel has come through, well and of course Liip who have donated hosting and several days of development time, without which the site would not have been possible. Stefan Sicher also stepped up on short notice to make the site look good. But without a full stack framework like symfony it would also not have been possible. The source code for the application is available online for anyone to see.

We rushed things, so I am sure there are plenty of things to tweak and I am using sfContext all over the place and there are no unit or functional tests. Patches welcome. But what is there is quite mind blowing given that I guess I spend less than 12 man days on the frontend. Of course it helped a lot that there were plugins like sfSolrPlugin, vjCommentPlugin (needed a bit of tweaking), sfDoctrineGuardPlugin/sfDoctrineApplyPlugin. Quite a few more days of course went into the database schema, the admintools (mainly the excel importer to fill the database with content about UN resolutions). Here I am very happy with how the sfFormExtraPlugin and sfAdminDashPlugin made the admintools eye-pleasing and easy to navigate. There are of course tons more ideas (list will grow once all the notes from the conference are reviewed) of what to implement as well, and its never to late to join :-)

Just briefly about two aspects that really helped a lot to get this going. For one using Doctrine model classes it because quite easy to quickly denormalize a few aspects. For example I wanted to show a history of clauses (sections within a UN resolution). For this I needed a linear history (well in reality its not linear, but since most of the data we currently had as linear we just went with this in order to keep the UI manageable given the short time we had). So both in the document and clauses I wanted to store a common ID which I could use to grab the entire linear history with a single query. Dennis implemented the excel importer and I implemented the frontend. The cool thing was that I was adding stuff to the model I needed for the frontend and Dennis didn't need to even worry about it, since by using the model classes his importer would automatically execute the logic I added:


<?php
    public function setUp()
    {
        parent::setUp();
        $this->hasAccessor('root_document_id', 'getRootDocumentId');
    }

    public function getRootDocumentId() {
        $root_document_id = $this->_get('root_document_id');
        return empty($root_document_id) ? $this->_get('id') : $root_document_id;
    }

    public function preSave($event) {
        $invoker = $event->getInvoker();

        $root_document_id = $invoker->_get('root_document_id');
        $parent_document_id = $invoker->_get('parent_document_id');
        if (!empty($parent_document_id) && empty($root_document_id)) {
            $root_document_id = $invoker->DocumentParent->root_document_id;
            $invoker->set('root_document_id', $root_document_id);
        }
    }
?>

Now the other thing that was a huge help was sfSolrPlugin. I have experience with Solr, and I was very hesitant if a plugin could actually help at all, but sure enough this plugin got me running quickly and made a lot of steps a lot easier. For example I can just define my indexes via a yaml config file my leveraging my existing model classes. Here I just reference my model methods, the data types etc. With this config file it will generate a separate Solr core config with all the necessary Solr specific config files. Furthermore it adds command to the symfony CLI to start/stop Solr, update the configs and (re-)index Solr from the database. There is even a listener you can add to your doctrine schema that will automatically keep the Solr index uptodate if there are any changes being made to the given model class instances. The API is also easy enough so that I quickly got a facetted search going.

Comments



Re: Un-informed.org beta launched

Another cool thing I added yesterday. We needed some relations to always be bi-directional. Now I could have done two things here: 1) I could have made a method in the model that when reading the relations, also reads the reverse direction, to make sure that I can pass everything to the template or 2) I could simply hook into the save() and delete() process to add/remove the other direction. I decided to go with 2). Note I do not anticipate that we will be updating those models, so I do not need to really handle that case.


<?php
    protected $post_delete_data;

    public function postSave($event) {
        $invoker = $event->getInvoker();
        if ($invoker->_get('type') === 'closely related') {
            $reverse = Doctrine_Query::create()
                ->from('DocumentDocumentRelation')
                ->where('document_id = ? AND related_document_id = ?', array($invoker->_get('related_document_id'), $invoker->_get('document_id')))
                ->fetchOne();
            if (!$reverse) {
                $reverse = new DocumentDocumentRelation();
                $reverse->set('document_id', $invoker->_get('related_document_id'));
                $reverse->set('related_document_id', $invoker->_get('document_id'));
                $reverse->set('type', $invoker->_get('type'));
                $reverse->save();
            }
        }
    }

    public function preDelete($event) {
        $invoker = $event->getInvoker();
        if ($invoker->_get('type') === 'closely related') {
            $this->post_delete_data['document_id'] = $invoker->_get('related_document_id');
            $this->post_delete_data['related_document_id'] = $invoker->_get('document_id');
            $this->post_delete_data['type'] = $invoker->_get('type');
        }
    }

    public function postDelete($event) {
        if (!empty($this->post_delete_data)) {
            $reverse = Doctrine_Query::create()
                ->from('DocumentDocumentRelation')
                ->where('document_id = ? AND related_document_id = ? AND type = ?', array($this->post_delete_data['document_id'], $this->post_delete_data['related_document_id'] , $this->post_delete_data['type']))
                ->fetchOne();
            if ($reverse) {
                $reverse->delete();
            }
        }
        unset($this->post_delete_data);
    }
?>

Re: Un-informed.org beta launched

Just deployed another tweak to the solr facetting. I now display the count for the given facet now without the facet filter applied and now additionally with the facet filter applied if that count is different.