Wzorce projektowe PHP – Behavioral, Creational, Structural i optymalizacja baz danych
Behavioralne wzorce projektowe
Behavioralne wzorce projektowe wspomagają procesy i mechanizmy komunikacji pomiędzy obiektami klas. Dzięki ich wykorzystaniu możemy zwiększyć elastyczność i wydajność prowadzenia wymiany danych.
Iterator
Iterator wykorzystujemy po to, by traktować obiekt jako listę / kolekcję obiektów.
/*
* Wykorzystujemy po to, by traktować obiekt jako listę / kolekcję obiektów
*/
namespace Patterns\Behavioral\Iterator;
use Iterator;
class ListaPiwIterator implements Iterator
{
private $listaPiw;
protected $aktualnePiwo = 0;
public function __construct(ListaPiw $listaPiw)
{
$this->listaPiw = $listaPiw;
}
public function current()
{
return $this->aktualnePiwo->getBook($this->aktualnePiwo);
}
public function next()
{
$this->aktualnePiwo++;
}
public function key()
{
return $this->aktualnePiwo;
}
public function valid()
{
return null !== $this->listaPiw->getBook($this->aktualnePiwo);
}
public function rewind()
{
$this->aktualnyObiekt = 0;
}
}
interface PiwoInterface
{
public function nazwa();
}
class Piwo implements PiwoInterface
{
private $nazwa;
public function __construct($nazwa)
{
$this->nazwa = $nazwa;
}
public function nazwa()
{
return $this->nazwa;
}
}
use Countable;
class ListaPiw implements Countable
{
private $piwa;
public function pobierzPiwo($numerPiwa)
{
if (isset($this->piwa[$numerPiwa])) {
return $this->piwa[$numerPiwa];
}
return null;
}
public function dodajPiwo(PiwoInterface $piwo)
{
$this->piwa[] = $piwo;
return $this->count();
}
public function usunPiwo(PiwoInterface $piwoDoUsuniecia)
{
foreach ($this->piwa as $key => $piwo) {
if ($piwo->nazwa() === $piwoDoUsuniecia->nazwa()) {
unset($this->piwa[$key]);
}
}
return $this->count();
}
public function count()
{
return count($this->piwa);
}
}
/** Budujemy listę */
$warka = new Piwo('Warka'); // super cukier
$harnas = new Piwo('Harnas'); // super pomyje
$lista = new ListaPiw;
$lista->dodajPiwo($warka);
$lista->dodajPiwo($harnas);
/** Budujemy iterator */
$iterator = new ListaPiwIterator($lista);
/** Wykorzystujemy iterator jako tablicę / kolekcję */
foreach ($iterator as $current) {
echo $current->nazwa();
}Mediator
Mediator – implementacja wtedy, kiedy obiekty klas nie mają ze sobą kontaktu i potrzebują klasy pośredniej do świadczenia usługi.
namespace Patterns\Behavioral\Mediator;
interface MediatorInterface
{
public function poprosOprzysluge();
}
class Mediator implements MediatorInterface
{
public function poprosOprzysluge()
{
echo 'Proszę o wykonanie przysługi';
}
}
/**
* Klasa abstrakcyjna, koledzy znają mediatora, ale nie znają samych siebie
*/
abstract class Kolega
{
private $mediator;
public function __construct(MediatorInterface $medium)
{
$this->mediator = $medium;
}
protected function getMediator()
{
return $this->mediator;
}
}
class Marcin extends Kolega
{
}
/** Użycie */
$mediator = new Mediator();
$marcin = new Marcin($mediator);
$marcin->getMediator()->poprosOprzysluge();Observer
Observer – kiedy potrzebujemy nasłuchiwania zmian w obiekcie.
namespace Patterns\Behavioral\Observer;
/**
* Definicja obserwatora
*/
class ZonaObserver
{
public function update()
{
echo 'Ty świnio, znowu pijesz piwo';
}
}
/**
* Definicja obiektu obserwowanego
*/
class Maz implements \SplSubject
{
protected $piwa = array();
protected $obserwatorzy;
public function __construct()
{
$this->obserwatorzy = new \SplObjectStorage();
}
public function attach(\SplObserver $obserwator)
{
$this->obserwatorzy->attach($obserwator);
}
public function detach(\SplObserver $obserwator)
{
$this->obserwatorzy->detach($obserwator);
}
public function notify()
{
foreach ($this->obserwatorzy as $obserwator) {
$obserwator->update();
}
}
public function napijSiePiwa($name, $value)
{
$this->piwa[$name] = $value;
// Poinformuj wszystkich obserwatorów o tym, że doszło do zmiany
$this->notify();
}
}
/** Użycie */
$maz = new Maz();
$obserwujacaZona = new ZonaObserver();
$maz->attach($obserwujacaZona);
/** Przy każdym napiciu się piwa żona krzyczy */
$maz->napijSiePiwa('warka', '5.6');
$maz->napijSiePiwa('harnas', '5.8');
$maz->napijSiePiwa('zywiec', '5.8');
/** Odłączamy żonę, żeby nie marudziła */
$maz->detach($obserwujacaZona);
$maz->napijSiePiwa('porter 20%', '20');Strategy
Strategy – kiedy jest potrzeba wykorzystania wieloplatformowego mechanizmu (różnych pod względem pewnych cech, ale tych samych pod względem zastosowania) operowania na danych.
namespace Patterns\Behavioral\Strategy;
interface RejestratorInterface
{
public function rejestruj($imie, $nazwisko);
}
abstract class Rejestrator implements RejestratorInterface
{
protected $dbal;
public function __construct()
{
$this->dbal = new \DBAL;
}
abstract public function rejestruj($imie, $nazwisko);
}
class RejestratorPodkarpacki implements RejestratorInterface
{
public function rejestruj($imie, $nazwisko)
{
$dbal = $this->dbal;
$dbal->region = 'podkarpackie';
$dbal->nazwa = $imie . ' ' . $nazwisko;
return $dbal->zapisz();
}
}
class RejestratorLubelski implements RejestratorInterface
{
protected $dbal;
public function rejestruj($imie, $nazwisko)
{
$dbal = $this->dbal;
$dbal->region = 'lubelskie';
$dbal->nazwa = $imie . ' ' . $nazwisko;
return $dbal->zapisz();
}
}
class StrategiaRejestracyjna
{
private $rejestrator;
public function setRejestrator(RejestratorInterface $rejestrator)
{
$this->rejestrator = $rejestrator;
}
public function rejestruj($imie, $nazwisko)
{
return $this->rejestruj($imie, $nazwisko);
}
}
/** Użycie */
$strategia = new StrategiaRejestracyjna();
/** Rejestracja w lubelskiem */
$strategia->setRejestrator(new RejestratorLubelski());
$strategia->rejestruj('Kazimierz', 'Wąskobrody');
/** Rejestracja w podkarpackiem */
$strategia->setRejestrator(new RejestratorPodkarpacki());
$strategia->rejestruj('Tadeusz', 'Ferenc');Creational – wzorce kreacyjne
Wzorce projektowe wspomagające procesy i mechanizmy budowania klas.
Abstract Factory
Abstract Factory – jeśli jest potrzeba projektowania funkcjonalności bez konieczności specyfikowania konkretnych klas.
namespace Patterns\Creational\AbstractFactory;
interface MediaInterface
{
public function render();
}
abstract class AbstractText implements MediaInterface
{
protected $text;
public function __construct($text)
{
$this->text = (string) $text;
}
public function __toString()
{
return $this->render();
}
}
class Text extends AbstractText
{
public function render()
{
return '';
}
}
class Numeric extends AbstractText
{
public function render()
{
return '1';
}
}
abstract class AbstractFactory
{
abstract public function createText($content);
}
class FormBuilderFactory extends AbstractFactory
{
public function createText($content)
{
return new Text($content);
}
public function createNumeric($content)
{
return new Numeric($content);
}
}
/** Użycie */
$factory = new FormBuilderFactory();
echo $factory->createText('cześć');
echo $factory->createNumeric('123');Builder
Builder – budowanie funkcjonalności obiektu sklejając go z różnych części.
namespace Patterns\Creational\Builder;
interface PiwoInterface
{
public function buildPiwo();
public function getPiwo();
}
class Piwo
{
protected $nazwa;
protected $sila;
public function ustawNazwe($nazwa)
{
$this->nazwa = $nazwa;
return $this;
}
public function ustawSile($sila)
{
$this->sila = $sila;
return $this;
}
public function __toString()
{
return $this->nazwa . ':' . $this->sila;
}
}
class PiwoWarkaBuilder implements PiwoInterface
{
private $piwo;
public function __construct()
{
$this->piwo = new Piwo();
}
public function buildPiwo()
{
$this->piwo->ustawNazwe("warka")->ustawSile('5.8');
}
public function getPiwo()
{
return $this->piwo;
}
}
class PiwoLechBuilder implements PiwoInterface
{
private $piwo;
public function __construct()
{
$this->piwo = new Piwo();
}
public function buildPiwo()
{
$this->piwo->ustawNazwe("lech")->ustawSile('10.8');
}
public function getPiwo()
{
return $this->piwo;
}
}
/** Użycie */
$warka = new PiwoWarkaBuilder();
$warka->buildPiwo();
echo $warka->getPiwo();
$lech = new PiwoLechBuilder();
$lech->buildPiwo();
echo $lech->getPiwo();Prototype
Prototype – jeśli jest potrzeba standardowego i kosztownego budowania obiektów (new Foo()), stwórz prototyp i wykorzystaj metodę __clone.
namespace Patterns\Creational\Prototype;
abstract class PrototypAuta
{
/**
* @var String
*/
protected $nazwa;
/**
* @abstract
* @return void
*/
abstract public function __clone();
/**
* @return String
*/
public function getNazwa()
{
return $this->nazwa;
}
/**
* @param string $nazwa
*/
public function setNazwa($nazwa)
{
$this->nazwa = $nazwa;
}
}
class GolfPrototyp extends PrototypAuta
{
/**
* Empty clone
*/
public function __clone()
{
}
}
/** Użycie */
$golfPrototype = new GolfPrototyp();
// Potrzebujemy 10 000 golfów
for ($i = 0; $i < 10000; $i++) {
$golf = clone $golfPrototype;
$golf->setNazwa('Golf 4 o numerze ' . $i);
}Simple Factory
Simple Factory – jeśli jest potrzeba projektowania funkcjonalności z zachowaniem konkretnej specyfikacji klasy. Wiesz, że specyfikacja danej klasy nie zmieni się w przyszłości lub nie powinna ulec zmianie.
namespace Patterns\Creational;
class ModuleFactory
{
/**
* @var array
*/
protected $typeList;
/**
* Wiesz, że dana klasa fabryki będzie składać się z określonego typu obiektów
*/
public function __construct()
{
$this->typeList = array(
'collector' => __NAMESPACE__ . '\ModuleCollector',
'user' => __NAMESPACE__ . '\ModuleUser',
);
}
/**
* Moduł collectora w fabryce
*
* @param type $type
* @return \Patterns\Creational\SimpleFactory\className
* @throws \InvalidArgumentException
*/
public function createModule($type)
{
if (!array_key_exists($type, $this->typeList)) {
throw new \InvalidArgumentException("$type is not valid module");
}
$className = $this->typeList[$type];
return new $className();
}
}
interface ModuleInterface
{
public function install();
}
class ModuleCollector implements ModuleInterface
{
public function install()
{
}
}
class ModuleUser implements ModuleInterface
{
public function install()
{
}
}
/** Użycie */
$factory = new ModuleFactory();
$collector = $factory->createModule('collector');
$collector->install();
$user = $factory->createModule('user');
$user->install();Singleton
Singleton – jeśli jest potrzeba posiadania jednej instancji obiektu, która obsłuży wszystkie żądania.
namespace Patterns\Creational\Singleton;
/**
* class Singleton
*/
class MeskieRurki
{
private static $mojeRurki;
public static function getInstance()
{
if (null === static::$mojeRurki) {
static::$mojeRurki = new static;
}
return static::$mojeRurki;
}
private function __construct()
{
}
public function obcisnij()
{
echo 'Rurki obciśnięte';
}
}
/**
* Użycie
*/
MeskieRurki::getInstance()->obcisnij();Structural – wzorce strukturalne
Wzorce projektowe stanowiące opis samej struktury obiektu lub powiązanych ze sobą obiektów.
Adapter
Adapter – jeśli istnieje potrzeba współpracy obiektów o różnych, niezgodnych interfejsach. Przykład z rzeczywistości: przejściówka z gniazda angielskiego na polskie. Najczęściej wykorzystywany, kiedy istnieje potrzeba nadpisania starego mechanizmu klasy bardziej nowoczesnymi rozwiązaniami, bez konieczności ingerencji w implementację starej klasy.
namespace Patterns\Structural\Adapter;
interface GniazdkoAngielskie
{
public function podlacz();
}
class GniazdkoPolskie
{
public function przepnijNaPolskie()
{
echo "Rach ciach ciach i gniazdko przepięte";
}
}
class GniazdkoPolskieAdapter implements GniazdkoAngielskie
{
public function podlacz()
{
$adaptee = new GniazdkoPolskie();
$adaptee->przepnijNaPolskie();
}
}
/**
* Użycie
*/
$gniazdkoPolskie = new GniazdkoPolskieAdapter;
$gniazdkoPolskie->podlacz();Bridge
Bridge – kiedy istnieje potrzeba rozbicia realizacji metod dwóch podobnych obiektów, tak aby pracowały niezależnie.
namespace Patterns\Structural\Bridge;
interface Zawod
{
public function pracuj();
}
class Hipster implements Zawod
{
public function pracuj()
{
print 'Zamówione rurki';
}
}
class Krawcowa implements Zawod
{
public function pracuj()
{
print 'Uszyte';
}
}
class RurkiIGlany
{
protected $zawod1;
protected $zawod2;
public function __construct(Zawod $zawod1, Zawod $zawod2)
{
$this->zawod1 = $zawod1;
$this->zawod2 = $zawod2;
}
public function produkuj()
{
$this->zawod1->pracuj();
$this->zawod2->pracuj();
}
}
$rurkiiglany = new RurkiIGlany(new Hipster(), new Krawcowa());
$rurkiiglany->produkuj();Composite
Composite – kiedy chcemy potraktować grupę obiektów tego samego typu jako jeden obiekt.
namespace Patterns\Structural\DataMapper;
abstract class FormElement
{
abstract public function render();
}
class InputTextElement extends FormElement
{
public function render()
{
return '';
}
}
class TextareaElement extends FormElement
{
public function render()
{
return '';
}
}
class Form extends FormElement
{
protected $elements;
public function render()
{
$formCode = '';
foreach ($this->elements as $element) {
$formCode .= $element->render();
}
return $formCode;
}
public function addElement(FormElement $element)
{
$this->elements[] = $element;
}
}
$form = new Form();
$inputText = new InputTextElement();
$form->addElement($inputText);
$textarea = new TextareaElement();
$form->addElement($textarea);
$form->render();DataMapper
DataMapper – kiedy chcemy rzutować elementy zwracane przez klasę/y na określony typ obiektu bez potrzeby „ruszania” innych warstw aplikacji.
namespace Patterns\Structural\DataMapper;
class Entity
{
protected $id;
protected $nazwa;
public function __construct($id = null, $nazwa = null)
{
$this->id = $id;
$this->nazwa = $nazwa;
}
public function getId()
{
return $this->id;
}
public function setId($id)
{
$this->id = $id;
}
public function getNazwa()
{
return $this->nazwa;
}
public function setNazwa($nazwa)
{
$this->nazwa = $nazwa;
}
}
class DataMapper
{
protected $adapter;
public function __construct($dbLayer)
{
$this->adapter = $dbLayer;
}
public function findById($id)
{
$result = $this->adapter->find($id);
if (0 == count($result)) {
return null;
}
$row = $result->current();
return $this->mapObject($row);
}
public function findAll()
{
$resultSet = $this->adapter->findAll();
$entries = array();
foreach ($resultSet as $row) {
$entries[] = $this->mapObject($row);
}
return $entries;
}
protected function mapObject(array $row)
{
$entry = new Entity();
$entry->setId($row['id']);
$entry->setNazwa($row['nazwa']);
return $entry;
}
}Decorator
Decorator – dynamiczne dodawanie funkcjonalności do już zaimplementowanych klas (dekorowanie).
namespace Patterns\Structural\Decorator;
interface PiwoszInterface
{
public function pije();
}
class Piwosz implements PiwoszInterface
{
protected $nazwaPiwa;
public function __construct($nazwaPiwa)
{
$this->nazwaPiwa = $nazwaPiwa;
}
public function pije()
{
return $this->nazwaPiwa;
}
}
abstract class Decorator implements PiwoszInterface
{
protected $wrapped;
public function __construct(PiwoszInterface $wrappable)
{
$this->wrapped = $wrappable;
}
}
class SmutnyPiwosz extends Decorator
{
public function pije()
{
$piwo = $this->wrapped->pije();
if ($piwo != 'warka') {
echo 'O jakie słodkie';
}
}
}
class WesolyPiwosz extends Decorator
{
public function pije()
{
$piwo = $this->wrapped->pije();
if ($piwo != 'porter') {
echo 'Ale się naebaem';
}
}
}
$piwosz = new Piwosz('warka');
$smutnyPiwosz = new SmutnyPiwosz($piwosz);
$smutnyPiwosz->pije();
$piwosz2 = new Piwosz('porter');
$wesolyPiwosz = new WesolyPiwosz($piwosz);
$wesolyPiwosz->pije();Dependency Injection
Dependency Injection – wstrzykiwanie obiektów i ich zależności celem uzyskania podatnego na testowanie i skalowalnego rozwiązania.
namespace Patterns\Structural\DI;
/**
* Interface PiwoInterface
*/
interface PiwoInterface
{
public function otworz();
public function pij();
}
abstract class PiwoAbstract
{
protected $nazwa;
public function __construct($nazwa)
{
$this->nazwa = $nazwa;
}
}
class Piwo extends PiwoAbstract implements PiwoInterface
{
public function otworz()
{
}
public function pij()
{
}
}
class Piwosz
{
protected $piwo;
public function __construct(PiwoInterface $piwo)
{
$this->piwo = $piwo;
}
public function pijPiwo()
{
return $this->piwo->otworz()->pij();
}
}
$piwo = new Piwo('warka');
$piwosz = new Piwosz($piwo);
$piwosz->pijPiwo();Facade
Facade – tworzy instancję sprawnie złożonego schematu działania na kilku obiektach. Najczęściej operuje na powiązanych tematycznie obiektach.
/**
* Class PiwoInterface
*/
interface PiwoInterface
{
/**
* Otwiera puszkę / butelkę
*/
public function otworz();
/**
* Pije
*/
public function napijSie();
/**
* Odkłada piwo
*/
public function odstaw();
}
/**
* Class BarInterface
*/
interface BarInterface
{
/**
* Składa zamówienie
*/
public function zamow();
/**
* Płaci
*/
public function zaplac();
}
class Facade
{
/**
* @var BarInterface
*/
protected $bar;
/**
* @var PiwoInterface
*/
protected $piwo;
/**
* @param PiwoInterface $piwo
* @param BarInterface $bar
*/
public function __construct(PiwoInterface $piwo, BarInterface $bar)
{
$this->piwo = $piwo;
$this->bar = $bar;
}
/**
* Pije piwo
*/
public function napijSiePiwa()
{
$this->bar->zamow();
$this->piwo->otworz();
$this->piwo->napijSie();
$this->piwo->odstaw();
$this->bar->zaplac();
}
}Proxy
Proxy – kiedy budowanie obiektów ma wysoki koszt, wtedy korzystamy z już utworzonych instancji i odwołujemy się poprzez proxy. Dojście do obiektu docelowego odbywa się poprzez proxy.
interface Foo
{
function something();
}
class FooSubject implements Foo
{
public function something()
{
/** ... */
}
}
class Proxy implements Foo
{
private $realObject;
public function something()
{
if ($this->realObject == null) {
echo 'Proxy';
$this->realObject = new FooSubject();
}
$this->realObject->something();
}
}
/** Użycie */
$proxy = new Proxy();
$proxy->something();Registry
Registry – przechowywanie danych z możliwością dostępu z jakiegokolwiek miejsca aplikacji.
abstract class Rejestr
{
/**
* Arrayable aggregator
*
* @var array
*/
protected static $wartosci = array();
/**
* Setter
*
* @param mixed $klucz
* @param mixed $wartosc
*/
public static function set($klucz, $wartosc)
{
self::$wartosci[$klucz] = $wartosc;
}
/**
* Getter
*
* @param mixed $klucz
* @return mixed
*/
public static function get($klucz)
{
return self::$wartosci[$klucz];
}
}Pozostałe wzorce
Repository
Repository – jeśli istnieje potrzeba oddzielenia dziedziny (domeny) od warstwy danych, wykorzystując kolekcje obiektów domenowych (entities). Umożliwia prowadzenie enkapsulacji obiektów zachowanych w pamięci i operacji na nich, wprowadzając obiektowo zorientowaną warstwę dostępu do tych danych.
namespace Patterns\Other\Repository;
interface WarstwaDanych
{
/**
* Uzupełnij tablicę obiektów w repozytorium
*
* @param mixed $data
* @return int
*/
public function persist($data);
/**
* Pobierz element
*
* @param int $id
* @return mixed
*/
public function retrieve($id);
}
class WarstwaDanychPamiec implements WarstwaDanych
{
/**
* @var array
*/
private $data;
/**
* @var int
*/
private $lastId;
/**
* Konstruktor
*/
public function __construct()
{
$this->data = array();
$this->lastId = 0;
}
/**
* Uzupełnij tablicę obiektów w repozytorium
*
* @param mixed $data
* @return int
*/
public function persist($data)
{
$this->data[++$this->lastId] = $data;
return $this->lastId;
}
/**
* Pobierz element
*
* @param int $id
* @return mixed
*/
public function retrieve($id)
{
return isset($this->data[$id]) ? $this->data[$id] : null;
}
}
class Post
{
/**
* @var int
*/
private $id;
/**
* @var string
*/
private $tytul;
/**
* @var string
*/
private $autor;
/**
* @param int $id
*/
public function setId($id)
{
$this->id = $id;
}
/**
* @return int
*/
public function getId()
{
return $this->id;
}
/**
* @param string $autor
*/
public function setAutor($autor)
{
$this->autor = $autor;
}
/**
* @return string
*/
public function getAutor()
{
return $this->autor;
}
/**
* @param string $tytul
*/
public function setTytul($tytul)
{
$this->tytul = $tytul;
}
/**
* @return string
*/
public function getTytul()
{
return $this->tytul;
}
}
class PostRepository
{
/**
* @var WarstwaDanych
*/
private $warstwaDanych;
/**
* Konstruktor
*
* @param \Patterns\Other\Repository\WarstwaDanychPamiec $warstwaDanych
*/
public function __construct(WarstwaDanychPamiec $warstwaDanych)
{
$this->warstwaDanych = $warstwaDanych;
}
/**
* Pobierz element z repozytorium na podstawie id
*
* @param int $id
* @return \Patterns\Other\Repository\Post
*/
public function getById($id)
{
$data = $this->warstwaDanych->retrieve($id);
if (is_null($data)) {
return null;
}
$post = new Post();
$post->setId($data['id']);
$post->setAutor($data['autor']);
$post->setTytul($data['tytul']);
return $post;
}
}
/** Użycie */
$warstwaDanych = new WarstwaDanychPamiec();
$warstwaDanych->persist(['id' => 1, 'autor' => 'Foo', 'tytul' => 'Foo']);
$repository = new PostRepository($warstwaDanych);
$repository->getById(1);Service Locator
Service Locator – kiedy istnieje potrzeba zaprojektowania luźnej i łatwej architektury dającej się łatwo testować i skalować. Za pomocą service locatora możesz zarejestrować serwis jako interfejs, zainicjalizować go w bootstrapie i korzystać z niego w każdym miejscu aplikacji bez konieczności znajomości tego, co jest w środku.
namespace Patterns\Other\ServiceLocator;
interface ServiceLocatorInterface
{
/**
* Czy serwis jest zapisany
*
* @param string $interface
*/
public function has($interface);
/**
* Zwróć serwis
*
* @param string $interface
*/
public function get($interface);
}
class ServiceLocator implements ServiceLocatorInterface
{
/**
* @var array
*/
private $services;
/**
* @var array
*/
private $instantiated;
/**
* @var array
*/
private $shared;
/**
* Konstruktor
*/
public function __construct()
{
$this->services = array();
$this->instantiated = array();
$this->shared = array();
}
/**
* Dodaj interfejs serwisu do tablicy z interfejsami zależnie.
* Serwis może być potraktowany jako wspólny.
*
* @param string $interface
* @param string $service
* @param boolean $share
*/
public function add($interface, $service, $share = true)
{
if (is_object($service) && $share) {
$this->instantiated[$interface] = $service;
}
$this->services[$interface] = (is_object($service) ? get_class($service) : $service);
$this->shared[$interface] = $share;
}
/**
* Czy informacja o interfejsie została już zapisana?
*
* @param string $interface
* @return boolean
*/
public function has($interface)
{
return (isset($this->services[$interface]) || isset($this->instantiated[$interface]));
}
/**
* Pobierz instancję serwisu
*
* @param string $interface
* @return \Patterns\Other\ServiceLocator\service
*/
public function get($interface)
{
// Zweryfikuj, czy instancja obiektu została już zapisana jako zainicjalizowana lub wspólna
if (isset($this->instantiated[$interface]) && $this->shared[$interface]) {
return $this->instantiated[$interface];
}
// W przeciwnym wypadku pobierz zarejestrowaną nazwę obiektu
$service = $this->services[$interface];
$object = new $service();
// Jeśli obiekt ma być wspólny, zapisz go do pamięci
if ($this->shared[$interface]) {
$this->instantiated[$interface] = $object;
}
return $object;
}
}
interface LogServiceInterface
{
}
class LogService
{
/**
* Zapisuje log, np. do pliku
*/
public function zapiszLog()
{
}
}
/** Użycie */
$serviceLocator = new ServiceLocator();
$logService = new LogService();
$serviceLocator->add(
'Patterns\Other\ServiceLocator\LogServiceInterface', $logService
);
$serviceLocator->get('Patterns\Other\ServiceLocator\LogServiceInterface')->zapiszLog();Metody optymalizacji baz danych
Relacyjność
- Stosujemy klucze obce i klucze główne, wykorzystując wartości numeryczne (najczęściej inty) jako typ danych kolumn łączonych.
- Pamiętamy o ustawieniach zachowania relacyjności podczas usuwania (kaskady, SET NULL (np. drzewa rekurencyjne)) i aktualizacji. Podczas usuwania użytkownika kaskada usunie dane ze wszystkich tabel odwołujących się do usuwanego wiersza. Podczas usuwania wiersza rodzica z drzewa master-slave, przy założeniu, że rodzic posiada dzieci, SET NULL ustawi dzieci jako rodziców.
Zapytania
- Nie używaj
SELECT *– zdefiniuj kolumny, których faktycznie potrzebujesz. - Jeśli zapytanie odpytuje więcej niż jedną tabelę, staraj się używać aliasów tabel. Zamiast:
SELECT autorzy.id, tytuly.id FROM autorzy INNER JOIN tytuly ON autorzy.id = tytuly.author_id;napisz:SELECT a.id, t.id FROM autorzy a INNER JOIN tytuly t ON a.id = t.author_id; - Staraj się korzystać z JOIN-ów zamiast podzapytań lub „starych” złączeń. Zamiast:
SELECT a.id, t.id FROM autorzy a, tytuly t WHERE a.id = t.author_id;napisz:SELECT a.id, t.id FROM autorzy a INNER JOIN tytuly t ON a.id = t.author_id; - Używaj maksymalnej liczby wiążących reguł w zapytaniach łączących tabele. Przykład:
SELECT k.nazwa, u.nrw, p.data FROM klienci k LEFT JOIN umowy u ON k.id_klienta = u.id_klienta LEFT JOIN platnosci p ON k.id_klienta = p.id_klienta AND u.id_umowy = p.id_umowy; - Pamiętaj, że poza INNER JOIN i LEFT JOIN istnieją również:
- OUTER JOIN – złączenie zewnętrzne:
Zwróci wszystkie wyniki z tabeli Customers i Orders, nawet jeśli dane z Orders nie będą miały „Customerów” z tabeli Customers.SELECT Customers.CustomerName, Orders.OrderID FROM Customers FULL OUTER JOIN Orders ON Customers.CustomerID = Orders.CustomerID ORDER BY Customers.CustomerName; - UNION – łączenie wyników:
Zwróci dane będące wynikiem zapytania dwóch SELECT-ów. Warunkiem poprawności zapytania jest tożsamość typów kolumn.SELECT City FROM Customers UNION SELECT City FROM Suppliers ORDER BY City;
- OUTER JOIN – złączenie zewnętrzne:
Widoki
Zapytania typu:
$select = new Select;
$select = $select->columns('...')
->from('...')
->join('...')
->join('...')
->join('...')
->where('...')
->order('...');Staraj się przerzucać na ujednolicone widoki, po to zostały stworzone, by z nich swobodnie korzystać:
CREATE VIEW view_name AS
SELECT column_name(s)
FROM table_name
WHERE condition;Widoki tymczasowe / tabele tymczasowe
Tabela tymczasowa (widok tymczasowy) to alias istniejącej tabeli, której czas istnienia rozpoczyna się od jej stworzenia, a kończy się wraz z zerwaniem połączenia klienta z systemem bazy danych.
Kiedy używać? Podczas przetwarzania (np. naliczania) dużych ilości danych, które wymagają dodatkowej agregacji i są wykorzystywane w obrębie sesji połączenia z serwerem bazy danych. Standardowy adapter obiektowy (architektura PHP, utrzymywanie miliona obiektów domenowych) może być nieefektywny w procesie obliczeniowym.
Przykład:
CREATE TEMPORARY TABLE PodsumowanieSprzedazyZMiesiaca (
nazwa_produktu VARCHAR(50) NOT NULL,
calkowita_sprzedaz DECIMAL(12,2) DEFAULT 0.00,
srednia_cena_jednostki DECIMAL(7,2) DEFAULT 0.00,
liczba_sprzedanych_jednostek INT UNSIGNED NOT NULL DEFAULT 0
);
/** Wyniki kosztownych operacji zapisujemy do tabeli tymczasowej */
INSERT INTO PodsumowanieSprzedazyZMiesiaca
(nazwa_produktu, calkowita_sprzedaz, srednia_cena_jednostki, liczba_sprzedanych_jednostek)
VALUES
('rurki męskie', 100.25, 90, 2);
/** Wykonujemy dalsze operacje */
/** Generujemy PDF-a wykorzystując zapisane dane */
$pdf = new Mock\PDF();
$collection = new Mock\Query('SELECT nazwa_produktu, calkowita_sprzedaz, srednia_cena_jednostki, liczba_sprzedanych_jednostek FROM PodsumowanieSprzedazyZMiesiaca');
$pdf->setDataProvider($collection);
$pdf->generate();Tabela tymczasowa zostanie automatycznie usunięta po zerwaniu połączenia. Można ją usunąć ręcznie:
DROP TABLE PodsumowanieSprzedazyZMiesiaca;Blackhole
Blackhole – specyficzny typ danych, który umożliwia zapis do tabel, przy czym dane giną bezpowrotnie. INSERT INTO daje rezultat, SELECT zawsze daje pusty wynik.
Kiedy użyć? Jeśli architektura twojego modułu lub systemu wykorzystuje (lub wymaga refactoringu) obserwatory insertujące do tabel, z których nigdy nie korzystasz. Usunięcie tych tabel może spowodować rozszczelnienie systemu, a koszt refactoringu może być zbyt duży. Tym rozwiązaniem możesz zwiększyć wydajność i stabilność systemu.
ORDER BY, GROUP BY, HAVING
- Staraj się unikać ORDER BY i GROUP BY, gdyż wpływają znacząco na koszt zapytania.
- Staraj się sortować i grupować po typach numerycznych kolumn w pierwszej kolejności.
- Staraj się unikać klauzuli HAVING – operuje na wynikach zapytania przed sobą.