a Null object minta használata Doctrine-nal

A null object mintáról sok helyen lehet olvasni. Alapvetően arról van szó, hogy abban az esetben, amikor egy objektum példányt kellene kapnod (mondjuk egy factory-tól), de az a feltételeknek megfelelő objektumot nem tud biztosítani, akkor nem null-t ad vissza, hanem egy úgynevezett null objektumot, amely típusában, viselkedésében megfelel egy “rendes” objektumnak.

A minta előnye, hogy nem kell folyamatosan vizsgálnunk a kapott értéket (null-e vagy objektum példány), egyszerűen csak használjuk. Hogy mire is jó, arra lássunk egy példát.

Tegyük fel, hogy van egy galériánk, amelyben (mily meglepő) képek találhatók. A galériának van egy kiemelt képe, amellyel megjelenik az oldalon. Feladat: hogyan tudjuk egyszerűen elérni, hogy azok a galériák, amelyeknek nincs kiemelt képe, egy “no-image.jpg”-et jelenítsenek meg?

Kezdjük az elején. A sémával.

Gallery:
  tableName: gallery
  columns:
    id:
      type: integer
      unsigned: true
      notnull: true
      primary: true
      autoincrement: true
    name:
      type: string(200)
    preview_image_id:
      type: integer
      unsigned: true
      notnull: true
  relations:
    PreviewImage:
      local: preview_image_id
      foreign: id
      type: one
      class: Image
Image:
  tableName: picture
  columns:
    id:
      type: integer
      unsigned: true
      notnull: true
      primary: true
      autoincrement: true
    gallery_id:
      type: integer
      unsigned: true
      notnull: true
    file:
      type: string(255)
    title:
      type: string(255)
  relations:
    Gallery:
      local: gallery_id
      foreign: id
      foreignAlias: Images
      onDelete: CASCADE

Ezzel a sémával már működni fog a galéria. Mint látható, a galéria tartalmaz egy preview_image_id mezőt, amely az előképre hivatkozás (ennek a mezőnek a kezelése a fejlesztőre hárul, általában egy, a galériába feltöltött képek közül). Mi történik akkor, ha most megprólájuk megjeleníteni az előképet?

Mindjárt meglátjuk, de előbb írjunk egy kis kódot. Készítsünk egy egyszerű API-t a képek kezeléséhez.

class Image extends BaseImage
{
  const TYPE_ORIGINAL = 1;
  const TYPE_THUMBNAIL = 2;

  // ...
  public function getImageName($type = self::TYPE_ORIGINAL)
  {
    $info = pathinfo($this->file);
    $names = array(
      self::TYPE_ORIGINAL => $this->file,
      self::TYPE_THUMBNAIL => $info['filename'].'_thumb.'.$info['extension'],
    );

    if (!isset($names[$type]))
    {
      throw new InvalidArgumentException();
    }

    return $names[$type];
  }

  public function getImagePath($absolute = true)
  {
    $path = '/uploads/images';
    $absolute_path = sfConfig::get('sf_web_dir').$path;

    if (!file_exists($absolute_path))
    {
      mkdir($absolute_path, 0777, true);
      chmod($absolute_path, 0777);
    }

    return $absolute ? $absolute_path : $path;
  }

  public function getImageFile($type = self::TYPE_ORIGINAL, $absolute = false)
  {
    return $this->getImagePath($absolute).'/'.$this->getImageFile($type);
  }
  // ...
}

Szóval mi történik, ha megpróbáljuk megjeleníteni az előképet?

<img src="<? echo $gallery->PreviewImage->getImageFile(); ?>" />

Hát az attól függ🙂 Ha van előkép, akkor megjelenik. Ha nincs, akkor viszont egy fatal error a jutalmunk. Tehát meg kell vizsgálnunk, hogy létezik-e az előkép. Sokkal kényelmesebb lenne, ha előkép beállítás nélkül is egy Image objektumot kapnánk (null object), amelyen meghívva a getImageFile() metódust, az alapértelmezett képet jelenítené meg.

Ehhez két dolog szükséges:

  1. a doctrine, beállítás nélkül is egy Image példányt adjon
  2. file megadás nélkül az alapértelmezett képet adja vissza

Az első pont könnyen teljesíthető. Változtassunk a sémán. A Gallery preview_image_id mezőjéről vegyük le a notnull előírást. Ekkor a doctrine egy frissen létrehozott Image példányt ad (roppant kellemes működés).

A második ponthoz kicsit át kell írni két metódust: getImageName(), getImageFile().

class Image extends BaseImage
{
  // ...
  public function getImageName($type = self::TYPE_ORIGINAL)
  {
    if ($this->file)
    {
      $info = pathinfo($this->file);
      $names = array(
        self::TYPE_ORIGINAL => $this->file,
        self::TYPE_THUMBNAIL => $info['filename'].'_thumb.'.$info['extension'],
      );
    }
    else
    {
      $names = array(
        self::TYPE_ORIGINAL => '/images/no_image.jpg',
        self::TYPE_THUMBNAIL => '/images/no_image_thumb.jpg',
      );
    }

    if (!isset($names[$type]))
    {
      throw new InvalidArgumentException();
    }

    return $names[$type];
  }

  public function getImageFile($type = self::TYPE_ORIGINAL, $absolute = false)
  {
    if (!$this->file)
    {
      return $this->getImageName($type);
    }
    return $this->getImagePath($absolute).'/'.$this->getImageFile($type);
  }
  // ...
}

Még egyszer kiemelném a Doctrine azon viselkedését, hogy az idegenkulcsról ha eltávolítjuk a notnull előírást, akkor a kapcsolt model lekérésekor nem null-t, hanem üres objektumot kapunk. A modelhez hozzáadott kód nagy előnye, hogy több képet szolgáltató objektum esetén alkalmazva egységes felületet kapunk. Emellett a getImagePath() nagyszerűen használható a feltöltő form validatorának path opciójaként.

Úgy gondolom ennyi. Várom a véleményeket, kritikákat😉

Kategória: fejlesztés, oop, php
Címke: , , ,
Közvetlen link a könyvjelzőhöz.

Vélemény, hozzászólás?

Adatok megadása vagy bejelentkezés valamelyik ikonnal:

WordPress.com Logo

Hozzászólhat a WordPress.com felhasználói fiók használatával. Kilépés / Módosítás )

Twitter kép

Hozzászólhat a Twitter felhasználói fiók használatával. Kilépés / Módosítás )

Facebook kép

Hozzászólhat a Facebook felhasználói fiók használatával. Kilépés / Módosítás )

Google+ kép

Hozzászólhat a Google+ felhasználói fiók használatával. Kilépés / Módosítás )

Kapcsolódás: %s