Latest Articles

QR-Code generation in PHP, now with bacon flavor

Monday, April 1, 2013 0 comments

First of, this is not an April Fool's joke. I actually planed to make one this year, but eventually gave up. Anyway, as some of you who follow me on Twitter may know, I worked on a modern QR-Code library, known as BaconQrCode, the past few weeks. Eventually it is now fully working and the public API can be considered stable.

As a base for my implementation I had choosen the ZXing library library. After writing the first unit tests though I noted that their implementation of the Reed-Solomon codec performed rather bad in PHP, so I exchanged it with the much faster implementation by Phil Karn. As the rest of the library performed quite well and seemed much more logical, I choosed to stay with it instead of a different implementation like qrencode, which formed the base for PHP QR Code.

So far my complete implementation works, at least according to the unit tests and some personal testing. I'd like to encourage you to try it out and give me your feedback. I have implemented three different renderers so far, namely Bitmap (PNG), SVG and EPS. There is still a little bit of work to be done for me, like finishing the code-documentation, adding a few more unit tests and doing a few more PHP-specific optimizations, but all that work won't influence the public API anymore.

So, what are you waiting for? As always, you can find the library in the Bacon repository on GitHub or include it in your application via Packagist. If you are interested in more cool stuff, check out the website of of Bacon.

Oh and before I forget it. I may also add QR-Code decoding in the future. This would really be a feature you won't find in any other QR-Code library for PHP ;)

Sequel of the Internet Explorer hell

Saturday, December 15, 2012 1 comment

Many of you may remember the "good old days", when Windows XP came out which would let Joe Average be stuck with Internet Explorer 6 for a while, while finally giving him IE7, on which he'd be stuck for all eternity though, unless he'd upgrade to another browser like Firefox or Chrome. Surely, Joe Average may not know about these or be happy with this browser, which was very bad for us web developers. Eventually, the IE6 and IE7 were falling, and (depending on your business) we were able to drop support for those legacy browsers.

Everything seemed to be fine for the moment, until the prefix hell begun. But so far we were able to survive it, until recently browser vendors started to drop vendor prefixes on many key features (even IE9 came with a few unprefixed CSS3 features). Microsoft is also doing well with IE10, by implementing all those new CSS3 features without prefixes at all, following a recommendation from the CSS working group.

So, this basically sounds very exciting, but since we don't live in a perfect world, there's always some bad player; In this case it's Webkit. So far, less of the mostly used CSS3 features are available unprefixed in current, near future or farther future versions of Webkit, like background gradients, transitions and so on. But okay, we can live with this for now, it's just a single additional prefix, right?

Wrong! Do you remember the first paragraph, were I was talking about people with old operation systems and stock browsers? Well, the same thing is now repeating in the mobile world. For instance, the majority of Android users is stuck on Gingerbread (Android 2.3), which comes with a (relatively) very outdated stock browser, which even requires you to use an even older prefix with a different syntax for gradients. This forces us again to use a third version, which works completely different from the official and the newer webkit implementation.

Again, people could upgrade to a different browser (Firefox, Opera, …), but most simply don't care, because the browser works for them. What's the solution you may ask? Well, in my opinion, Google should handle the browser as any other app in the system and upgrade the Webkit base as often as they upgrade their desktop browser. Those are just my two cents, and I know that this action wouldn't change anything about the status quo, but could avoid the same problem in the future.

Slides for my internationalization talk

Wednesday, October 24, 2012 1 comment

I just uploaded my slides for my internationalization talk at ZendCon. Again, please rate the talk on joind.in if you attended.

http://stuff.dasprids.de/slides/zendcon2012/internationalization-in-zend-framework-2/presentation.html

Slides for my ZF2 router presentation

Tuesday, October 23, 2012 1 comment

I've just uploaded the slides for my ZF2 router presentation for those wo are interested in them. You can find them right here on my website:

http://stuff.dasprids.de/slides/zendcon2012/introducing-the-new-zend-framework-2-router/presentation.html

For those who attended, please don't forget to rate the talk on joind.in: https://joind.in/6872

If you are interested in internationalization, you may join my talk tomorrow about i18n in ZF2.

Feeding Zend\I18n\Translator from a database

Thursday, August 23, 2012 4 comments

Sometimes you need to get translation messages from a database, for instance when you want your clients to be able to add or edit translations. By default, this is not possible with the translator, but its extendibility allows you to easily integrate it.

