Genel

Peder Bey’e Mektup

Asım GündüzBaba;

Valla daha geçen gün annemle kulaklarını çınlattık. Geçen yine birkaç entegreyle dünyayı kurtarıyorum, aklıma bilgisayarla yürüttüğüm ledleri sana gösterdiğimde “ne varki, iki transistörle yapardın bunu zaten” deyip daha orta okullu bir ergenkene bütün şevkimin içine edişin geldi :) Öyle demeyeydin iyiydi de… neyse.

10411723_10152925822963077_4931603895096145643_nHerhalde aynı dönemde olsa gerek, okul harçlıklarımı biriktirip uğruna aç kaldığım ADC0804 vardı ya, bu yıl sanırım 18 yaşına bastı, ellerinden öper ve henüz milli olmadı (henüz ayağına lehim değmedi.) Nasıl aldığımı öğrendikten sonra “Demek aç da yaşanabiliyormuş” diyip harçlığımı kesmiştin ya hani, herhalde ondan olsa gerek kullanmaya kıyamadım.

Son olarak hani “okulu bitirince ne yapıyorsan yap!” diyip yasakladıkların var ya, ben onları senden sonra da inadına yaptım. Hatta şu anda ekmeğimi bundan kazanıyorum. Zaten yasaklamasaydın büyük ihtimalle bir iki hevesimi alıp bir daha elimi sürmeyecektim. Bugün sahip olduğum hiçbirşeye belki de sahip olamayacaktım.

13301257_10154152573013077_9182928230089661715_oEvde sorun yok. Odam hala dağınık, hala yerlerde kablolar var ve hala birşeyleri tamir edeceğim derken bozuyorum. Huy işte…

Unutmadan eski evdeki televizyonun arkasındaki perde yanmıştı ya hani, onu ben yaktım ama valla istemeden oldu.

Asıl o değil de, ben bu tatlı rekabeti de seni de çok özledim.

Sevgiler,
Oğlun, İbrahim Gündüz

Genel

PHPKonf Geliyor…!

PHPKonfİstanbul PHP User Group yine sabırsızlıkla beklediğim muhteşem bir konferansa ev sahipliği yapıyor. 21-22 Mayıs 2016 tarihinde Bahçeşehir Üniversitesi, Beşiktaş yerleşkesinde gerçekleştirilecek olan etkinlikte paralel oturumlarla, yazılım dünyasından pek çok yerli ve yabancı konuşmacı, dinleyicileriyle buluşacak.

Eğer PHP geliştiricisiyseniz yerinizde olsam bu etkinliği kesinlikle kaçırmazdım. Zira SensioLabs kurucusu ve Symfony framework’ün babası Fabien Potencier, Doctrine core geliştiricilerinden Marco Pivetta ve Gianluca Arbezzano, SitePoint’in PHP editörü Bruno Skvorc, Yii framework’ün core geliştiricisi Alexander Makarov, Magento Evangelist Ben Marks ve Microsoft’tan PHP’in ve eklentilerinin neredeyse bütün desteğini sağlayan Pierre Joye, Ahmet Alp Balkan gibi(ve çok daha fazlası!) çok değerli isimleri bir arada görebilme şansına sahip olacağız.

Etkinlikle ilgili gelişmeleri şuradan takip edebilir; etkinlikle ilgili detayları buradan öğrenebilirsiniz.

Genel

Helva Yapacak Adam Aranıyor…

mahmut_kBundan yaklaşık 23 yıl önce, evde internet yokken sınıf arkadaşlarım hatırlar arka sırada ajandaya kod yazıyordum. Hem de it gibi yazıyordum. Çünkü programlamaya ayırdığım zaman ders çalışmaya ayırdığım zamanın kat be kat üstünde olduğundan peder bey bilgisayarı yasaklamıştı. Elimdeki tek kaynak msdos, cobol ve basic kitapları; evdeki en teknolojik cihaz çok değerli bir aile dostumuzun verdiği masaüstü bilgisayardı. Sadece 4 küsür MHz di (bugün IoT cilerin elinden düşürmediği arduinonun %25 hızında). Etrafta soru sorabilecek tek bir adam yoktu.

Velhasıl tüm bu imkansızlıklara rağmen ortaokula civarında hatırı sayılır cobol ve basic bilgisine sahip bir velet olmuştum. Internet bundan tam 10 küsür sene sonra evime girdi. Cep telefonu ve mobil internet sanırım üniversite döneminde cebime girdi. Stackoverflow… Yoktu!

Şevkinizi kırmak istemiyorum ama internet var, kitap var, soru sourlabilecek/kolay ulaşılabilecek bir dünya adam var, staj yapabileceğin zilyon tane şirket var, peki sen nerdesin Z kuşağı ??

Genel

Türk İnsanı, Kalite ve Boş Küme Kavramı

İlkokulda, matematik dersinde küme kuramı anlatılırken boş küme, genellikle konuşan maymunlar, uçan atlar gibi yanyana geldiğined birşey ifade etmeyen ve olması mümkün olmayan ifadelerle anlatılırdı. İşte Türk insanı, kalite ve iş güvenliği denildiğinde de bende hep aynı his uyanıyor.

***

