Monthly Archives: Mart 2013

Yazılım ve Sistem Mühendisliği

Bi Fonksiyon Yazdım…

Bugün de yazılım dünyasının kanayan başka bir yarasına deyinelim dedim. :)

Kod tekrarının sıkca yapıldığı umumiyetle de OOP olmayan projelerde aynı işi farklı parametrelerle gerçekleştirip benzer çıktılar üreten fonksiyonlar görmeniz olasıdır. Genellikle bu tarz durumlarda tepesi atan bir geliştirici refactoring yapma aşkıyla sistemdeki benzer tüm fonksiyonları birleştirip leb demeden çerez tabağı üreten fonksiyonlar geliştirir.:) Bu yaklaşımın ne kadar yanlış olduğunu anlamak için öncelikle şu yazıyı incelemenizi öneririm.

Bugün de yine birlikte gerçek hayattan bir örnek inceleyeceğiz. Elektronik ticaret şirketleri, oto servisleri veya perakende ürün/hizmet satışı yapılan yerlerde müşteriye satılan bir ürün ve bu ürünün satışı ile ilgili değişik iş modelleri vardır. Partenere göre, satış kurallarına göre, kullanılan kupona göre indirim, vergi ve ürün fiyatı değişkenlik gösterebilir ve siz bu problemi “Bir tane fonksiyon yazdım…” yaklaşaımıyla çözemezsiniz. Peki bu problemin ilacı ne ola ?

Örnek olarak inceleyeceğimiz yapıda bir elektronik ticaret sitesinin sepet hesaplamasını konu alacağız. Satış modelimiz yüzdelikli ve sabit tutarlı kupon kullanımından ibaret olacak.

Sepet hesaaplaması yaparken sırasıyla aşağıdaki işlemleri yapmamız gerekiyor.

  • Kupon tipine göre indirim tutarını hesaplamak.
  • Ürün ve sepet bazında KDV tutarı hesaplamak.
  • Sepet toplamını hesaplamak.

Strategy design pattern İlk maddeyi ele alacak olursak kullanacağımız kuponun tipine bağlı olarak indirim tutarını farklı algoritmalarla hesaplamamız gerektiğinden burada kullanabileceğimiz en doğru tasarım kalıbı strateji tasarım desenidir. Strateji tasarım deseni, belirli bir işlemin farklı algoritmalarla gerçekleştirilmesi gerektiği durumlarda kullanılan oluşumsal bir tasarım desenidir. Daha fazla bilgi için bu yazıyı inceleyebilirsiniz.

Decorator Design Pattern Örneği genel olarak ele aldığımızda ise indirim, kdv ve sepet toplamı gibi hesaplamaların ayrı ayrı yapılarak siparişi güncellemesi gerektiğini görüyoruz. Bu durumda kullanabileceğimiz en doğru tasarım deseni ise dekoratör tasarım desenidir. Dekoratör tasarım deseni, bir nesnenin özelliklerinin dinamik olarak değiştirilmesi gerektiği durumlarda kullanılır. Daha fazla bilgi için bu yazıyı inceleyebilrsiniz.

Kodlama kısmı yazıyı biraz uzattığı için sizleri sıkmamak adına bu yazıda yalnızca kupon hesaplayıcıyı inceliyor olacağız.

Öncelikle sipariş ve siparişe bağlı ürün sınıflarımızı oluşturalım.

<?php 
class Order
{
	/**
	 * @var string
	 */
	public $orderNumber;
	
	/**
	 * @var float
	 */
	public $netTotal;
	
	/**
	 * @var float
	 */
	public $totalDiscount;
	
	/**
	 * @var float
	 */
	public $totalTax;
	
	/**
	 * @var float
	 */
	public $grandTotal;

	/**
	 * @var array
	 */
	public $items = array();
       
        /**
         * @string
         */
        public $couponCode;

        /**
         * @int
         */
        public $couponType;
}

class Product
{
	/**
	 * @var string
	 */
	public $sku;
	
	/**
	 * @var float
	 */
	public $netPrice;
	
	/**
	 * @var float
	 */
	public $discountPercent;
	
	/**
	 * @var float
	 */
	public $discountAmount;
	
	/**
	 * @var float
	 */
	public $taxPercent;
	
	/**
	 * @var float
	 */
	public $taxAmount;
	
