Faule Models und große Datenmengen

Unter verschiedenen Umständen kann es vorteilhaft sein, wenn Models nicht vollständig geladen und mit Daten befüllt werden, weil das Lesen zu komplex ist (Stichwort EAV-Model) oder wir mit sehr vielen Datensätzen arbeiten und dabei Speicherplatz sparen wollen/müssen.

Gleichzeitig wollen wir aber zu jeder Zeit sicherstellen, dass wir das Model wie gewohnt vollständig abfragen und verwenden können. An dieser Stelle kommt eine Technik namens LazyLoading (lazy = engl. faul, träge) zum Einsatz.

Nun konstruiere ich einfach ein Beispiel, um den Ablauf besser erklären zu können. In unserer Anwendung brauchen wir ein Model “Person”, das Informationen wie Name, Geburtstag und diverse andere speichern kann. Den Namen und den Geburtstag brauchen wir immer (z.B. in der Listenansicht) und die restlichen Felder nur wenn wir eine Detailansicht erstellen.

Wie kommt unser Model nun an die Daten? In der Regel wird sicherlich load() auf dem Modelobjekt aufgerufen oder eine vergleichbare Methode auf der jeweiligen Collection, dann werden die Daten über ein Resourcemodel geladen und letztendlich komplett auf die einzelnen Modelobjekte kopiert.

Um unser Vorhaben umzusetzen müssen wir das etwas abändern. Die Methode load() des Models funktioniert wie gewohnt und lädt alle Daten und die Collection nur die Nötigsten. Um nun sicherzustellen, dass wir trotzdem immer alles abrufen können, muss das Model etwas intelligenter werden.

Die Entwicklung des Custom_Models hat der eine oder andere ja vielleicht mitbekommen und kennt den grundlegenden Aufbau. Relativ neu ist die Klasse Custom_Model_Persistent, durch die eine weitere Unterscheidung zwischen einfachen Datencontainern und speicherbaren Models möglich wird.

Für das automatische Nachladen muss die Modelklasse nun erweitert werden.

<?php
class Custom_Model_Persistent extends Custom_Model
{
  private $_isLoaded;

  // ...

  public function load($value = null, $field = 'id')
  {
    if (null === $value && $this->_isset($field)) {
      $value = $this->_get($field);
    }

    $this->getResource()
         ->loadModel($this, $value, $this->_mapKey($field))
         ->markLoaded();

    return $this;
  }

  public function isLoaded()
  {
    return $this->_isLoaded;
  }

  public function markLoaded()
  {
    $this->_isLoaded = true;
    return $this;
  }

  protected function _get($key, $default = null)
  {
    if (!$this->_isset($key) && !$this->isLoaded()) {
      $this->load();
    }
    return parent::_get($key, $default);
  }
}

Die entscheidenen Stellen sind hier der überschriebene zentrale Getter, der ein das “Default-Load()” auslöst, wenn der Wert nicht gesetzt ist und die Daten auch noch nicht vollständig geladen wurden und die Methode load() mit optionalen Parameter $value.

Im Beispiel verlasse ich mich darauf, dass das Standardfeld schon gesetzt ist. Eine ordentliche Fehlerbehandlung muss aber unbedingt her, wenn wir das ganze produktiv einsetzen wollen.

Die Methoden isLoaded() bzw markLoaded() sind absichtlich public, damit sie z.B. aus einer Collection aufgerufen werden können, wenn das Model komplett geladen wurde. Eine weitere Möglichkeit wäre ein Vergleich mit einer Liste die gesetzt sein sollten.

Im Moment ist das noch etwas im Entwurf und sogar ungetestet. Wenn euch nun noch was dazu einfällt, dann schreibt einen Kommentar. Wo seht ihr für euch die Vor- oder evtl. auch die Nachteile?

4 Kommentare

  1. Wie lädst du die Properties die LazyLoading sind später nach?
    Machst du je nachgefragten Propertie einen Query?

    Christoph

  2. Das wäre vorsichtig gesagt suboptimal. Das Model kann unter gewissen Umständen unvollständig “befüllt” sein und wird komplett geladen, wenn ein nicht gesetztes Property abgerufen wird. Aber auch das lässt sich bei Bedarf noch in mehrere Stufen unterteilen.

  3. Noch viel einfacher und sauberer ist es, dem View genau die (sprich: alle) Daten zu liefern, die es darstellen soll.

    Daraus resultiert zwangsläufig, dass man mindestens zwei Modelle haben sollte. Eins oder mehrere für das Reporting (Anzeige) und nur ein einziges für die eigentliche Arbeit (was wiederum in Submodels gegliedert sein kann).

  4. Der View soll auch alle Daten bekommen, nur das Model entscheidet, wann diese geladen werden. Mit Subclassing hätte man hier nicht viel gewonnen.

Schlagwörter: Adapter, Amazon, Animation, Annotations, Anonyme Klasse, Ant, Apache, API, Array, ArrayAccess, Attachment, AutoLoader, Bedienung, Bedingung, Benchmark, Bildbearbeitung, BOM, Bootstrap, Bot, Byte Order Mark, Callback, CamelCase, Canvas, Captcha, Cheatsheet, CLI, Closure, Cloud, CodeSniffer, Community, Comparator, Contest, Controller, Converter, CouchDB, Countable, Cronjob, CSV, CustomLibrary, Custom_Model, Data Mapper, Datei, Datenbank, Datenstruktur, Datentypen, Dating, Decorator, Dekorierer, Design Patterns, Dump, Duplikat, each, Eclipse, Entwicklung, Entwurfsmuster, Enum, Erweiterung, Eventhandling, Exception-Handling, Extension, Factory, Fehler, Flash, Foreach, Formatierung, Formular, Funktion, Futon, Header, HTML5, HTTP, IDE, If, Implementierung, InnoDB, Interceptor, Interface, isset, Iterator, Java, JavaScript, jQuery, Konfiguration, Konsole, Kontrollstruktur, kopieren, Late Static Binding, Layout, Linux, Listeners, Logging, Löschen, Magento, Magic Methods, Marketing, Methode, Model, MVC, MySQL, NetBeans, Objekt, Observable, Observer, OOP, Operator, Parameter, Partnersuche, Performance, PHP, phpMyAdmin, PHPUnit, Plugin, Proxy, Qualitätssicherung, Query, Reflection, Request, Response, Rest-API, Rockstar, Routing, S3, Samba, Scheifen, Schleife, Schutz, Secure Shell, Selbstreferenz, Shop, Sicherheit, Sicherung, Singleton Pattern, SOAP, Sortierung, Sourcecode, Spam, Speicherproblem, Spickzettel, SPL, SSH, Statement, Stellvertreter, Strategy Pattern, Stream, String, Sun VirtualBox, Support, Switch, Symfony, Symfony2, Symfony Live, Tag, Template, Template Method, Ternär Operator, Testing, Thumbnail, Tool, Tour, Twig, Type-Cast, Umwandlung, Underscore, unset, Vererbung, Verzweigung, Video, Videospiel, Virtualisierung, Visitor Pattern, Vorschaubild, walk, Webserver, Webservice, Weiterleitung, Wrapper, Youtube, Zeitsteuerung, Zend Framework, Zend_Cloud, Zend_CodeGenerator, Zend_Http_Client, Zend_Service, Zugriffsmethode