İş GüvenliğiElektrik, elektronik, su tesisatı gibi hayati iş kollarında Türk insanının çalışmasını uluslararası yaptırımlarla yasaklamak gerektiğini düşünüyorum. Olmuyor çünkü. Çünkü Türk insanı her zaman elinin(!) ucuyla iş yapıyor. Çünkü kendimden biliyorum…!

***

Evimde aküsü biten güvenlik sisteminin akü özelliklerini öğrenmek için kapağını açtığımda, herhangibir yere monte edilmemiş olan transformatör avucumunun içine küt diye düşüverdi. Malesef ben de yukarıdaki özellikleri taşıyan bir adam olduğum için bu işlemi enerjiyi kesmeden yapmıştım. En fazla yapacağım kapağı açıp akünün üstünde yazan değerleri okumaktı, başıma ne gelebilirdiki ?…

Eğer trafo elime düştüğü sırada izole edilmemiş şebeke girişi uçları elime deyseydi belki elektrik akımına kapılıp, belki de üstünde bulunduğum merdivenden düşüp hayatımı kaybedebilirdim veya kalıcı bir sakatlığa sahip olabilirdim.

***

cakmak-ile-tuo-kontrol-600x337Yazıyı okuyup lütfen gereksiz milliyetçi duygularla yorumlamayın. Ağzıyla kablo açıp, gaz kaçağını çakmakla kontrol eden, avometreyi gereksiz görüp şebeke gerilimini short circuit ile kontrol eden ve hatta koca inşaat iskelesini iki küçük taş parçasının üstüne kurup bilmem kaçıncı katında tek ayağının üstünde gösteri yapan bir toplumdan bahsediyorum.

***

Vaktiyle bir vatandaşın yazdığı şu yazıyı da okumanızı öneriririm.

Genel

Logstash ile Veri Analizi

Logstash farklı kanallardan veri toplayıp, konfigürasyon seviyesinde filtrelerle belirli kuralalra göre parçalamanızı sağlayan ve farklı tipterde kanallara dağıtabilen gerçek zamanlı ve açık kaynaklı bir veri toplama moturudur.

Logstash, ayrıştırma işlemini kendi komut seti içerisinde klasik regex eşleştirmeleri ile yapabildiği gibi LOGLEVEL, TIMESTAMP_ISO8601, DATETIME.. gibi ön tanımlı çok sayıda veri tipleriyle de kolayca yapabilmenizi sağlar.

Logstash, TCP/UDP soketleri ve log dosyaları gibi basit kaynaklar dışında çeşitli streaming protokollerindan tutun, kafka, log4j, redis araçlardan github, heroku, twitter ve irc gibi servislere kadar pek çok kaynaktan beslenebilme yeteneğine sahiptir. Ayrıca tüm bu kaynaklardan aldığınız verileri belirlediğiniz kurallar çerçevesinde işledikten sonra dilerseniz email olarak gönderebilir, ya da elastic search üzerine yazarak Kibana gibi veri görselleştirme araçlarıyla analiz edebilrsiniz.

Logstash aynı anda birden fazla kaynaktan beslenip yine birden fazla kaynağa veri dağıtabilir. Desteklenen veri kaynakları ile ilgili olarak şuraya, aktarım kaynakları ile ilgili olarak buraya bakabilirsiniz.

Logstash, tüm sistem/servis loglarınızı toplamaktan, özel olarak yazacağınız filtrelerle ödeme sisteminizin loglarını işleyerek istatistiksel veriler çıkarmanıza olanak sağlayacak çok geniş bir kullanım alanına sahiptir.

NGINX Access Logları Logstash ile Nasıl İşlenir ?

Aşağıdaki adresten logstash uygulamasının güncel sürümünü indirelim.
https://www.elastic.co/downloads/logstash

Ben şu anki güncel sürüm olan 2.2.2 sürümünü indiriyorum.
https://download.elastic.co/logstash/logstash/logstash-2.2.2.tar.gz

$ wget https://download.elastic.co/logstash/logstash/logstash-2.2.2.tar.gz

Sıkıştırılmış logstash paketini açalım.

$ tar -zxvf logstash-2.2.2.tar.gz

Konfigürasyon dosya(ları)mızı barındıracağımız conf.d dizinini oluşturalım.

$ cd logstash-2.2.2
$ mkdir conf.d

Logstash konfigürasyon dosyası, json benzeri bir yapıya sahiptir. Konfigürasyon dosyası, veri kaynaklarının tanımlandığı input{}, işleme tanımlamalarının yapıldığı filter{} ve işlenen verinin aktarılacağı kaynaklarla ilgili tanımlamaların yapıldığı output{} olmak üzere üç bölümden oluşur.

input {
	# Bu bölümde logstash i besleyecek veri kaynakları 
	# ve veri kaynaklarının opsiyonları ile ilgili 
	# ayarlar yeralır.
}

filter {
	# Bu bölümde gelen verinin nasıl parse edileceği,
	# verinin hangi bölümünün hangi alanlarda tutulacağı
	# gibi tanımlamalar yeralır.
}

output {
	# Burada işlenen verinin hangi kaynaklara aktarılacağı 
	# ve kaynakların ayarları ile ilgili tanımlamalar yeralır.
}

Okuyacağımız veri, nginx access loglarında yeraldığına göre file eklentisini kullanarak dosyayı okumak için gerekli tanımlamaları gerçekleştirelim.

input {
	file {
		path => "/var/log/nginx/*-access*"
		exclude => "*.gz"
		start_position => "beginning"
	}
}

