Voor een webwinkelplatform heb ik een class Product, die nu een active record pattern volgt. Elk product heeft tientallen eigenschappen, die met uiteenlopende beslissingsregels in Product::select() worden ingesteld. Ingekort voor drie eigenschappen en twee beslissingsregels ziet de klasse er zo uit:


<?php
class Product implements ActiveRecordInterface
{
    private $ProductID;
    private $Name;
    private $Price;
    // Enzovoort...

    public function __construct($id = null)
    {
        if (isset($id)) {
            $this->select($id);
        }
    }

    /**
     * @param int $id
     * @return bool
     */
    public function select($id)
    {
        if (is_numeric($id) && $id <= PHP_INT_MAX) {
            $id = (int) abs($id);
        } else {
            return false;
        }
        $query = 'SELECT * FROM products WHERE product_id = ' . $id;
        $dbh = new Database();
        if ($result = $dbh->query($query)) {
            if ($result->num_rows == 1) {
                $record = $result->fetch_assoc();
                $result->free();
                $dbh->close();
                unset($result, $dbh);
                $this->ProductID = $id;

                // Beslissingsregels
                if ($record['product_name_nl'] != null) {
                    $this->Name = $record['product_name_nl'];
                } else {
                    $this->Name = $record['product_name_en_us'];
                }

                if ($record['sale_price'] != null) {
                    $this->Price = (float) $record['sale_price'];
                } elseif ($record['price'] != null) {
                    $this->Price = (float) $record['price'];
                } else {
                    $this->Price = null;
                }
                // Enzovoort...

                return true;
            }
        }
        return false;
    }
}
?>

Voor uiteenlopende lijsten met meerdere producten (tabellen, prijslijsten, zoekresultaten, enzovoort) is het onpraktisch om meerdere complete productobjecten te laden die elk een eigen query uit te voeren. Wat is hiervoor een efficiënte oplossing waarbij de beslissingsregels niet verloren gaan?
Om te beginnen zou ik deze class gaan splitsen. Op dit moment heeft deze class meerdere verantwoordelijkheden. Het beheert de product gegevens en bouwt de query. Dat laatste zou niet zijn taak moeten zijn. Die query moet er dus uit en in een aparte class (noem het een datamapper, of query class). En dan heb je je oplossing in feite al. Die query class geeft een array aan producten terug (kan er 1 zijn, kunnen er meerdere zijn) en voor elke element in die array maak je een object van de product class aan. In de constructor van die class geef je een array mee met alle product eigenschappen en daar worden dan de door jouw getoonde tests in uitgevoerd.

Eventueel heb je er dus nog wel een class tussen nodig (wat ik een facade noem), die die product objecten aanmaakt op basis van de array die het krijgt uit de query class.
Dank je Erwin! Ik zocht het te makkelijk in één extra klasse. Met twee klassen lijkt het me inderdaad beter: een datamapper voor de ruwe data plus een façade voor de beslissingsregels.

Een deel van de hardere beslissingsregels hoort inderdaad thuis in een querybuilder. Bijvoorbeeld: bij een product zonder Nederlandse naam gebruiken we altijd de Engelse naam. (Dat is namelijk een IFNULL in SQL.) Zachtere beslissingsregels, bijvoorbeeld price rules, kunnen over naar de façade. Mooie oplossing!

Denk je dat hier nog winst is te behalen met bijvoorbeeld een flyweight pattern?
Ik gebruik inderdaad een flyweight patern voor wat ik noem de 'sanitizers'. Dit zijn objecten die voor de juiste formatering zorgen van bepaalde velden. Bijvoorbeeld datums komen altijd op de MySQL manier uit de database, maar mijn sanitizers zorgen ervoor dat dat op een correcte manier geformateerd wordt (dus niet '2013-09-12 10:43:12' maar '12 September 2013 10:43'). Maar bijvoorbeeld ook voor het afhandelen van lege waardes, of zelfs niet bestaande waardes. Zo hoef ik in mijn view nooit te controleren of een veld wel bestaat of niet. Elke product object (in jouw geval) zou geinjecteerd kunnen worden met een sanitizer en die zorgt voor die formatering, zonder zelf data opgeslagen te hebben.
sorry deze post was niet voor hier had verkeerd geklikt

Reageren