	/**
	 * @var float
	 */
	public $lineTotal;
}

Kupon hesaplayıcılarımız aynı işi farklı algoritmalarla gerçekleştiren iki farklı sınıf olacağına göre belirli bir arayüze tabi olmaları gerekir.

interface ICouponCalculator
{
	const CALCULATOR_FIXED = 1;
	const CALCULATOR_PERCENT = 2;
	
	/**
	 * @param Product $product
	 * @return float
	 */
	public function calculate(Product $product);
}

İndirim miktarı sepete dağıtılacağından indirim miktarı (oran veya sabit tutar olarak) ve toplam tutar verilerini hesaplayıcı örneğini oluştururken daha sonra kullanmak üzere saklıyoruz. Bu işlemi soyut hesaplayıcı sınıfımızın __construct() metodu içerisinde gerçekleştireceğiz.

abstract class AbstractCouponCalculator
{
	/**
	 * @var float
	 */
	protected $_discount_amount;
	
	/**
	 * @var float
	 */
	protected $_total_amount;
	
	/**
	 * @var float
	 */
	protected $_rounding_errors;
	
	/**
	 * @param float $discountAmount
	 * @param float $totalAmount
	 */
	public function __construct($discountAmount, $totalAmount)
	{
		$this->_discount_amount = $discountAmount;
		$this->_total_amount = $totalAmount;
	}
}

Ön hazırlıklarımızı tamamladığımıza göre hesaplayıcılarımızı kodlamaya başlayabiliriz. İlk olarak sabit tutarlı hesaplayıcımızı kodlayarak başlayalım.

class FixedCouponCalculator extends AbstractCouponCalculator implements ICouponCalculator
{
	/**
	 * (non-PHPdoc)
	 * @see ICouponCalculator::calculate()
	 */
	public function calculate(Product $product)
	{
		$percentage = $product->netPrice / $this->_total_amount;
		$discountAmount = $this->_rounding_errors + ($this->_discount_amount * $percentage);
                //yuvarlamadan kaynaklanan hatalari ihmal etmiyoruz.
		$this->_rounding_errors = $discountAmount - round($discountAmount);
                return $discountAmount;
	}	
}

Şimdi de oransal kupon hesaplayıcımızı kodlayalım.

class PercentCouponCalculator extends AbstractCouponCalculator implements ICouponCalculator
{
	/**
	 * (non-PHPdoc)
	 * @see ICouponCalculator::calculate()
	 */
	public function calculate(Product $product)
	{
		$discountAmount = $product->netPrice * $this->_discount_amount;
		return $discountAmount;
	}
}

Geldik hesaplama stratejimizi belirlemeye. Şimdi de kupon tipine göre hesaplama yapacak strateji sınıfımızı kodlayalım.

class CouponCalculator implements ICouponCalculator
{
	/**
	 * @var ICouponCalculator
	 */
	private $_calculator;
	
	/**
	 * @param int $calculatorType
	 * @param float $discountAmount
	 * @param float $totalAmount
	 */
	public function __construct($calculatorType, $discountAmount, $totalAmount)
	{
		switch($calculatorType) {
			case ICouponCalculator::CALCULATOR_FIXED:
				$this->_calculator = new FixedCouponCalculator($discountAmount, $totalAmount);
				break;
			case ICouponCalculator::CALCULATOR_PERCENT:
				$this->_calculator = new PercentCouponCalculator($discountAmount, $totalAmount);
				break;
		}
	}
	
	/**
	 * (non-PHPdoc)
	 * @see ICouponCalculator::calculate()
	 */
	public function calculate(Product $product)
	{
		return $this->_calculator->calculate($product);
	}
} 

Bir sonraki yazıda dekoratör tasarım desenini kullanarak sizlerle birlikte sepet hesaplayıcısını kodlayacağız.

Yazılım ve Sistem Mühendisliği

Observer Tasarım Kalıbı ve Olay Güdümlü Programlama

Üzerinde çalıştığım bir projeye kısa bir çay molası vermişken sizlerle çok sevdiğim ve şu an üzerinde çalıştığım projede de kullandığım gözlemci (observer) tasarım kalıbı ve olay güdümlü programlama konularndan kısaca bahsetmek istiyorum.