Yukarıdaki konfigürasyonda;

path => “/var/log/nginx/*” ifadesi, /var/log/nginx dizinindeki tüm dosyaların okunması gerektiğini logstash e söyler. *-access* kullanılmasının nedeni, logstash in güncel ve rotate edilmiş tüm access loglarını değerlendirmesini sağlamaktır. Ancak bu dizinde arşivlenen log dosyaları da barındırıldığından exclude komutunu kullanarak *.gz uzantılı arşiv dosyalarını hariç tutulmasını sağlıyoruz.

Logstash varsayılan olarak path bölümünde verilen dosya yada dosyaların sonuna eklenen satırları değerlendirmektedir. Ancak log stash in rate edilmiş logları da değerlendirebilmesi için start_position parametresine beginning değerini vererek, dosyaları başından itibaren değerlendirmesi gerektiğini logstash e söylüyoruz.

Artık tüm nginx access loglarını okuyabilir durumda olduğumuza göre, aşağıdaki örnek log çıktısının logstash tarafından incelenmesini sağlamaya hazırız.

157.55.39.132 - - [20/Mar/2016:06:49:05 -0400] "GET /tag/software-engineering/page/2/ HTTP/1.1" 200 9329 "-" "Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)"
157.55.39.132 - - [20/Mar/2016:06:49:06 -0400] "GET /tag/yii-framework/ HTTP/1.1" 200 14823 "-" "Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)"
157.55.39.132 - - [20/Mar/2016:06:49:07 -0400] "GET /goygoy-yonelimli-programlama-ggop/ HTTP/1.1" 200 14980 "-" "Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)"
157.55.39.132 - - [20/Mar/2016:06:49:07 -0400] "GET /django-uygulamalarinin-apache-uzerinde-sanal-ortam-virtualenv-ile-birlikte-calistirilmasi/ HTTP/1.1" 200 10964 "-" "Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)"
157.55.39.132 - - [20/Mar/2016:06:49:08 -0400] "GET /dekorator-tasarim-deseni/ HTTP/1.1" 200 13180 "-" "Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)"
157.55.39.132 - - [20/Mar/2016:06:49:08 -0400] "GET /cv/ HTTP/1.1" 200 9723 "-" "Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)"
157.55.39.132 - - [20/Mar/2016:06:49:09 -0400] "GET /tasarim-candir/ HTTP/1.1" 200 12253 "-" "Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)"
157.55.39.132 - - [20/Mar/2016:06:49:10 -0400] "GET /cem-karaca-5-nisan-1945-8-subat-2004/ HTTP/1.1" 200 9825 "-" "Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)"
157.55.39.132 - - [20/Mar/2016:06:49:10 -0400] "GET /page/2/?s=git+di HTTP/1.1" 200 33628 "-" "Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)"

Logdaki veriyi parse edecek filtre konfigürasyonunu oluşturalım.

filter {
	grok {
		match => {"message" => "%{COMBINEDAPACHELOG}+%{GREEDYDATA:extra_fields}"}
	}

	mutate {
		convert => ["response", "integer"]
		convert => ["bytes", "integer"]
		convert => ["responsetime", "float"]
	}

	geoip {
		source => "clientip"
		target => "geoip"
	}

	date {
		match => ["timestamp", "dd/MMM/YYYY:HH:mm:ss Z"]
	}
}

Yukarıda gördüğünüz filtre konfigürasyonunda grok, mutate, geoip ve date eklentilerini kullandık. Şimdi dilerseniz sırayla bu eklentilerin ne işe yaradıklarını ve konfigürasyondaki işlevlerini görelim.

grok eklentisi, belirli bir yapıya sahip olmayan girdiyi belirtilen patterne uygun olarak parse etmeye yarar. Parse edilen veriyi COMBINEDAPACHELOG gibi ön tanımlı değişken gruplarıyla parse edebileceğiniz gibi GREEDYDATA tipindeki belirli bir veriyi kendi istediğiniz bir değişkene atayabilirsiniz.
Yukarıdaki örnekde grok eklentisi, match parametresinden gönderilen paterne uygun verileri parse ederek ön tanımlı ve/veya paternde tanımlanan extra_fields gibi değişkenlere atamaktadır. COMBINEDAPACHELOG veri tipi, log verisinde yeralan istemci ip adresi, istek yapılan zaman, istek yapılan uç nokta, transfer edilen veri miktarı vb. verileri clientip, timestamp, request, bytes gibi değişkenlere aktarılmasını sağlar. GREEDYDATA tipi ise genel amaçlı bir veri tipidir. Yapısal olmayan ve COMBINEDAPACHELOG tarafından parse edilmeyen diğer veriler GREEDYDATA tipindeki extra_fields değişkenine aktarılır.

Desteklenen diğer grok paternleri için şu github reposunu inceleyebilirsiniz.

mutate eklentisi, işlenen veri sonucunda oluşan alanlarda değişiklik yapmaya yarayan filtre eklentisidir. Yukarıdaki örnekte mutate eklentisi, convert komutuyla COMBINEDAPACHELOG tarafından parse edilip response, bytes ve responsetime gibi alanlara yazılan sayısal verilerin, tam sayı tipine çevirerek (Logstash, yapısal olmayan log girdisindeki veriyi varsayılan olarak string tipinde parse eder.) gerektiğinde sayısal operasyonlara tabi tutulabilir durumda olmalarını sağlar.

geoip eklentisinin bu örnekteki en eğlenceli eklenti olduğunu söyleyebilirim. Kendisi, access logda parse edilen istemci IP adresinin, coğrafi konum bilgisini elde eder.

date eklentisi ise belirli bir alandaki zaman bilgisini parse ederek belirli bir alana yazmak veya logstash in dahili zaman damgası olarak kullanabilmek için kullanılır. Burada, logdan parse edilen tarih/saat bilgisi, kendisine uygun zaman formatı ile eşleştirilerek logstash UTC formatındaki dahili zaman damgası alanının üzerine yazılır. Date filtresi, target komutu ile özellikle farklı bir hedef alan belirtilmediği sürece eşleşen zaman bilgisini varsayılan olarak logstash in dahili zaman damgası alanı olan @timestamp alanına yazar.

Artık logumuzu parse edebilir durumda olduğumuza göre sıra geldi parse edilen veriyi aktaracağımız kaynağı tanımlamaya. Örnek çıktısını konsolda görebilmek adına stdout eklentisini kullanacağız. Ancak başta bahsettiğim gibi şuradan kullanabileceğiniz tüm eklentilerle ilgili detaylı bilgi alabilirsiniz.

İşlenen veri çıktısını konsola basmak için aşağıdaki konfigürasyonu oluşturuyoruz.

output {
	stdout {
		codec => "rubydebug"
	}
}

Burada stdout eklentisi, işlenen verinin konsola basılmasını sağlar. codec olarak belirtilen rubydebug ise awsome_print isimli ruby kütüphanesini kullanarak çıktının okunabilirliğini arttırır. Diğer codec seçenekleri için şu sayfayı inceleyebilirsiniz.

Oluşturduğumuz konfigürasyonu conf.d dizinine kaydettikten sonra artık uygulamayı çalıştırmaya hazırırz. Örnek konfigürasyonumuzun ismi

nginx_access_log.conf

olsun.

$ ./bin/logstash -f ./conf.d/nginx_access_log.conf

Uygulamayı çalıştırdığınızda, logstash size şöyle bir çıktı üretecektir.

...

{
        "message" => "157.55.39.132 - - [20/Mar/2016:06:49:10 -0400] \"GET /cem-karaca-5-nisan-1945-8-subat-2004/ HTTP/1.1\" 200 9825 \"-\" \"Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)\"",
       "@version" => "1",
     "@timestamp" => "2016-03-20T10:49:10.000Z",
           "host" => "king",
       "clientip" => "157.55.39.132",
          "ident" => "-",
           "auth" => "-",
      "timestamp" => "20/Mar/2016:06:49:10 -0400",
           "verb" => "GET",
        "request" => "/cem-karaca-5-nisan-1945-8-subat-2004/",
    "httpversion" => "1.1",
       "response" => 200,
          "bytes" => 9825,
       "referrer" => "\"-\"",
          "agent" => "\"Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)\"",
          "geoip" => {
                    "ip" => "157.55.39.132",
         "country_code2" => "US",
         "country_code3" => "USA",
          "country_name" => "United States",
        "continent_code" => "NA",
              "latitude" => 38.0,
             "longitude" => -97.0,
              "dma_code" => 0,
             "area_code" => 0,
              "location" => [
            [0] -97.0,
            [1] 38.0
        ]
    }
}


...
Genel

Bileşik (Composite) Tasarım Kalıbı

Composite design pattern, belirli bir nesne grubuna, tek bir nesneyi kullanıyormuşcasına muamele etmek için kullanılan yapısal bir tasarım desenidir.

Gerçek Hayat Örneği:

Problem:
2300 TL değerindeki Nikon D90 fotograf makinesi, 150 TL değerindeki sparkfun tripod ve 110 TL değerindeki şarjlı pil+şarj aleti seti bundle ürün halinde satışa sunulmak isteniyor.

Uygulama:
Elektronik ticaret sitelerinde ürünler, teker teker satılabildiği gibi yukarıdaki problemde belirtildiği gibi zaman zaman kit şeklinde de satılabilirler. Ancak sistemin her durumda tek ürüne de çoklu ürüne de benzer muamele uygulayarak indirim, kupon kodu kullanımı gibi diğer mevcut işlevlerini yerine getirebilmeye devam etmesi gerekmektedir. Composite tasarım deseni bu gibi durumlar için elverişli bir tasarım kalıbıdır.

Uygulamaya ilk olarak ürünümüzün özelliklerini tanımlayacak arayüz sınıfını oluşturarak başlayalım.
ProductInterface.php:

<?php
interface ProductInterface
{
    /**
     * @return string
     */
    public function getName();

    /**
     * @return string
     */
    public function getPrice();
}

