Ein Beispiel zur Objektorientierung

Artikel zur Objektorientierung gibt es schon genügend (Wikipedia). Ich möchte hier anhand eines Beispiels die Unterschiede zur prozeduralen Programmierung verdeutlichen.

require 'globalconfig.php';

function paypalTransaction($amount, $buyerEmail, $receiverEmail)
{
   // make a transaction with Paypal
   // ...

   return $success;
}

function payoneTransaction($amount, $buyerEmail, $receiverEmail)
{
   // make a transaction with Payone
   // ...

   return $success;
}

function informGame($gameId, $userId, $amount)
{
   switch ($gameId) {
      case 1:
         $url    = 'https://game1.example.com/payment';
         $params = ['user' => $userId, 'money' => $amount];
         break;
      case 2:
         $url    = 'https://game2.example.com/payment';
         $params = ['player' => $userId, 'gold' => $amount];
         break;
   }

   $paramStr = [];
   foreach ($params as $k => $v) {
      $paramStr[] = "$k=$v";
   }

   return file_get_contents($url.'?'.implode('&', $paramStr));
}

// do some payment
$gameId = (int)$_POST['game'];
$userId = (int)$_POST['user'];
$buyer  = $_POST['buyer'];
$amount = abs((int)$_POST['amount']);
$method = $_POST['method'];

$success = false;
switch ($method) {
   case 'paypal':
      $success = paypalTransaction($amount, $buyer, $globalConfig['paypalAccountEmail']);
      break;
   case 'payone':
      $success = payoneTransaction($amount, $buyer, $globalConfig['payoneAccountEmail']);
      break;
}

if ($success) {
   $gameResponse = informGame($gameId, $userId, $amount);

   if ($gameResponse != 'ok') {
      logError('Game failed!');
   }
} else {
   logError('Bad news!');
}

Hier haben wir also die Grundzüge einer Bezahlfunktion. Zunächst muss das Geld transferiert werden und anschließend – je nach Erfolg – wird einem Spieler etwas in einem Spiel gutgeschrieben. Es müssen also verschiedene Entscheidungen anhand von Eingangsparametern getroffen werden (switch). Eine Erweiterung an dieser Stelle, bedeutet einen weiteren case anzulegen.

Nun die Variante in OOP.

class TransactionException extends Exception {}

abstract class Transaction
{
   protected $amount, $buyerEmail, $receiverEmail;

   public function setAmount($amount)
   {
      $this->amount = $amount;
      return $this;
   }

   public function setBuyerEmail($buyerEmail)
   {
      $this->buyerEmail = $buyerEmail;
      return $this;
   }

   public function setReceiverEmail($receiverEmail)
   {
      $this->receiverEmail = $receiverEmail;
      return $this;
   }

   protected function checkInput()
   {
      if ($this->amount <= 0) {
         throw new TransactionException('Negative Amount');
      }
      // more checks ...
   }

   final public function execute()
   {
      $this->checkInput();
      $this->sendRequest();
   }

   /**
    * @param string $type
    *
    * @throws TransactionException
    * @return self
    */
   public static function factory($type)
   {
      $className = ucfirst($type).'Transaction';

      if (!class_exists($className, true)) {
         throw new TransactionException("Undefined type: $type");
      }

      return new $className();
   }

   abstract protected function sendRequest();
}

class PaypalTransaction extends Transaction
{
   protected function sendRequest()
   {
      // make a transaction
      if (!$success) {
         throw new TransactionException('Failed to contact Paypal');
      }
   }
}

class PayoneTransaction extends Transaction
{
   protected function sendRequest()
   {
      // ...
   }
}

class CommunicatorException extends Exception {}

abstract class Communicator
{
   protected $userId, $amount;

   public function informGame($userId, $amount)
   {
      $this->userId = (int)$userId;
      $this->amount = (int)$amount;

      $paramStr = [];
      foreach ($this->getParams() as $k => $v) {
         $paramStr[] = "$k=$v";
      }

      $success = file_get_contents($this->getUrl().'?'.implode('&', $paramStr));

      if ($success != 'ok') {
         throw new CommunicatorException('Game did not respond.');
      }
   }

   /**
    * @param $gameId
    *
    * @return self
    * @throws CommunicatorException
    */
   public static function factory($gameId)
   {
      $className = 'Game'.((int)$gameId).'Communicator';

      if (!class_exists($className, true)) {
         throw new CommunicatorException("Undefined Game: $gameId");
      }

      return new $className();
   }

   abstract protected function getUrl();

   abstract protected function getParams();
}

class Game1Communicator extends Communicator
{
   protected function getUrl()
   {
      return 'https://game1.example.com/payment';
   }

   protected function getParams()
   {
      return ['user' => $this->userId, 'money' => $this->amount];
   }
}

class Game2Communicator extends Communicator
{
   protected function getUrl()
   {
      return 'https://game2.example.com/payment';
   }

   protected function getParams()
   {
      return ['player' => $this->userId, 'gold' => $this->amount];
   }
}

class Payment
{
   /** @var Transaction */
   private $transaction;
   /** @var Communicator */
   private $communicator;

   public function __construct(Transaction $transaction, Communicator $communicator)
   {
      $this->transaction  = $transaction;
      $this->communicator = $communicator;
   }

   public function handle($userId, $amount)
   {
      try {
         $this->transaction->execute();
         $this->communicator->informGame($userId, $amount);
      } catch (TransactionException $e) {
         // some logging
      } catch (CommunicatorException $e) {
         // some logging
      }
   }
}

// do some payment
$communicator = Communicator::factory($_POST['game']);
$transaction  = Transaction::factory($_POST['method']);
$payment      = new Payment($transaction, $communicator);
$payment->handle($_POST['user'], $_POST['amount']);

Für eine Erweiterung muss jetzt lediglich einen neue Klasse angelegt werden. Die übergeordnete abstrakte Klasse diktiert dabei notwendige Funktionen und sorgt dafür das wichtige Checks im Vorfeld ausgeführt werden. Durch Exceptions wird die Ablaufkontrolle vereinfacht.