87628254
Olay güdümlü programlama, iç veya dış etkenlerle meydana gelen olaylar doğrultusunda program akışının değiştirilmesi gerektiği durumlarda kullanılan bir tekniktir. Bir e-ticaret sitesinde ziyaretcinin herhangibir nedenle alışverişi tamamlayamaması durumunda belirli bir kullanıcı gurubunun haberdar edilmesi, herhangibir servisle iletişim sorunu olması durumunda aksiyon alınması gibi durumlar, olay güdümlü programlama gerektirir.

Observer
Observer tasarım kalıbı ise birbirleriyle bire-çok ilişki içindeki nesnelerden herhangibirinde değişiklik olması durumunda bir merkez üzerinden diğer nesnelerin haberdar edilmesi gerektiği durumlarda kullanılır ve olay güdümlü programlama için biçilmiş kaftandır :)

Dilerseniz gelin hemen birlikte gerçek hayattan bir örnek inceleylim.

Bir elektronik ticaret şirketinde çalışıyoruz. Saat başı zamanlanmış bir görevle gelen siparişleri lojistik şirketine iletiyoruz. Ancak siparişlerin herhangibir nedenle iletilememesi durumunda 1 dk bekleyip tekrar deneme gerçekleştirmek istiyoruz.

Kod örneğine geçmeden önce gözlemci tasarım kalıbı ve olay güdümlü programlama arasındaki ilişki konusunda kısa bir bilgi vermekte yarar var. Observer tasarım desenini kullanarak yazılımımızın akışıını olaylarla değiştirebilmek için olayın gerçekleştiği yeri merkez, olayı dinleyen bölümü observer olarak düşünmeliyiz.

Aşağıdaki gibi bir lojistik arayüzümüz var. Tüm lojistik implementasyonlarımız bu arayüzü implement eder.

interface ILogistic
{
	/**
	 * @param string $articleCode
	 * @param string $refNumber
	 * @return bool
	 */
	public function send($articleCode, $refNumber);
	
	/**
	 * @param string $refNum
	 * @return string
	 */
	public function getStatus($refNum);
}

Basit ve anlaşılır olması açısından herbir gözlemcinin tek bir olay içerdiğini varsayıyoruz. Aşağıda gözlemci sınıflarımızın implement edeceği arayüz kodu yeralıyor.

interface IObserver
{
	/**
	 * @param Event $event
	 */
	public function invoke(Event $event);
}

Olayın gerçekleşeceği bölümün implement edeceği IObservable arayüzünü kodlayalım.

interface IObservable
{
	/**
	 * @param IObserver $observer
	 * @param string $eventName
	 */
	public function attach(IObserver $observer, $eventName);
	
	/**
	 * @param Event $event
	 */
	public function fireEvent(Event $event);
}

Olay gerçekleştiğinde olayla ilgili bilgileri gözlemciye (observer) taşıyacak olay sınıfımızı kodlayalım.

class Event
{
	/**
	 * @var string
	 */
	private $_event_type;
	
	/**
	 * @var IObservable
	 */
	private $_source;
	
	/**
	 * @var string
	 */
	private $_last_called_method;
	
	/**
	 * @var array
	 */
	private $_arguments;
	
	/**
	 * @param string $eventName
	 * @param IObservable $source
	 * @param string $lastCalledMethod
	 * @param array $arguments
	 */
	public function __construct($eventName,  IObservable $source, $lastCalledMethod, array $arguments)
	{
		$this->_event_name = $eventName;
		$this->_source = $source;
		$this->_last_called_method = $lastCalledMethod;
		$this->_arguments = $arguments;
	}
	
	/**
	 * @return string
	 */
	public function getEventName()
	{
		return $this->_event_type;
	}
	
	/**
	 * @return IObservable
	 */
	public function getSource()
	{
		return $this->_source;
	}
	
	/**
	 * @return string
	 */
	public function getLastCalledMethod()
	{
		return $this->_last_called_method;
	}
	
	/**
	 * @return array
	 */
	public function getArguments()
	{
		return $this->_arguments;
	}
}

Lojistik implementasyonlarımızın devralacağı soyut sınıfımızı kodlayalım.

abstract class LogisticAbstract
{	
	/**
	 * @param string $articleCode
	 * @param string $refNum
	 * @return array
	 */
	abstract protected function _getSendRequest($articleCode, $refNum);
	
	/**
	 * @param string $refNum
	 * @return array
	 */
	abstract protected function _getStatusRequest($refNum);
	