Basit ürün ve bundle ürün tarafından kullanılacak ortak metodları içeren soyut ürün sınıfını oluşturalım.

AbstractProduct.php:

<?php
abstract class AbstractProduct implements ProductInterface
{
    /**
     * @var string
     */
    protected $name;
    
    /**
     * @var double
     */
    protected $price;

    /**
     * constructor
     * @param string $name
     * @param double $price
     */
    public function __construct($name, $price=null)
    {
        $this->name = $name;
        $this->price = $price;
    }

    /**
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * @return double
     */
    public function getPrice()
    {
        return $this->price;
    }
}

Fotograf makinesi ve tripod gibi tekil ürünleri temsil edecek basit ürün sınıfını tanımlayalım.

SimpleProduct.php:

<?php
class SimpleProduct extends AbstractProduct
{
    
}

Şarj aleti+pil seti ve diğer basit ürünleri kit haline getirmemizi sağlayacak bundle ürün sınıfını tanımlayalım.
BundleProduct.php:

<?php
class BundleProduct extends AbstractProduct
{
    /**
     * @var array
     */
    private $products;

    /**
     * constructor
     * @param string $name
     * @param array $products
     * @param double $price
     */
    public function __construct($name, array $products, $price=null)
    {
        $this->setProducts($products);
        parent::__construct($name, $price ? $price : $this->getTotalPrice());
    }