Let's get started with the table layout. To get full support for all features, you will need two tables:
CREATE TABLE `locales` (
  `locale_id` char(5) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `locale_plural_forms` varchar(255) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  PRIMARY KEY (`locale_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `messages` (
  `message_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `locale_id` char(5) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `message_domain` varchar(255) CHARACTER SET ascii COLLATE ascii_bin NOT NULL,
  `message_key` text NOT NULL,
  `message_translation` text NOT NULL,
  `message_plural_index` tinyint(3) unsigned NOT NULL,
  PRIMARY KEY (`message_id`),
  KEY `locale_id` (`locale_id`),
  KEY `message_domain` (`message_domain`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

ALTER TABLE `messages`
  ADD CONSTRAINT `messages_ibfk_1`
  FOREIGN KEY (`locale_id`)
  REFERENCES `locales` (`locale_id`)
  ON DELETE CASCADE
  ON UPDATE CASCADE;
The first table is pretty easy to explain. It contains the 5-character locale (for instance: en-EN) and the definitions for the plural forms. The plural forms are defined the same way as they are in gettext and other formats.

The second table is not much more complex. It contains a primary integer key, a reference to the locale and the message specific data. Those are for once text domain, the key, so the string which you use in your source code, the translation for it and optionally the plural index, in case the translation requires plural forms. The plural index shoudl be equivalent with the result of the plural forms evaluation.

Now after we have created our table layout, we need to write a translation loader which is able to retrieve translations from the database. A very simple implementation with Zend\Db could look something like this:
<?php
use Zend\Db\Adapter\Adapter as DbAdapter;
use Zend\Db\Sql\Sql;
use Zend\I18n\Translator\Loader\LoaderInterface;
use Zend\I18n\Translator\Plural\Rule as PluralRule;
use Zend\I18n\Translator\TextDomain;

class DatabaseTranslationLoader implements LoaderInterface
{
    protected $dbAdapter;

    public function __construct(DbAdapter $dbAdapter)
    {
        $this->dbAdapter = $dbAdapter;
    }

    public function load($filename, $locale)
    {
        $textDomain = new TextDomain();
        $sql        = new Sql($this->dbAdapter);

        $select = $sql->select();
        $select->from('locales');
        $select->columns(array('locale_plural_forms'));
        $select->where(array('locale_id' => $locale));

        $localeInformation = $this->dbAdapter->query(
            $sql->getSqlStringForSqlObject($select),
            DbAdapter::QUERY_MODE_EXECUTE
        );

        if (!count($localeInformation)) {
            return $textDomain;
        }

        $localeInformation = reset($localeInformation);

        $textDomain->setPluralRules(
            PluralRule::fromString($localeInformation['locale_plural_forms'])
        );

        $select = $sql->select();
        $select->from('messages');
        $select->columns(array(
            'message_key',
            'message_translation',
            'message_plural_index'
        ));
        $select->where(array(
            'locale_id'      => $locale,
            'message_domain' => $filename
        ));

        $messages = $this->dbAdapter->query(
            $sql->getSqlStringForSqlObject($select),
            DbAdapter::QUERY_MODE_EXECUTE
        );

        foreach ($messages as $message) {
            if (isset($textDomain[$message['message_key']])) {
                if (!is_array($textDomain[$message['message_key']])) {
                    $textDomain[$message['message_key']] = array(
                        $message['message_plural_index'] => $textDomain[$message['message_key']]
                    );
                }

                $textDomain[$message['message_key']][$message['message_plural_index']]
                    = $message['message_translation'];
            } else {
                $textDomain[$message['message_key']] = $message['message_translation'];
            }
        }

        return $textDomain;
    }
}
This loader is a little bit tricky, as we are abusing the $filename parameter to pass in the text domain we want to load. Apart from that, the code should be pretty much self-explaining. Next you need to create a factory, so that the service manager can populate the loader with a database adapter when the translator requests an instance of the loader:
<?php
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;

class DatabaseTranslationLoaderFactory implements FactoryInterface
{
    public function createService(ServiceLocatorInterface $serviceLocator)
    {
        return new DatabaseTranslationLoader(
            $serviceLocator->get('Zend\Db\Adapter\Adapter')
        );
    }
}
Finally we can add the translations to our application:
<?php
$translator->addTranslationFile(
    'DatabaseTranslationLoader',
    'text-domain',
    'text-domain'
);
We have to enter the text domain twice here, as you read earlier we are abusing the $filename parameter to pass it to the loader. Now the translator is ready to use. You should make sure to use caching, as loading the translations on every request is kinda heavy. You'd usually choose a long caching time, and then simply invalidate the cache everytime the database translations are updated.

I hope this post will help all of you seeking for a solution to this problem. All code examples are written down out of my head, so they may contain mistakes or something may be missing at all. In that case please gist me a corrected version of that part, so I can update it.

Be sure that more i18n related topics are following in the near future, and when you are going to ZendCon, don't miss my i18n or router talk there!

Update
I'm currently working on a database translation loader added to ZF2 itself. It will most likely be available in 2.1.