	/**
	 * @param string $apiResponse
	 * @return bool
	 */
	abstract protected function _parseSendRequest($apiResponse);
	
	/**
	 * @param string $apiResponse
	 * @return string
	 */
	abstract protected function _parseStatusRequest($apiResponse);
	
	/**
	 * @param string $url
	 * @param array $data
	 * @throws Exception
	 * @return string
	 */
	protected function _sendRequest($url, array $data)
	{
		$postData = http_build_query($data);
		$ch = curl_init();
		$options = array(CURLOPT_URL => $url,
					    CURLOPT_RETURNTRANSFER => true,
					    CURLOPT_POST 	   => true,
					    CURLOPT_POSTFIELDS 	   => $postData);
		curl_setopt_array($ch, $options);
		$response = curl_exec($ch);
	    if($error = curl_errno($ch)) {
	    	throw new Exception('Error occurred while sending data to remote server. Detail:' . $error, '');
	    }
	    return $response;
	}
}

Lojistik şirketimizin adı örnek lojistik olsun.

class OrnekLojistik extends LogisticAbstract implements ILogistic, IObservable
{
	/**
	 * @var array
	 */
	private $_observers = array();
	
	/**
	 * @param string $articleCode
	 * @param string $refNumber
	 * @return array
	 */
	protected function _getSendRequest($articleCode, $refNumber)
	{
		$xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>
				<OrnekLogisticRequest>
				  <transaction>
				  <type>send</type>
				  <articleCode>{$articleCode}</articleCode>
				  <refNum>{$refNumber}</refNum>
				  </transaction>
				</OrnekLogisticRequest>";
		return array('data' => $xml);
	}
	
	/**
	 * @param string $refNum
	 * @return array
	 */
	protected function _getStatusRequest($refNum)
	{
		//...
	}
	
	/**
	 * @param string $apiResponse
	 */
	protected function _parseSendRequest($apiResponse)
	{
		try {
			$xml = new SimpleXMLElement($xml);
			return $xml->Response->Status == 'OK';		
		} catch(Exception $e) {
			throw new Exception('Unexpected Response. Response message:' . $apiResponse);
		}
	}
	
	/**
	 * @param string $apiResponse
	*/
	protected function _parseStatusRequest($apiResponse)
	{
		//...
	}
	
	/**
	 * @param string $articleCode
	 * @param string $refNumber
	 */
	public function send($articleCode, $refNumber)
	{
		try {
			$request = $this->_getSendRequest($articleCode, $refNumber);
			$apiResponse = $this->_sendRequest('http://ws.orneklojistik.com/OrnekWS.php', $request);
			$response = $this->_parseSendRequest($apiResponse);
			if(! $response ) {
				$this->fireEvent(new Event('shipping_failed', $this, __METHOD__, func_get_args() ));
			}
			return $response;
		} catch(Exception $e) {
			$this->fireEvent(new Event('shipping_failed', $this, __METHOD__, func_get_args() ));
                        return false;
		}
	}
	
	/**
	 * @param string $refNum
	*/
	public function getStatus($refNum)
	{
		//...		
	}
	
	/**
	 * (non-PHPdoc)
	 * @see IObservable::attach()
	 */
	public function attach(IObserver $observer, $eventName)
	{
		if( ! isset( $this->_observers[$eventName] ) ) {
			$this->_observers[$eventName] = array();
		}
		$this->_observers[$eventName][] = $observer;
	}
	
	/**
	 * (non-PHPdoc)
	 * @see IObservable::fireEvent()
	 */
	public function fireEvent(Event $event)
	{
		if( isset( $this->_observers[$event->getEventName() ] ) ) {
			/* @var $event IObserver  */
			foreach ($this->_observers[$event->getEventName()] as $observer) {
				$observer->invoke($event);
			}
		}
	}
}

Lojistik şirketine gönderim sırasında sorun yaşamamız durumunda çalışacak gözlemci sınıfını kodlayalım.

class OrnekLojistik_Observer_OnFail implements IObserver
{
	/**
	 * @var bool
	 */
	private $_tried = false;

	/**
	 * (non-PHPdoc)
	 * @see IObserver::invoke()
	 */
	public function invoke(Event $event)
	{
		if( ! $this->_tried ) {
			sleep(60);
			call_user_method($event->getLastCalledMethod(), $event->getSource(), $event->getArguments());
		}	
	}
}