    /**
     * @param ProductInterface $product
     */
    private function addProduct(ProductInterface $product)
    {
        $this->products[] = $product;
    }   

    /**
     * @param array $products
     */
    private function setProducts(array $products)
    {
        if(count($products) <= 1) {
            throws new ValueErrorException('Bundle products must have more than one product!');
        }
        for($products as $product) {
            $this->addProduct($product);
        }
    }

    /**
     * @return duobule
     */
    public function getTotalPrice()
    {
        return array_sum(array_map(function($product) {
            return $product->getPrice();
        }, $this->products));
    }
}

Gerekli tüm sınıf tanımlamalarını tamamladığımıza göre artık örnek problemde tanımlanan senaryoyu oluşturabiliriz.

<?php
$photoMachine = new SimpleProduct('Nikon D90', 2300);
$tripod = new SimpleProduct('SparkFun Tripod', 150);

$chargerKit = new BundleProduct('Şarjlı Pil + Şarj aleti seti', array(
    new SimpleProduct('Şarj aleti', 80),
    new SimpleProduct('Pil', 30)
));

$bundleProduct = new BundleProduct('Amatör Fotoğraf Kiti', array(
    array($photoMachine, $tripod, $chargerKit)
));

echo $bundleProduct->getPrice(); // 2560

Genel

Cem Karaca – 5 Nisan 1945 – 8 Şubat 2004

Cem Karaca
Cem Karaca

Seni çok özledik…
Cem Karaca
(5 Nisan 1945 – 8 Şubat 2004)

Genel

Süzgeç Tasarım Deseni

Belirli bir nesne grubunun bir veya daha fazla kritere göre filtrelenebilmesini sağlayan yapısal tasarım desenidir. Tanımdan da anlaşılacağı üzere filtreleme işlemi tekil veya zincir halinde gerçekleştirilebilir.

Gerçek Hayat Örneği:

Dilerseniz bu örneği Dekoratör Tasarım Deseni yazısında söz ettiğimiz e-ticaret uygulaması üzerinden devam ettirelim.

Kısaca hatırlatmak gerekirse sitemizden aynı kategoriden 3 ürün alan müşteriye 2 fiyatı ödetecek ve 10 TL indirim vereecktik. Bu işlemi yaparken dekoratör ve strateji tasarım desenlerinden yararlanmıştık. Şimdi bu örneği biraz daha özelleştirelim.

Kullanıcımızın, yeni yıl kategorisinden satın alacağı 3 adet 100 TL üzeri ürün için 2 fiyatı ödetelim ve 10 TL indirim uygulayalım.

Uygulama:

Örnekde belirttiğimiz şartlara uygun olan sepet ürünlerini filtrelemek için oluşturacağımız filtrelerin implement edeceği arayüz sınıfını oluşturalım.

<?php
namespace Promotion;

interface FilterInterface
{
    /**
     * @param Traversable $items
     * @param array $args
     * @return Generator
     */
    public function apply(Traversable $items, array $args=array());
}

Kullanıcıya sağlayacağımız fırsatın ilk şartı kategori ekseninde olduğuna göre sepet öğelerini kategori bazlı filtreleyecek filtre sınıfını kodlayalım.

<?php

namespace Promotion\Filter;

use Promotion\FilterInterface;

class CategoryFilter implements FilterInterface
{
    /**
     * @param Traversable $items
     * @param array $args
     * @return Generator
     */
    public function apply(Traversable $items, array $args=array())
    {
        foreach($items as $item) {
            if( $item->getCategory()->getId() == $args['category_id'] ) {
                yield $item;
            }
        }
    }
}

Bir sonraki ölçütümüz olan tutar bilgisi için yeni bir filtre sınıfı oluşturalım.

<?php

namespace Promotion\Filter;

use Promotion\FilterInterface;

class PriceFilter implements FilterInterface
{
    const CRITERIA_EQUAL = 'eq';
    const CRITERIA_LESS_THAN = 'lt';
    const CRITERIA_GREATER_THAN = 'gt';
    const CRITERIA_LESS_THAN_OR_EQUAL = 'lte';
    const CRITERIA_GREATER_THAN_OR_EQUAL = 'gte';

