Doctrine Flagable Behavior/Template

Tags:

Doctrine 1.2 ist mein bevorzugter OR-Mapper für PHP. Durch sogenannte Behaviors lassen sich Models mit Funktionalität, als auch Struktur erweitern. Diese Behaviors werden im Datenbankschema an beliebige Tabellen angehängt und klinken sich dann sowohl bei der Generierung der Datenbankstruktur, also auch zur Script-Laufzeit ein.

Was sind Flags?

Stellen wir uns als Beispiel eine Tabelle messages vor. Diese könnte wie folgt aussehen:

+----------------------+--------------+
| Feld                 | Typ          |
+----------------------+--------------+
| id                   | INTEGER(6)   |
| sender_id            | INTEGER(3)   |
| recipient_id         | INTEGER(3)   |
| subject              | VARCHAR(255) |
| message              | TEXT         |
| sent_at              | TIMESTAMP    |
| read                 | TINYINT(1)   |
| favored              | TINYINT(1)   |
| deleted_by_sender    | TINYINT(1)   |
| deleted_by_recipient | TINYINT(1)   |
+----------------------+--------------+

Etwas was ich bei Tabellen öfters verwende ist ein flags-Feld. Mit diesem kann ich obige bool'schen Felder in ein einziges Feld packen. Dies geschieht mit Hilfe einer Bitmaske.

+--------------+--------------+
| Feld         | Typ          |
+--------------+--------------+
| id           | INTEGER(6)   |
| sender_id    | INTEGER(3)   |
| recipient_id | INTEGER(3)   |
| subject      | VARCHAR(255) |
| message      | TEXT         |
| sent_at      | TIMESTAMP    |
| flags        | INTEGER(6)   |
+--------------+--------------+

FLAG_READ                 = 1
FLAG_FAVORED              = 2
FLAG_DELETED_BY_SENDER    = 4
FLAG_DELETED_BY_RECIPIENT = 8

Möchte ich nun die Nachricht als gelesen und vom Empfänger gelöscht markieren, setze ich dem flags-Feld den Wert 9 (1 + 8). Dieser Wert ist eindeutig für diesen beiden Flags. Er lässt sich mit keiner anderen Kombinationen der Zahlen 1, 2, 4 und 8 bilden.

Der grosse Vorteil eines flags-Feldes ist, dass man neue Flags hinzufügen kann, ohne die Datenbankstruktur verändern zu müssen. Per Bit-Operatoren lassen sich die Flags ausserdem auf simple Weise testen. Weiter in die Thematik von Bitmasken möchte ich aber gar nicht eingehen, dafür gibt's genügend gute Ressourcen im Internet. :-)

API

Die PHPhil_Doctrine_Template_Flagable-Klasse bietet das Folgende:

Konfigurationsoptionen

+---------+-------------+----------------------+-------------------------------+
| Name    | Typ         | Standardwert         | Beschreibung                  |
+---------+-------------+----------------------+-------------------------------+
| name    | string      | 'flags'              | Name des Feldes               |
| alias   | string|null | null                 | Name der Klasseneigenschaft   |
| type    | string      | 'int'                | Typ des Feldes                |
| length  | int         | 12                   | Grösse des Feldes             |
| options | array       | array(               | Sonstige Doctrine-spezifische |
|         |             |   'default' => 0,    | Optionen des Feldes           |
|         |             |   'notnull' => true, |                               |
|         |             | )                    |                               |
+---------+-------------+----------------------+-------------------------------+

An Models "vererbte" Methoden

+----------------------+-----------------+--------------------------------------------+
| Methode              | Rückgabewert    | Beschreibung                               |
+----------------------+-----------------+--------------------------------------------+
| getFlags()           | int             | Gibt den Wert des Feldes zurück            |
| setFlags(int $flags) | Doctrine_Record | Setzt den Wert des Feldes                  |
| hasFlag(int $flag)   | boolean         | Prüft ob ein bestimmtes Flag gesetzt wurde |
| setFlag(int $flag)   | Doctrine_Record | Setzt ein bestimmtes Flag                  |
| unsetFlag(int $flag) | Doctrine_Record | Entfernt ein gesetztes Flag                |
| getFlagConstants()   | array           | Gibt alle Klassenkonstanten mit einem      |
|                      |                 | bestimmten Prefix zurück. Komfortfunktion. |
+----------------------+-----------------+--------------------------------------------+

Anwendungsbeispiel

Konfiguration im schema.yml

Message:
  tableName: messages
  columns:
    id:
      type: in...
  actAs:
    PHPhil_Doctrine_Template_Flagable:

Model erweitern

class Message extends BaseMessage
{
    const FLAG_READ                 = 1;
    const FLAG_FAVORED              = 2;
    const FLAG_DELETED_BY_SENDER    = 4;
    const FLAG_DELETED_BY_RECIPIENT = 8;

    public function isRead()
    {
        return $this->hasFlag(self::FLAG_READ);
    }

    public function markAsRead()
    {
        $this->setFlag(self::FLAG_READ)
             ->save();

        return $this;
    }

    public function markAsUnread()
    {
        $this->unsetFlag(self::FLAG_READ)
             ->save();

        return $this;
    }
}

Code

Und hier nun der eigentliche Code. Viel Spass!

class PHPhil_Doctrine_Template_Flagable extends Doctrine_Template
{
    /**
     * @var array
     */
    protected $_options = array(
        'name'    => 'flags',
        'alias'   => null,
        'type'    => 'int',
        'length'  => 12,
        'options' => array(
            'default' => 0,
            'notnull' => true,
        ),
    );


    /**
     * Add field to table
     */
    public function setTableDefinition()
    {
        $name = $this->getOption('name');
        if ($this->getOption('alias')) {
            $name .= ' as ' . $this->getOption('alias');
        }

        $this->hasColumn($name, $this->getOption('type'), $this->getOption('length'), $this->getOption('options'));
    }


    /**
     * Return all flags
     * 
     * @return int
     */
    public function getFlags()
    {
        return (int)$this->getInvoker()->get($this->getOption('name'));
    }


    /**
     * Set the 'flags' property
     * 
     * @param int flags
     * @return Doctrine_Record
     */
    public function setFlags($flags)
    {
        $this->getInvoker()->set($this->getOption('name'), (int)$flags);

        return $this->getInvoker();
    }


    /**
     * Check if a specifc flag is set
     * 
     * @param int $flag
     * @return boolean
     */
    public function hasFlag($flag)
    {
        return (bool)($this->getFlags() & (int)$flag);
    }


    /**
     * Set a specific flag
     *
     * @param int $flag
     * @return Doctrine_Record
     */
    public function setFlag($flag)
    {
        $this->setFlags((int)$flag | $this->getFlags());

        return $this->getInvoker();   
    }


    /**
     * Delete a specific flag
     *
     * @param int $flag
     * @return Doctrine_Record
     */
    public function unsetFlag($flag)
    {
        $this->setFlags($this->getFlags() & ~(int)$flag);

        return $this->getInvoker();
    }


    /**
     * Return all constants of the model beginning with a specific needle
     * 
     * @param string $needle
     * @return array
     */
    public function getFlagConstants($needle = 'FLAG_')
    {
        $flags = array();

        $reflection = new ReflectionClass($this->getInvoker());
        foreach ($reflection->getConstants() as $name => $value) {
            if (0 === strpos($name, $needle)) {
                $flags[$name] = $value;
            }
        }

        return $flags;
    }
}

Ähnliche Artikel

Kommentare