Geldik zamanlanmış görevimize. Buyrun size zamanlanmış görev…

class SendLogisticCommand implements ICommand
{
	/**
	 * @param array $args
	 */
	public function run($args)
	{
		$products = array('ABC123', 'DEF234', 'GHI123');
		
		$logistic = new OrnekLojistik();
		$observer = new OrnekLojistik_Observer_OnFail();
		$logistic->attach($observer, 'shipping_failed');
		
		foreach($products as $product) {
			$logistic->send($product, time() );
		}
	}
}

Biraz uzun oldu sanırım :) Hızlıca hazırladığım için ufak tefek hiyerarşi sıkıntıları olabilir ancak genel anlamda sizlere fikir verebileceğini ümit ediyorum.

Programa Dilleri ve Çatıları

Pratik Zend Config Kullanımı

Zend framework ile proje geliştirirken konfigürasyon dosyalarını okumak için sıklıkla kullandığım kütüphanemin kaynak kodlarını sizlerle paylaşmak istedim.

MyVendor/Config.php :

<?php
class MyVendor_Config extends MyVendor_Pattern_Singleton
{
    private $_config = array();

    const FILE_TYPE_JSON = 'Json';
    const FILE_TYPE_XML = 'Xml';
    const FILE_TYPE_INI = 'Ini';
    const FILE_TYPE_YAML = 'Yaml';

    /**
     * returns configuration Data.
     * @param string $fileName
     * @param string $section
     * @return object:
     */
    public function loadFromFile($fileName, $section = null)
    {
        $key = $fileName . '_' . $section;
        if( ! array_key_exists( $key, $this->_config ) ) {
            $this->_config[$key] = $this->_getAdapter($fileName, $section);
        }

        return $this->_config[$key];
    }

    /**
     * returns file extension.
     * @param string $fileName
     * @return string
     */
    private function _getExtension($fileName)
    {
        $explodedFileName = array_reverse(explode('.', $fileName));
        $extension = reset($explodedFileName);
        return ucfirst($extension);
    }

    /**
     * returns reader adapter.
     * @param string $fileName
     * @param string $section
     * @throws MyVendor_Config_Exception
     * @return Zend_Config_Json|Zend_Config_Xml|Zend_Config_Ini|Zend_Config_Yaml
     */
    private function _getAdapter($fileName, $section = null)
    {
        $extension = $this->_getExtension($fileName);
        switch($extension) {
            case self::FILE_TYPE_JSON:
                return new Zend_Config_Json($fileName, $section);
            case self::FILE_TYPE_XML:
                return new Zend_Config_Xml($fileName, $section);
            case self::FILE_TYPE_INI:
                return new Zend_Config_Ini($fileName, $section);
            case self::FILE_TYPE_YAML:
                return new Zend_Config_Yaml($fileName, $section);
            default:
                throw new MyVendor_Config_Exception('Unknown file type:' . $extension, '');
        }
    }
}

MyVendor/Pattern/Singleton.php :

<?php
abstract class MyVendor_Pattern_Singleton
{
    private static $_instance = array();
    
    final static public function getInstance()
    {
        $class = get_called_class();
        if( ! array_key_exists($class, self::$_instance) ) {
            self::$_instance[ $class ] = new $class;
        }
        return self::$_instance[$class];
    }
}

MyVendor/Config/Exception.php :

<?php
class Sd_Config_Exception extends Exception
{}

Örnek Kullanım:

<?php
$file = "myconfig.xml";
$config = MyVendor_Config::getInstance()->loadFromFile($file, APPLICATION_ENV);
Programa Dilleri ve Çatıları

Eclipse PDT Auto Complete Sorunu

Eclipse PDT kullanıyorsanız, kodlama sırasında editörünüz otomatik tamamlama yapmıyorsa ve CTRL+SPACE ile zorladığınızda “No completions available” mesajını alıyorsanız:
– Projenizin üzerine sağ klik yapın ve açılan menüden “Properties” öğesine sol klik yapın.
– Açılan pencerede sol taraftaki listeden PHP Include Path öğesini seçin.
– libraries sekmesinde “Add External Source Folder” butonuna tıklayın.
– Açılan dialogdan projenizin kaynak kodunun olduğu dizini seçin ve ok butonuna basın.
– OK butonuna basarak properties penceresini kapatın.