    /**
     * @param Traversable $items
     * @param array $args
     * @return Generator
     */
    public function apply(Traversable $items, array $args=array())
    {
        foreach($items as $item) {
            switch($args['condition']) {
                case self::CRITERIA_EQUAL:
                    if($item->getPrice() == $args['price']) {
                        yield $item;
                    }
                    break;
                case self::CRITERIA_LESS_THAN:
                    if($item->getPrice() < $args['price']) {
                        yield $item;
                    }
                    break;
                case self::CRITERIA_GREATER_THAN:
                    if($item->getPrice() > $args['price']) {
                        yield $item;
                    }
                    break;
                case self::CRITERIA_LESS_THAN_OR_EQUAL:
                    if($item->getPrice() <= $args['price']) {
                        yield $item;
                    }
                    break;
                case self::CRITERIA_GREATER_THAN_OR_EQUAL:
                    if($item->getPrice() >= $args['price']) {
                        yield $item;
                    }
                    break;
            }
        }   
    }
}

Son olarak her iki şartı VE lojiğine tabi tutacak filtre sınıfını oluşturalım.

<?php

namespace Promotion\Filter;

use Promotion\FilterInterface;

class AndFilter implements FilterInterface
{
    /**
     * @var FilterInterface $filter1
     */
    private $filter1;

    /**
     * @var FilterInterface $filter2
     */
    private $filter2;

    /**
     * @param FilterInterface $filter1
     * @param FilterInterface $filter2
     */
    public function AndFilter(FilterInterface $filter1, FilterInterface $filter2)
    {
        $this->filter1 = $filter1;
        $this->filter2 = $filter2;
    }

    /**
     * @param Traversable $items
     * @param array $args
     * @return Generator
     */
    public function apply(Traversable $items, array $args=array())
    {
        $filtered = $this->filter1->apply($items, $args);
        return $this->filter2->apply($filtered, $args);
    }
}

Filtrelerimiz hazır olduğuna göre bir önceki örnekde oluşturduğumuz SpecialOfferCalculator sınıfını yeni şartlara göre düzenleyelim.

<?php
namespace Calculator\Calculator;

use Calculator\Calculator\CalculatorAbstract;
use Promotion\Filter\CategoryFilter;
use Promotion\Filter\PriceFilter;
use Promotion\Filter\AndFilter;

class SpecialOfferCalculator extends CalculatorAbstract
{
    const OFFER_LIMIT = 3;
    const OFFER_CATEGORY_ID = 5; // CATEGORY_ID:5 = Yeni Yıl
    const OFFER_PRICE_LIMIT = 100;
    const OFFER_PRICE_COND = PriceFilter::CRITERIA_GREATER_THAN_OR_EQUAL;

    private prepareItems()
    {
        $filter = new AndFilter(new CategoryFilter(), new PriceFilter());
        $filteredItems = $filter->apply(
            $this->decoratedObject->getItems(), 
            array(
                'category_id' => self::OFFER_CATEGORY_ID,
                'price'       => self::OFFER_PRICE_LIMIT,
                'condition'   => self::OFFER_PRICE_COND
            )
        );

        // Generator tip, iterate edilmedigi surece sonuc donmedigi icin,
        // bir sonraki metodda yapilacak belirli kategorideki urun sayisi
        // kontrolu nedeniyle generator tipini diziye donusturuyoruz.
        $items = array();
        foreach($filteredItems as $item) {
            $items[] = $item;
        }
        return $items;
    }

    public function getTotalPrice()
    {
        $totalPrice = $this->decoratedObject->getTotalPrice();
        $items = $this->prepareItems();

        if(count($items) > self::OFFER_LIMIT) {
            // Urunleri pahalidan ucuza siralayarak 100 TL ve uzeri en ucuz
            // urunun tutarini indirim olarak uyguluyoruz.

            usort($items, function($itemA, $itemB) {
                return $itemA->getPrice() > $itemB->getPrice();
            });

            $totalPrice -= end($items)->getPrice();
        }
        return $totalPrice;
    }
}

Artık sepet tutarımızı hesaplamaya hazırız.

<?php
namespace MyApp;

use Basket\Basket;
use Basket\BasketItem;
use Promotion\Discount;
use Calculator\Calculator\TotalPriceCalculator;
use Calculator\Calculator\SpecialOfferCalculator;
use Calculator\Calculator\DiscountCalculator;

class Main
{
    public function main()
    {
        $basket = new Basket();
        $basket->add(new BasketItem("001", new Category(5, 'Yeni Yıl'), 'Gömlek', 150))
            ->add(new BasketItem("001", new Category(5, 'Yeni Yıl'), 'Gömlek', 180))
            ->add(new BasketItem("001", new Category(5, 'Yeni Yıl'), 'Gömlek', 105))
            ->add(new BasketItem("002", new Category(5, 'Yeni Yıl'), 'Pantolon', 80);
        $basket->setDiscount(new Discount(Discount::DISCOUNT_TYPE_FIXED, 10));
        $basket = new DiscountCalculator(
            new SpecialOfferCalculator(
                new TotalPriceCalculator($basket)
            )
        );
        print $basket->getTotalPrice(); 
        // sub total = 150 + 180 + 105 + 80 = 515
        // ofered total = sub total - 105 = 410
        // Payment total = offered total - discount amount (10) = 405 TL
    }
}
Genel

Dekoratör Tasarım Deseni

Dekoratör tasarım deseni, bir nesne üzerinde yapısal değişiklik gerçekleştirmeden yeni yetenekler kazandırılmak istenildiği durumlarda tercih edilen bir tasarım kalıbıdır. Genellikle tekil sorumluluk ilkesi gereği sorumlulukların müferit sınıflar arasında bölünmek istendiği yerlerde tercih edilir. Belirli bir mal veya hizmetin satışında ara toplam, indirimler, vergiler ve özel durumların hesaplanması istendiği durumlar için son derece elverişli bir yapısal tasarım desenidir.

Gerçek Hayat Örneği:

Bir elektronik ticaret sitesinde aşağıdaki kampsamda hizmetler veirlebilmektedir.

* Kullanıcı sepetine istediği kadar ürün ekleyebilir.
* Sabit tutarlı veya yüzdelik cinsten indirimlerden yararlanabilir.

İstisnalar:

* Kullanıcı A kategorisindeki aynı üründen 3 adet satın alırsa 2 tanesinin parasını öder.

Problem:

Kullanıcı yeni yıl kategorisinden 3 adet gömlek satın almış, hesabına tanımlı olan 10 TL lik indirimden yararlanmıştır.

Uygulama:

Başlamadan önce sepet, ürün ve indirim sınıflarımızı tanımlayalım.

namespace Basket;

class BasketItem
{
	/**
	 * @var string
	 */
	private $code;

	/**
	 * @var Category;
	 */
	private $category;

	/**
	 * @var string
	 */
	private $name;

	/**
	 * @var float
	 */
	private $price;

	public function BasketItem($code, $category, $name, $price)
	{
		$this->code = $code;
		$this->cateogry = $categorY;
		$this->name = $name;
		$this->price = $price;
	}

	/**
	 * @return string
	 */
	public function getCode()
	{
		return $this->code;
	}

	/**
	 * @return Category
	 */
	public function getCategory()
	{
		return $this->category;
	}

	/**
	 * @return string
	 */
	public function getName()
	{
		return $this->name;
	}

	/**
	 * @return float
	 */
	public function getPrice()
	{
		return $this->price;
	}
}
<?php
namespace Basket;

use Basket\BasketItem;
use Promotion\Discount;

class Basket
{
	/**
	 * @var SplObjectStorage
	 */
	private $collection;

	/**
	 * @var float
	 */
	private $totalPrice = 0.0;

	/**
	 * @var \Promotion\Discount
	 */
	private $discount;

	public function Basket()
	{
		$this->collection = new SplObjectStorage();
	}

	/**
	 * @return SplObjectStorage
	 */
	public function getItems()
	{
		return $this->collection;
	}

	/**
	 * @param BasketItem $item
	 * @return Basket
	 */
	public function add(BasketItem $item)
	{
		$this->collection->attach($item);
		return $this;
	}
	
	//...

	public function getTotalPrice()
	{
		return $this->totalPrice;
	}

	public function setTotalPrice($value)
	{
		$this->totalPrice = $value;
	}

	/**
	 * @return \Promotion\Discount
	 */
	public function getDiscount()
	{
		return $this->discount;
	}

	/**
	 * @param \Promotion\Discount $discount
	 */
	public function setDiscount(Discount $discount)
	{
		$this->discount = $value;
	}

	//...
}
<?php
namespace Promotion;

class Discount
{
	DISCOUNT_TYPE_FIXED = 'fixed';
	DISCOUNT_TYPE_PERCENTAGE = 'percentage';

	/**
	 * @var string
	 */
	private $type;
	
	/**
	 * @var float
	 */
	private $amount;

	public function Discount($type, $amount)
	{
		$this->type = $type;
		$this->amount = $amount;
	}

	/**
	 * @return string
	 */	
	public function getType()
	{
		return $this->type;
	}

	/**
	 * @return float
	 */
	public function getAmount()
	{
		return $this->amount;
	}
}

Hesaplama işlemi sırasında kullanıcının sepetini dekore edeceğimize göre sepet ve somut dekoratör sınıfları benzer niteliklere sahip olmalıdır. Bunun için sepet ve dekoratör sınıflarının implement edeceği arayüz sınıfını oluşturalım.

<?php
namespace Calculator;

interface Calculatable
{
	public function getTotalPrice();
}

* Sepet nesnesinin yeni oluşturduğumuz arayüzü implement etmesini sağlıyoruz.

<?php
namespace Basket;

use Basket\BasketItem;
use Calculator\Calculatable;

class Basket implements Calculatable
{
	//...
}

Her dekoratör, kurulum sırasında sepeti veya bir başka dekoratörü argüman olarak alabilir. Birden fazla somut dekoratör sınıfım olacağını varsayarak yeni bir soyut dekoratör sınıfı oluşturuyorum.

<?php
namespace Calculator\Calculator;

use Calculator\Calculatable;

abstract class CalculatorAbstract implements Calculatable
{
	/**
	 * @var Calculatable
	 */
	protected $decoratedObject;

	/**
	 * @var Calculatable $decoratedObject
	 */
	public function CalculatorAbstract(Calculatable $decoratedObject)
	{
		$this->decoratedObject = $decoratedObject;
	}

	public function getItems()
	{
		return $this->decoratedObject->getItems();
	}

	public function getTotalPrice()
	{
		return $this->decoratedObject->getTotalPrice();
	}
}

* Sepet toplamını hesaplayacak yeni bir dekoratör sınıfı oluşturuyorum.

<?php
namespace Calculator\Calculator;

use Calculator\Calculator\CalculatorAbstract;

class TotalPriceCalculator extends CalculatorAbstract
{
	public function getTotalPrice()
	{
		$total = 0;
		foreach($this->decoratedObject->getItems() as $item) {
			$total += $item->getPrice();
		}
		return $toal;
	}
}

* Aynı kategorideki bir üründen 3 adet satın alındığında 2 ürün parası ödemeyi sağlayacak decoratörü geliştirelim.

<?php
namespace Calculator\Calculator;

use Calculator\Calculator\CalculatorAbstract;

class SpecialOfferCalculator extends CalculatorAbstract
{
	const OFFER_LIMIT = 3;

	public function getTotalPrice()
	{
		$totalPrice = $this->decoratedObject->getTotalPrice();
		$groupedProducts = array();

		$isDiscountApplied = false;

		foreach($this->decoratedObject->getItems() as $item) {
			$categoryId = $item->getCategory()->getId();
			
			if(!array_key_exists($groupedProducts, $categoryId)) {
				$groupedProducts[$categoryId] = array();
			}
			
			$groupedProducts[$categoryId][] = $item;

			if(count(groupedProducts[$categoryId]) >= self::OFFER_LIMIT and !$isDiscountApplied) {
				$totalPrice -= $item->getPrice();
				$isDiscountApplied = true;
			}
		}

		return $totalPrice;
	}
}

Yazının başında kullanıcıların, indirimleri yüzde veya sabit tutar cinsinden kullanabileceğinden söz etmiştik. Ancak soyut düşündüğümüzde indirim hesaplamak bizim için nihayi rakama ulaşmaktaki adımlardan biri olup, indirimin yüzde veya sabit tutar cinsinden olması ise indirimin hesaplama metodolojisidir. Bu nedenle indirim hesaplama yöntemini sepet hesaplayıcılardan soyutlamak için strateji tasarım desenini kullanarak yeni bir indirim hesaplayıcı sınıf ailesi oluşturacağız.

<?php
namespace Promotion\Discount;

interface DiscountType
{
	/**
	 * @param float $discountAmount
	 * @param float $price
	 */
	public function calculate($discountAmount, $price);
}
<?php
namespace Promotion\Discount;

use Promotion\Discount\DiscountType;

class FixedDiscount implements DiscountType
{
	/**
	 * @param float $discountAmount
	 * @param float $price
	 */
	public function calculate($discountAmount, $price)
	{
		return $price - $discountAmount;
	}
}
<?php
namespace Promotion\Discount;

use Promotion\Discount\DiscountType;

class PercentageDiscount implements DiscountType
{
	/**
	 * @param float $discountAmount
	 * @param float $price
	 */
	public function calculate($discountAmount, $price)
	{
		return $price - ($price * ($discountAmount / 100));
	}
}
<?php
namespace Promotion;

use Promotion\Discount\DiscountType;

class DiscountContext
{
	/**
	 * @var \Promotion\Discount\DiscountType
	 */
	private $discountType;

	public function DiscountContext(DiscountType $discountType)
	{
		$this->discountType = $discountType;
	}

	public function apply($discountAmount, $price)
	{
		return $this->discountType->calculate($discountAmount, $price);
	}
}

* Şimdi de İndirim tutarını sepet toplamına yansıtacak dekoratör sınıfımızı oluşturalım.

namespace Calculator\Calculator;

use Promotion\Discount;
use Promotion\DiscountContext;

class DiscountCalculator extends CalculatorAbstract
{
	public function getTotalPrice()
	{
		$discount = $this->decoratedObject->getDiscount();
		if($discount->getType() == Discount::DISCOUNT_TYPE_FIXED) {
			$discountType = new \Promotion\Discount\FixedDiscount();
		} elseif($discount->getType() == Discount::DISCOUNT_TYPE_PERCENTAGE) {
			$discountType = new \Promotion\Discount\PercentageDiscount();
		} else {
			throw new Exception('Bad discount defination.');
		}
		$context = new DiscountContext($discountType);
		return = $context->apply($discount->getAmount(), $this->decoratedObject->getTotalPrice());
	}
}

Artık sepet tutarımızı hesaplamaya hazırız.

<?php
namespace MyApp;

use Basket\Basket;
use Basket\BasketItem;
use Promotion\Discount;
use Calculator\Calculator\TotalPriceCalculator;
use Calculator\Calculator\SpecialOfferCalculator;
use Calculator\Calculator\DiscountCalculator;

class Main
{
	public function main()
	{
		$basket = new Basket();
		$basket->add(new BasketItem("001", new Category(1, 'Giyim'), 'Gömlek', 80))
			->add(new BasketItem("001", new Category(1, 'Giyim'), 'Gömlek', 80))
			->add(new BasketItem("001", new Category(1, 'Giyim'), 'Gömlek', 80))
			->add(new BasketItem("002", new Category(1, 'Giyim'), 'Pantolon', 200));
		$basket->setDiscount(new Discount(Discount::DISCOUNT_TYPE_FIXED, 10));
		$basket = new DiscountCalculator(new SpecialOfferCalculator(new TotalPriceCalculator($basket)));
		print $basket->getTotalPrice(); //TotalPrice = 350 TL
	}
}

Genel

10 Kasım


Mustafa-Kemal-Atatürk-37
Ulu önderimiz Mustafa Kemal Atatürk’ü saygı ve rahmetle anıyoruz.