Tag Archives: PHP

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

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 Yazılım ve Sistem Mühendisliği

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
	}
}

Programa Dilleri ve Çatıları

JMS Serializer Kullanarak Serialization İşlemi Gerçekleştirme

Paranoia’nın ilk sürümlerinde bankalara gönderilecek XML datasını üretmek için PHP dizilerini XML’e dönüştüren açık kaynaklı bir kütüphane kullamıştım. Pratikti ve her halukarda PHP sınıfının içine apaçık XML yazmaktan çok daha temiz bir yoldu. Ancak yine de hala entegrasyon sınıflarını açtığınızda koca koca dizileri görmek hem çirkin, hem de okunabilirlik açısından çok hoş değil. Projenin daha başından sağolsun Osman Üngür pek çok yapısal öneride bulunmuştu. JMS Serializer da bu önerilerden biriydi.

Serialization nedir ?

Kabaca programatik bir varlığın saklanmak veya aktarılmak maksadıyla yeniden dönüştürülebilir bir formata çevirilmesi işlemidir.

JMS Serializer Kullanılarak Serialization Nasıl Gerçekleştirilir ?

Örneğin NestPay altyapısı üzerinde çalışan bir bankanın ödeme için kullandığı XML datasını üreteceğimiiz varsayalım. Üreteceğimiz XML aşağıdaki gibi olacaktır.

<?xml version="1.0" encoding="ISO-8859-1"?>
<CC5Request>
    <Name> </Name>
    <Password> </Password>
    <ClientId> </ClientId>
    <Mode>P</Mode>
    <OrderId> </OrderId>
    <Type>Auth</Type>
    <Number> </Number>
    <Expires> </Expires>
    <Cvv2Val> </Cvv2Val>
    <Total> </Total>
    <Taksit> </Taksit>
    <Currency>949</Currency>
    <UserId></UserId>
</CC5Request>

Öncelikle JMSSerializer paketinin kurulumunu gerçekleştirelim.

$ composer require jms/serializer "1.1.0"

Eğer JMS Serializer kütüphanesini Symfony2 çatısı altında kullanmıyorsanız, kütüphanenin doctrine annotation kütüpahnesiyle olan bağımlılığı nedeniyle mapping sınıflarında kullanacağımız annotationları otomatik olarak yükleyebilmek için öncelikle Annotation otomatik yükleyicisini ayaklandırmamız gerekmektedir.

require_once(__DIR__ . "/vendor/autoload.php");

\Doctrine\Common\Annotations\AnnotationRegistry::registerAutoloadNamespace(
    'JMS\Serializer\Annotation', __DIR__.'/vendor/jms/serializer/src'
);

Şimdi de XML formatına dönüştürülecek PHP Sınıfını oluşturalım.

class CC5Request
{
	private $name;
	private $password;
	private $clientId;
	private $mode;
	private $orderId;
	private $type;
	private $number;
	private $expires;
	private $cvv2val;
	private $total;
	private $taksit;
	private $currency;
	private $userId;
}

JMS Serializer’ın, yukarıdaki sınıf için serialization işlemi gerçekleştirdiğinde hangi alanın ne şekilde XML şemasında tanımlanacağını anlayabilmesi için , sınıfı aşağıdaki gibi gerekli annotation larla donatalım.

use JMS\Serializer\Annotation\XmlElement;
use JMS\Serializer\Annotation\XmlRoot;
use JMS\Serializer\Annotation\Type;
use JMS\Serializer\Annotation\SerializedName;

/**
 * @XmlRoot("CC5Request")
 */
class CC5Request
{
    /**
     * @XmlElement(cdata=false)
     * @Type("string")
     * @SerializedName("Name")
     */
    private $name;

    /**
     * @XmlElement(cdata=false)
     * @Type("string")
     * @SerializedName("Password")
     */
    private $password;

    /**
     * @XmlElement(cdata=false)
     * @Type("string")
     * @SerializedName("ClientId")
     */
    private $clientId;

    /**
     * @XmlElement(cdata=false)
     * @Type("string")
     * @SerializedName("Mode")
     */
    private $mode;

    /**
     * @XmlElement(cdata=false)
     * @Type("string")
     * @SerializedName("OrderId")
     */
    private $orderId;

    /**
     * @XmlElement(cdata=false)
     * @Type("string")
     * @SerializedName("Type")
     */
    private $type;

    /**
     * @XmlElement(cdata=false)
     * @Type("string")
     * @SerializedName("Number")
     */
    private $number;

    /**
     * @XmlElement(cdata=false)
     * @Type("string")
     * @SerializedName("Expires")
     */
    private $expires;

    /**
     * @XmlElement(cdata=false)
     * @Type("integer")
     * @SerializedName("Cvv2Val")
     */
    private $cvv2val;

    /**
     * @XmlElement(cdata=false)
     * @Type("double")
     * @SerializedName("Total")
     */
    private $total;

    /**
     * @XmlElement(cdata=false)
     * @Type("integer")
     * @SerializedName("Taksit")
     */
    private $taksit;

    /**
     * @XmlElement(cdata=false)
     * @Type("integer")
     * @SerializedName("Currency")
     */
    private $currency;

    /**
     * @XmlElement(cdata=false)
     * @Type("string")
     * @SerializedName("UserId")
     */
    private $userId;

    /**
     * @param mixed $clientId
     */
    public function setClientId($clientId)
    {
        $this->clientId = $clientId;
    }

    /**
     * @return mixed
     */
    public function getClientId()
    {
        return $this->clientId;
    }

    /**
     * @param mixed $currency
     */
    public function setCurrency($currency)
    {
        $this->currency = $currency;
    }

    /**
     * @return mixed
     */
    public function getCurrency()
    {
        return $this->currency;
    }

    /**
     * @param mixed $cvv2val
     */
    public function setCvv2val($cvv2val)
    {
        $this->cvv2val = $cvv2val;
    }

    /**
     * @return mixed
     */
    public function getCvv2val()
    {
        return $this->cvv2val;
    }

    /**
     * @param mixed $expires
     */
    public function setExpires($expires)
    {
        $this->expires = $expires;
    }

    /**
     * @return mixed
     */
    public function getExpires()
    {
        return $this->expires;
    }

    /**
     * @param mixed $mode
     */
    public function setMode($mode)
    {
        $this->mode = $mode;
    }

    /**
     * @return mixed
     */
    public function getMode()
    {
        return $this->mode;
    }

    /**
     * @param mixed $name
     */
    public function setName($name)
    {
        $this->name = $name;
    }

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

    /**
     * @param mixed $number
     */
    public function setNumber($number)
    {
        $this->number = $number;
    }

    /**
     * @return mixed
     */
    public function getNumber()
    {
        return $this->number;
    }

    /**
     * @param mixed $orderId
     */
    public function setOrderId($orderId)
    {
        $this->orderId = $orderId;
    }

    /**
     * @return mixed
     */
    public function getOrderId()
    {
        return $this->orderId;
    }

    /**
     * @param mixed $password
     */
    public function setPassword($password)
    {
        $this->password = $password;
    }

    /**
     * @return mixed
     */
    public function getPassword()
    {
        return $this->password;
    }

    /**
     * @param mixed $taksit
     */
    public function setTaksit($taksit)
    {
        $this->taksit = $taksit;
    }

    /**
     * @return mixed
     */
    public function getTaksit()
    {
        return $this->taksit;
    }

    /**
     * @param mixed $total
     */
    public function setTotal($total)
    {
        $this->total = $total;
    }

    /**
     * @return mixed
     */
    public function getTotal()
    {
        return $this->total;
    }

    /**
     * @param mixed $type
     */
    public function setType($type)
    {
        $this->type = $type;
    }

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

    /**
     * @param mixed $userId
     */
    public function setUserId($userId)
    {
        $this->userId = $userId;
    }

    /**
     * @return mixed
     */
    public function getUserId()
    {
        return $this->userId;
    }
}

Normal şartlarda JMSSerilizer biz, bu annotationları kullanmıyor olsak da halihazırda tanımlanan sınıf ve attribute isimlendirmelerine göre serialization işlemini gerçekleştirebiliyor. Ancak örnek XML deki isimleindirmelerin farklı ve PHP için genel geçer isimlendirme standarlarının dışnda olması nedeniyle annotation lar vasıtasıyla sınıfımızın serialization çıktısını dekore ediyoruz.

Dilerseniz birlikte kısaca kullanıdğımız annotation ları tanıyalım.

XmlRoot: Serialiation sonrasında oluşacak XML kök bölümünün isimlendirmesi için kullanılır.

XmlElement: Bulunduğu alanın serialize edilmesi ile ilgili çeşitli özelliklerinin düzenlenmesi için kullanılır.

Type: Bulunduğu alanın veri tipinin belirtilmesini sağlar.

SerializedName: İlgili alanın serialization sırasında nasıl isimlendirilecğeini tanımlamak için kullanılır.

JMS Serializer tarafından desteklenen diğer annotation’lar için bu dökümanı inceleyebilirsiniz.

Artık serialization işlemine hazır olduğumuza göre oluşturduğumuz sınıfdan bir örnek oluşturalım ve test verisiyle dolduralım.

$obj = new CC5Request();
$obj->setClientId("006641");
$obj->setOrderId('000000000001');
$obj->setNumber('5105105105105100');
$obj->setExpires('12/18');
$obj->setCvv2Val('000');
$obj->setCurrency(949);
$obj->setTotal(55.41);
$obj->setUserId(1);
$obj->setMode('P');
$obj->setName('TESTUSER');
$obj->setPassword('123qwe123');
$obj->setType('Auth');

Serialize edelim.

$serializer = JMS\Serializer\SerializerBuilder::create()->build();
echo $serializer->serialize($obj, 'xml');

**Çıktı:**

<?xml version="1.0" encoding="UTF-8"?>
<CC5Request>
  <Name>TESTUSER</Name>
  <Password>123qwe123</Password>
  <ClientId>006641</ClientId>
  <Mode>P</Mode>
  <OrderId>000000000001</OrderId>
  <Type>Auth</Type>
  <Number>5105105105105100</Number>
  <Expires>12/18</Expires>
  <Cvv2Val>000</Cvv2Val>
  <Total>55.41</Total>
  <Currency>949</Currency>
  <UserId>1</UserId>
</CC5Request>
Genel

Symfony2: Kullanıcı Doğrulama İşleminin Veritabanı Üzerinden Gerçekleştirilmesi

Kullanıcı doğrulama ve yetkilendirme ile ilgili edindiğim ufak tecrübeleri özet halinde sizlerle paylaşmak istedim.

Burada anlatacaklarımın tamamı tüm açılığı ile symfony2 belgeleri arasında yeralmaktadır.

Composer ile Symfony2 standart paketi kurduğunuzda güvenlik yapılandırma ayarlarını içeren app/config/security.yml dosyasındaki security bölümünde, aşağıdaki gibi bir yapılandırma ile karşılaşırsınız:

security:
    encoders:
        Symfony\Component\Security\Core\User\User: plaintext

    role_hierarchy:
        ROLE_ADMIN:       ROLE_USER
        ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]

    providers:
        in_memory:
            memory:
                users:
                    user:  { password: userpass, roles: [ 'ROLE_USER' ] }
                    admin: { password: adminpass, roles: [ 'ROLE_ADMIN' ] }
...   

Gelin şimdi kısım kısım yukarıdaki güvenlik yapılandırmasını inceleyelim.

    encoders:
        Symfony\Component\Security\Core\User\User: plaintext

Yukarıdaki yapılandırma, symfony temel kullanıcı implementasyonu olan Symfony\Component\Security\Core\User\User için, parola kodlama metodolojisinin düz metin şeklinde olacağını sölyüyor.

    role_hierarchy:
        ROLE_ADMIN:       ROLE_USER
        ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]

Bu kısımda ise kullanıcıların sahip olabileceği roller ve bu roler arasındaki hiyerarşi tanımlaması gerçekleştirilmiş.

    providers:
        in_memory:
            memory:
                users:
                    user:  { password: userpass, roles: [ 'ROLE_USER' ] }
                    admin: { password: adminpass, roles: [ 'ROLE_ADMIN' ] }

Bu kısım, symfony2’nin oturum açma işlemi sırasında kullanıcı bilgilerini temin edebileceği sağlayıcılar ile ilgili tanımlamaları içeriyor. Yukarıdaki yapılandırma bize, in_memory isimli memory tipindeki sağlayıcının, users listesindeki kullanıcıları içerdiğini söylüyor. Sağlayıcının memory tipinde olması ise kullanıcı doğrulama işleminin konfigürasyondaki users listesinde yeralan kullanıcıların bilgileri doğrultusunda gerçekleştirileceği anlamına gelir.

Symfony, size birden fazla sağlayıcı ile çalışabilme olanağını sunar. İlk sağlayıcı her zaman varsayılan olarak kabul edilir.

Symfony2’nin kullanıcı doğrulama işlemini veritabanındaki kayıtları kullanarak sağlayabilmesi için, Doctrine üzerinden veritabanına ulaşabilen kullanıcı tanımlı yeni bir sağlayıcı geliştirilmesi gerekmektedir. Dilerseniz hemen aşağıdaki işlem adımlarını birlikte uygulayarak kendi sağlayıcımızı geliştirelim.

1. Kullanıcı kaydını temsil edecek User isimli yeni doctrine entity sınıfını oluşturalım.
src/Acme/DemoBundle/Entity/User.php:

<?php
namespace Acme\DemoBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Serializable;
use Symfony\Component\Security\Core\User\UserInterface;
use Acme\DemoBundle\Entity\Role;

/**
 * User
 *
 * @ORM\Table(name="acme_demo_user")
 * @ORM\Entity(repositoryClass="Acme\DemoBundle\Repository\UserRepository")
 */
class User implements UserInterface, Serializable
{
    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="username", type="string", length=50)
     */
    private $username;

    /**
     * @var string
     *
     * @ORM\Column(name="email", type="string", length=70)
     */
    private $email;

    /**
     * @var string
     *
     * @ORM\Column(name="password", type="string", length=255)
     */
    private $password;

    /**
     * @var string
     *
     * @ORM\Column(name="fullname", type="string", length=50)
     */
    private $fullname;

    /**
     * @var string
     *
     * @ORM\Column(name="isActive", type="boolean")
     */
    private $isActive;

    /**
     * @var string
     *
     * @ORM\Column(name="salt", type="string", length=255)
     */
    private $salt;

    /**
     * @ORM\ManyToMany(targetEntity="Role")
     * @ORM\JoinTable(name="user_role",
     *               joinColumns={@ORM\JoinColumn(name="user_id", referencedColumnName="id")},
     *               inverseJoinColumns={@ORM\JoinColumn(name="role_id", referencedColumnName="id")})
     */
    private $roles;

    public function __construct()
    {
        $this->roles = new \Doctrine\Common\Collections\ArrayCollection();
        $this->isActive = true;
        $this->salt = base_convert(sha1(uniqid(mt_rand(), true)), 16,
            36);
    }

    /**
     * Get id
     *
     * @return integer 
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set username
     *
     * @param string $username
     * @return User
     */
    public function setUsername($username)
    {
        $this->username = $username;

        return $this;
    }

    /**
     * Get username
     *
     * @return string 
     */
    public function getUsername()
    {
        return $this->username;
    }

    /**
     * Set email
     *
     * @param string $email
     * @return User
     */
    public function setEmail($email)
    {
        $this->email = $email;

        return $this;
    }

    /**
     * Get email
     *
     * @return string 
     */
    public function getEmail()
    {
        return $this->email;
    }

    /**
     * Set password
     *
     * @param string $password
     * @return User
     */
    public function setPassword($password)
    {
        $this->password = $password;

        return $this;
    }

    /**
     * Get password
     *
     * @return string 
     */
    public function getPassword()
    {
        return $this->password;
    }

    /**
     * Set fullname
     *
     * @param string $fullname
     * @return User
     */
    public function setFullname($fullname)
    {
        $this->fullname = $fullname;

        return $this;
    }

    /**
     * Get fullname
     *
     * @return string 
     */
    public function getFullname()
    {
        return $this->fullname;
    }

    /**
     * Set isActive
     *
     * @param boolean $isActive
     * @return User
     */
    public function setIsActive($isActive)
    {
        $this->isActive = $isActive;

        return $this;
    }

    /**
     * Get isActive
     *
     * @return boolean 
     */
    public function getIsActive()
    {
        return $this->isActive;
    }

    /**
     * Set salt
     *
     * @param string $salt
     * @return User
     */
    public function setSalt($salt)
    {
        $this->salt = $salt;

        return $this;
    }

    /**
     * Get salt
     *
     * @return string 
     */
    public function getSalt()
    {
        return $this->salt;
    }

    /**
     * Add roles
     *
     * @param \Acme\DemoBundle\Entity\Role $roles
     * @return User
     */
    public function addRole(\Acme\DemoBundle\Entity\Role $roles)
    {
        $this->roles[] = $roles;

        return $this;
    }

    /**
     * Remove roles
     *
     * @param \Acme\DemoBundle\Entity\Role $roles
     */
    public function removeRole(\Acme\DemoBundle\Entity\Role $roles)
    {
        $this->roles->removeElement($roles);
    }

    /**
     * Get roles
     *
     * @return \Doctrine\Common\Collections\Collection 
     */
    public function getRoles()
    {
        return $this->roles->toArray();
    }

    /**
     * Removes sensitive data from the user.
     *
     * This is important if, at any given point, sensitive information like
     * the plain-text password is stored on this object.
     */
    public function eraseCredentials()
    {
        // TODO: Implement eraseCredentials() method.
    }

    /**
     * (PHP 5 &gt;= 5.1.0)<br/>
     * String representation of object
     * @link http://php.net/manual/en/serializable.serialize.php
     * @return string the string representation of the object or null
     */
    public function serialize()
    {
        return serialize(array(
            $this->id,
            $this->username,
            $this->password,
            // see section on salt below
            $this->salt,
        ));
    }

    /**
     * (PHP 5 &gt;= 5.1.0)<br/>
     * Constructs the object
     * @link http://php.net/manual/en/serializable.unserialize.php
     * @param string $serialized <p>
     * The string representation of the object.
     * </p>
     * @return void
     */
    public function unserialize($serialized)
    {
        list (
            $this->id,
            $this->username,
            $this->password,
            // see section on salt below
            $this->salt
            ) = unserialize($serialized);
    }
}

2. Kullanıcının rolünü temsil edecek Role isimli yeni bir doctrine entitiy sınıfı oluşturalım.
app/src/Acme/DemoBundle/Entity/Role.php:

<?php
namespace Acme\DemoBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\Role\RoleInterface;

/**
 * Role
 *
 * @ORM\Table(name="acme_demo_role")
 * @ORM\Entity
 */
class Role implements RoleInterface
{
    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="name", type="string", length=128)
     */
    private $name;

    /**
     * @var string
     *
     * @ORM\Column(name="role", type="string", length=20, unique=true)
     */
    private $role;

    /**
     * @ORM\ManyToMany(targetEntity="User", mappedBy="roles")
     */
    private $users;

    /**
     * Get id
     *
     * @return integer 
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set name
     *
     * @param string $name
     * @return Role
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    /**
     * Get name
     *
     * @return string 
     */
    public function getName()
    {
        return $this->name;
    }
    /**
     * Constructor
     */
    public function __construct()
    {
        $this->users = new \Doctrine\Common\Collections\ArrayCollection();
    }

    /**
     * Set role
     *
     * @param string $role
     * @return Role
     */
    public function setRole($role)
    {
        $this->role = $role;

        return $this;
    }

    /**
     * Get role
     *
     * @return string 
     */
    public function getRole()
    {
        return $this->role;
    }

    /**
     * Add users
     *
     * @param \Acme\DemoBundle\Entity\User $users
     * @return Role
     */
    public function addUser(\Acme\DemoBundle\Entity\User $users)
    {
        $this->users[] = $users;

        return $this;
    }

    /**
     * Remove users
     *
     * @param \Acme\DemoBundle\Entity\User $users
     */
    public function removeUser(\Acme\DemoBundle\Entity\User $users)
    {
        $this->users->removeElement($users);
    }

    /**
     * Get users
     *
     * @return \Doctrine\Common\Collections\Collection 
     */
    public function getUsers()
    {
        return $this->users;
    }
}

3. Symfony nin sağlayıcı olarak kabul edeceği, güvenlik bileşeninin veritabanındaki kullanıcı listesine ulaşmasını sağlayak yeni bir Doctrine deposunu tanımlayalım.
Acme/DemoBundle/Repository/UserRepository.php:

<?php
namespace Acme\DemoBundle\Repository;

use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\NoResultException;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;

class UserRepository extends EntityRepository implements UserProviderInterface
{

    /**
     * Loads the user for the given username.
     *
     * This method must throw UsernameNotFoundException if the user is not
     * found.
     *
     * @param string $username The username
     *
     * @return UserInterface
     *
     * @see UsernameNotFoundException
     *
     * @throws UsernameNotFoundException if the user is not found
     *
     */
    public function loadUserByUsername($username)
    {
        $query = $this->createQueryBuilder("u")
                ->select("u, r")
                ->leftJoin("u.roles", "r")
                ->where("u.username = :username OR u.email = :email")
                ->setParameter("username", $username)
                ->setParameter("email", $username)
                ->getQuery();
        try {
            $user = $query->getSingleResult();
        } catch (NoResultException $e) {
            $message = sprintf("Unable to find the specified user: %s", $username);
            throw new UsernameNotFoundException($message, 0, $e);
        } catch (BadCredentialsException $e) {
            $message = sprintf("Unable to find the specified user: %s", $username);
            throw new UsernameNotFoundException($message, 0, $e);
        }
        return $user;
    }

    /**
     * Refreshes the user for the account interface.
     *
     * It is up to the implementation to decide if the user data should be
     * totally reloaded (e.g. from the database), or if the UserInterface
     * object can just be merged into some internal array of users / identity
     * map.
     * @param UserInterface $user
     *
     * @return UserInterface
     *
     * @throws UnsupportedUserException if the account is not supported
     */
    public function refreshUser(UserInterface $user)
    {
        $class = get_class($user);
        if(!$this->supportsClass($class)) {
            throw new UnsupportedUserException(
                sprintf(
                    "Instance of %s is not supported",
                    $class
                )
            );
        }
        return $this->find($user->getId());
    }

    /**
     * Whether this provider supports the given user class
     *
     * @param string $class
     *
     * @return Boolean
     */
    public function supportsClass($class)
    {
        return $this->getEntityName() == $class
            || is_subclass_of($class, $this->getEntityName());
    }
}

4. Kullanıcılara ait parolaların veritabanında şifrelenmiş şekilde barındırılabilmesi için app/config/security.yml güvenlik yapılandırma dosyasındaki encoders bölümüne aşağıdaki ayarları girelim. Bu ayarlar, kullanıcı parolalarının veritabanında sha512 algoritması ile şifrelenmesini sağlayacaktır.
app/config/security.yml:

security:
...
    encoders:
...
        Acme\DemoBundle\Entity\User:
          algorithm: sha512
          iterations: 10
...

5. Aynı dosyadaki providers isimli listeye biraz önce oluşturduğumuz doctrine deposunu sağlayıcı olarak tanıtacak ayarları ekleyelim.
app/config/security.yml:

security:
...
    providers:
...
        user_db:
            entity:
                class: AcmeDemoBundle:User
                property: username
...

6. Yine aynı yapılandırma dosyasındaki firewall isimli bölümde oturum açma işleminin yeni oluşturduğumuz sağlayıcı üzerinden gerçekleşmesini sağlayacak ayarları ekleyelim.
app/config/security.yml:

security:
...
    firewalls:
...
        secured_area:
            provider: user_db # bu bölümü ekliyoruz.
            pattern:    ^/.*
            form_login:
                check_path: authenticate
                login_path: login
            logout:
                path:   logout
                target: home
...            

7. Yeni oluşturduğumuz doctrine entitiy sınıfları nedeniyle veritabanı şemamızı güncellemek için aşağıdaki shell komutunu çalıştıralım:

$ php app/console doctrine:schema:update --force

8. Son olarak ROLE_USER ve ROLE_ADMIN isimli kullanıcı rollerini yaratacak aşağıdaki SQL betiğini çalıştıralım.

INSERT INTO acme_demo_role (role, name) VALUES ('ROLE_ADMIN', 'ROLE_ADMIN'), ('ROLE_USER', 'ROLE_USER');

Uygulamamız artık kullanıcı doğrulama işlemini veritabanı üzerinden gerçekleştirmeye hazır. Ancak kullanıcı parolalarının sha512 ile kodlanması gerektiğinden kullanıcı yaratma işlemini yine symfony ye yaptırmak gerekiyor.

Aslına bakarsanız doğrulama ve yetkilendirme işlemlerini yapabilmek adına FosUserBundle gibi çok daha pratik çözümler mevcut. Ancak ben de henüz öğrenme aşamasında olduğum için başlangıçta pratik olarak kullanıcı yaratabilmek adına aşağıdaki gibi bir konsol komutu işleyicisi yazmak durumunda kaldım.
app/src/Acme/DemoBundle/Command/UserCreator.php:

<?php
namespace Acme\DemoBundle\Command;

use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Acme\DemoBundle\Entity\Role;
use Acme\DemoBundle\Entity\User;

class AuthCommand extends ContainerAwareCommand
{
    protected function configure()
    {
        $this->setName('create:user')
            ->setDescription('This ocmmand creates a new user.')
            ->addOption('username', null, InputOption::VALUE_REQUIRED, 'username of user.')
            ->addOption('fullname', null, InputOption::VALUE_REQUIRED, 'Fullname of user.')
            ->addOption('email', null, InputOption::VALUE_REQUIRED, 'Email of user.')
            ->addOption('password', null, InputOption::VALUE_REQUIRED, 'Password of user.')
            ->addOption('role', null, InputOption::VALUE_REQUIRED, 'Role of user.');
    }

    protected function execute(InputInterface $input, OutputInterface $output)
    {
        try {
            $factory = $this->getContainer()->get('security.encoder_factory');
            $user = new User();
            $encoder = $factory->getEncoder($user);
            $user->setSalt(md5(time()));
            $password = $encoder->encodePassword($input->getOption('password'), $user->getSalt());
            $user->setUsername($input->getOption('username'));
            $user->setPassword($password);
            $user->setEmail($input->getOption('email'));
            $user->setFullname($input->getOption('fullname'));
            $user->setIsActive(1);
            $role = $this->getContainer()
                ->get('doctrine')
                ->getRepository('YdCatalogBundle:Role')
                ->findOneBy(array('role' => $input->getOption('role')));
            $user->addRole($role);

            $em = $this->getContainer()->get('doctrine')->getEntityManager();
            $em->persist($user);
            $em->flush();
            $output->writeln('User is created');
        } catch(Exception $e) {
            $output->writeLn('User is not created');
        }
    }
}

Dilerseniz sizler de bu basit komut dosyasını kullanarak veritabanında yeni bir kullanıcı yaratabilirsiniz:

$ php app/console create:user --username=ibrahim.gunduz --password=123456 --email=ibrahimgunduz34@gmail.com --fullname='İbrahim Gündüz' --role=ROLE_ADMIN
Genel

PHPKonf Ardından

PHPKonf 2014 Bahçeşehir Üniversitesi Geçtiğimiz cumartesi günü PHP geliştiricileri ve teknoloji meraklıları PHPKonf da buluştuk. Kaliteli konu ve konuşmacılarla (kendimi tenzi ederekten :)) dopdolu ve son derece eğlenceli bir etkinlik oldu. Öğleden sonra gelebilmeme rağmen salonun neredeyse tamamını dolu görmek hem etkinliğe olan ilgi hem de hep birlikte akıl yorduğumuz bu sektörün gelişimi ile ilgili olarak beni son derece mutlu etti.

Yakın geçmişte “register_global kullanmak iyidir” söylemlerinin olduğu etkinliklerden sonra bugün Doctrine, MongoDB, Solr ve RabbitMQ gibi teknolojilerin programlama etkinliklerinde konuşuluyor olması, sektörün gelişimi ve insanların yeni teknolojileri tanıması açısından son derece önemliydi.

phpkonf14

Öğleden sonra gelebildiğim için malesef öncesinde yapılan sunumları kaçırdım. Emir Karşıyakalı‘dan Restful servis protokolüyle ve Restful API geliştirme ile ilgili bilgiler, Tayyar Beşik‘den MongoDB ile ilgili çeşitli ipuçları ve Muhittin Özer‘den workshop tadında bir Doctrine sunumu dinledik. Etkinlik sonunda küçük bir katılımcı kitlesiyle birlikte mini bir “Feyizli Sohbet” gerçekleştirdik.

PHPKonf 2014 Bahçeşehir Üniversitesi - Feyizli Sohbet

Etkinlik vesilesiyle uzun zamandır görüşemediğimiz pek çok dostumuzu da görebilme mutluluğunu yaşadık. Hoş, zaten gördüğüm kadarıyla sektör o kadar küçük ki salonda birbirini tanımayan çok az insan vardı diyebilirim.

Tecrübelerimizi değerli katılımcılarla paylaşabilme ve sektördeki dostlarımızla bir araya gelebilme fırsatını sunan kıymetli kardeşimiz Emir Karşıyakalı‘ya bu vesileyle bir kez daha teşekkür ediyorum ve yeni etkinliklerini sabırsızlıkla bekliyorum.

Kaynaklar:

Fotoğraflar aşağıdaki sitelerden alınmıştır.
http://bilalbaraz.com/22-subat-2014-phpkonf-php-konferansi/
http://dijitalkutuphane.com/phpkonf-bahcesehir-universitende-yapildi//

Genel

PHP Geliştiricileri 22 Şubat 2014 de PHPKonf ile Bir Araya Geliyor

PHPKonf22 Şubat 2014 Cumartesi günü düzenlenecek olan PHPKonf etkinliği, web teknolojileriyle ilgilenen meraklıları Bahçeşehir Üniversitesi’nin Beşiktaş’daki kampüsünde bir araya getirecek.

Etkinlik isminin PHP Konferansı olması sizleri yanıltmasın. Zira tecrübeli geliştiricilerden gerçek hayat deneyimleri, teknolojik ve mimari pek çok konuyla dopdolu bir etkinlik, katılımcılarını bekliyor.

Değerli dostumuz Emir Karşıyakalı tarafından organize edilen PHPKonf, etkinlik destekçilerinden paylas.com tarafından canlı olarak yayınlanacak. Etkinlik kaydını Eventbrite üzerinden ücretsiz olarak gerçekleştirebilir, gelişmeleri @phpkonf isimli twitter hesabından takip edebilir, diğer tüm detayları PHPKonf web sitesinden öğrenebilirsiniz.

Elini taşın altına koyup böyle bir etkinliği organize ederek biz geliştiricileri bir araya getirecek olan Emir Karşıyakalı ve tüm destekçilerine gönülden teşekkür ederiz.

Genel

Guzzle ile Tanışma

Yazılım geliştiricilerinin en büyük şansları keyifsiz günlerde bugün benim yaptığım gibi context switch yapabilme yetenekleri olsa gerek. Geçtiğimiz haftasonundan bu yana gerçekten keyifsiz günler geçiriyorum. Deli gibi kod okuyorum, yazı yazıyorum. Bu da yazdıklarımdan birisi…

Bu gece guzzle kütüphanesinin kaynak kodlarının bir kısmını ve dökümanlarını okudum. Ufak bir inceleme yazısı ve çok basit bir örneği sizlerle paylaşmak istedim. Umarım başlangıç için yararlı olur. Keyifli okumalar…

***

Daha önce Osman Üngür’ün Paranoia için yaptığı önerilere istinaden Guzzle ile tanışma fırsatını yakaladım. Bu vesileyle Osman’ a da çok teşekkür ediyorum.

Guzzle, arka tarafta düşünülen bunca inceliğe karşın kullanıcılarına son derece güçlü, kullanışlı ve son derece basit bir arayüz sunuyor. Gerçekten çok kısa sürede basit bir restful istemcisi yapabilirsiniz. Ayrıca içinde barındırdığı symfony2 nin event dispatcher bileşeni sayesinde kod tabanınızın temiz kalmasını ve diğer sistemlerle entegrasyonunu kolayca gerçekleştirebilmenizi sağlıyor.

Kodu okudukca hayranlığım katlanarak arttı.Her okuduğum kod parçası bana budur dedirtti. Az önce de belirttiğim gibi kendisi symfony2 event dispatcher’ın gücüne sahip. Bağımlılık konusunu o kadar güzel yönetmeişler ki kütüphanenin dilerseniz tamamını dilerseniz işinize yarayan her bir bileşenini ayrı ayrı kullanabiliyorsunuz. Paralel veya kalıcı istekler yapabilmenize olanak sunuyor. Tüm temel http metodlarını,dilerseniz isteğe bağlı metodlar kullanabiliyorsunuz. Loglama, önbellekleme, QAuth vs. vs. konularda pek çok bileşeni mevcut. Curl’ün gözü kör(l) olsun dedirtecek cinsten çok fazla özelliği var. Heyecanımı paylaşmak isteyen arkadaşlar şuradan dökümanı, buradan github reposunu inceleyebilirler.

Örnek incelemeye başlamadan önce dilerseniz composer ile guzzle ı geliştirme ortamımıza kuralım.

Şayet henüz composer ile tanışmadıysanız öncelikle aşağıdaki komutu terminalde çalıştırarak composer kurulumunu gerçekleştirebilirsiniz.

$ curl -sS https://getcomposer.org/installer | php

Şimdi de Guzzle kütüphaneisini geliştirme ortamımıza kuralım.

$ php composer.phar require guzzle/guzzle:~3.7

Şimdide basit bir örnek yapalım. Aşağıdaki örnek kod, github a bağlanarak public gist lerin listesini çekecek.

<?php
//composer autoloader vermiş, kullanmayalım mı ?
require 'vendor/autoload.php';

//guzzle kütüphanesindeki http istemciyi import edelim.
use Guzzle\Http\Client;

//Github a istek salmak üzere bir adet client oluşturalım.
$client = new Client('https://api.github.com');

//Github a salmak üzere bir adet istek oluşturuyoruz.

/* @var Guzzle\Http\Message\Request $request */
$request = $client->get('gists/public');

//isteği github'a salıyoruz.
/* @var Guzzle\Http\Message\Response $response */
$response = $request->send();

//Response geldi ama json geldi. Elin deymişken parse et diyoruz.
$data = $response->json();

print_r($data);

Gönderilen İstek ve Dönen Yanıtların Loglanması

Loglama işlemini Guzzle’ın log eklentisi ile kolaylıkkla gerçekleştirebilirsiniz. Guzzle’ın log eklentisi, Zend Framework Logger ve Monolog gibi diğer uygulamaların logger larıyla haberleşebilme yeteneğine sahiptir.

Biraz önceki örneğe ilave olarak github a yaptığınız isteği ve dönen yanıtı loglayalım.

//composer autoloader vermiş, kullanmayalım mı ?
require 'vendor/autoload.php';

use Guzzle\Http\Client;
use Guzzle\Log\Zf1LogAdapter;
use Guzzle\Plugin\Log\LogPlugin;
use Guzzle\Log\MessageFormatter;

$client = new Client('https://api.github.com');

//loglama islemi icin Zend Framework 1.x logger adaptörü oluşturalım.
$logAdapter = new Zf1LogAdapter(
    new \Zend_Log(new \Zend_Log_Writer_Stream('php://output'))
);
$logPlugin = new LogPlugin($logAdapter, MessageFormatter::DEBUG_FORMAT)
//...ve logger eklentisini istemciye ilistirelim.
$client->addSubscriber($logPlugin);

$request = $client->get('gists/public');
$response = $request->send();
$data = $response->json();

İşte hepsi bu. Olaylar, metodlar, ve daha pek çok konuyla ilgili olarak Guzzle Dökümanlarını inceleyebilirsiniz.

Programa Dilleri ve Çatıları

PHP ile Crawling İşlemi

PHP ile Data Crawling

Eğer sizde benim gibi crawler yazmayı seviyorsanız, PHP nin DOMXPath objesi bu işi gerçekten oldukca kolaylaştırıyor. Ben daha önceki kendi denemelerimde metin işleme yoluyla aradığım veriye ulaşmayı tercih ediyordum. Ancak bu yöntem zaman açısından oldukca maliyetli oluyordu. Bunun yerine xPath sorgusundan yararlanarak veri çekmek gerçekten çok kolay.

Örnek olarak bigpara.com sitesindeki Dolar kurunun alış fiyatını crawle edelim. İlk olarak crawler kodumuzu yazalım.

mycrawler.php:

$url = ''; # Bu degiskenin icinde crawle edecegimiz sitenin url bilgisi yeralacak.
$xpath = ''; # Bu degiskenin icinde crawle edecegimiz verinin xpath adresi yeralacak.
$htmlContent = file_get_contents($url);
if(!$htmlContent) {
  throw new Exception('Failed to connection.');
}
libxml_use_internal_errors(true);   
$domObj = new DOMDocument();
$domObj->loadHTML($htmlContent);
$xPathObj = new DOMXPath($domObj);
$value = $xPathObj->query($xpath)->item(0)->firstChild->nodeValue;
print $value . PHP_EOL;

Şimdi de crawle edeceğimiz verinin xpath adresini alalım. Bu işlem için google chrome tarayıcısından yararlanacağız. XPath adresini tespit etmek için:

* Sayfayı google chrome ile açtıktan sonra verinin bulunduğu alanda fare ile sağ tıklayarak Inspect Element seçeneğine tıklayın.

* Tarayıcının alt kısmında açılan bölümdeki html kodları arasında okumak istediğiniz metni bulun ve üzerine sağ tıklayarak Copy as xPath seçeneğine tıklayın.

Şimdi de edindiğimiz xpath ve url bilgisini kodumuzun içine yerleştirelim.

$url = 'http://www.bigpara.com/dolar/?bprtme=1463181297&sTo=301'; # Bu degiskenin icinde crawle edecegimiz sitenin url bilgisi yeralacak.
$xpath = '//*[@id="content"]/div[2]/div[1]/div[3]/div[2]/strong'; # Bu degiskenin icinde crawle edecegimiz verinin xpath adresi yeralacak.
...

Artık kodumuzu çalıştırmaya hazırız. Hemen bir terminal açalım ve php betiğimizi çalıştıralım.
shell:

$ php mycrawler.php

Sonuç:

1,985
Programa Dilleri ve Çatıları

Zend_Auth ile Kimlik Doğrulama İşleminin Gerçekleştirilmesi

Zend_Auth, Zend Framework’un oldukça kullanışlı kütüphanelerinden birisidir. Çeşitli kimlik doğrulama servislerine sağladığı destekle projenizin kimlik doğrulama mekanizmasını hızlıca biryerden biryere taşıyabilmenize olanak sağlar. Örneğin Zend üzerinde geliştirdiğiniz intranetinizin kimlik doğrulama işlemini Zend_Auth_Adapter_DbTable kullanarak veritabanındaki kullanıcı kayıtları üzerinden gerçekleştirirken, küçük bir geliştirme ile kimlik doğrulama operasyonunuzu şirketinizdeki LDAP sunucusu üzerine taşıyarak, uygulamanızı firmanızdaki e-posta hesapları, domain kullanıcıları ve IP telefonlar gibi sistemlerle aynı authentication servisini kullanarak kolayca yönetmeniz mümkündür.

Zend_Auth, bize aşağıdaki adaptörleri kullanarak kimlik doğrulama işlemi yapabilmemize olanak sağlar:

  • Zend_Auth_Adapter_DbTable
  • Zend_Auth_Adapter_Digest
  • Zend_Auth_Adapter_Http
  • Zend_Auth_Adapter_Ldap
  • Zend_Auth_Adapter_OpenId

Yukarıdaki adaptörleri kullanabildiğiniz gibi sizler de Zend_Auth_Adapter_Interface arayüzüne tabi yeni bir adaptör yazarak kendi kimlik doğrulama servisinizi geliştirebilirsiniz.

Bugün, konuyu temel düzeyde anlatabilmek adına sizlerle veritabanı adaptörünü kullanarak kimlik doğrulama işleminin nasıl gerçekleştirildiğini inceleyeceğiz.

Kimlik Doğrulama İşleminin Zend_Auth_Adapter_DbTable kullanılarak Gerçekleştirilmesi:

Zend_Auth_Adapter_DbTable, kimlik doğrulama işleminin veritabanındaki bir tabloda yeralan kullanıcı kayıtlarını kullanarak gerçekleştirilmesini sağlar. İnşası sırasında bizden aşağıdaki konfigürasyon verilerini beklemektedir:

zendDb: Zend_Db_Adapter_Abstract tipindeki veritabanı bağlantısını içerir.

tableName: Bu parametre, kullanıcı kimlik verilerinin bulunduğu veritabanındaki tablonun adını içermelidir.

identityColumn: Bu alan kullanıcı adı veya e-posta adresi gibi uniq bir kimlik verisi içermelidir.

credentialColumn: Kullanıcı adı/parola şemasındaki kimlik doğrulama yapılarında bu alan, kullanıcı parolasını içermelidir.

credentialTreatment: Pek çok durumda kullanıcı parolasının farklı algoritmalarla şifrelenmesi istenir. Bu alanda şifrelemenin nasıl gerçekleştirileceği metin olarak belirtilir. (MD5(?) gibi)

Ben inceleyeceğimiz örneklerde MySQL tercih ettim. Sizler dilerseiniz, MonboDB, SQLLite veya başka bir veritabanı kullanabilirsiniz.

İlk olarak kullanıcı tablomuzu oluşturalım.

CREATE TABLE `intranet_users` (
`id` INTEGER  NOT NULL AUTO_INCREMENT, 
`username` VARCHAR(50) UNIQUE NOT NULL, 
`password` VARCHAR(32) NULL, 
`real_name` VARCHAR(150) NULL,
PRIMARY KEY (id));

Veritabanı bağlantımızı oluşturalım.

$db = Zend_Db::factory('Pdo_Mysql', array(
    'host'     => '127.0.0.1',
    'username' => 'webuser',
    'password' => 'xxxxxxxx',
    'dbname'   => 'myintranetapp'
));

Yeni bir Zend_Auth_Adapter_DbTable örneği oluşturarak konfigürasyon verilerini örneğin inşası sırasında parametre olarak gönderiyoruz.

$authAdapter = new Zend_Auth_Adapter_DbTable(
    $db,
    'intranet_users',
    'username',
    'password'
);

Kullanıcı adı ve parola bilgilerimizi adaptöre tanımlıyoruz.

authAdapter->setIdentity('myusername')
           ->setCredential('mypassword');

Oturum açıyoruz.

$auth = = Zend_Auth::getInstance();
$result = $auth->authenticate($authAdapter);

Kimlik doğrulama işleminin başarılı/başarısız olma durumunu kontrol ediyoruz.

if( $result->isValid() ) {
    //Oturum basariliyse birseyler yap.
} else {
    //Oturum basarisizsa birseyler yap.
}

Kullanıcımızın kimliğini doğruladık. Ancak bu yeterli değil. Kimlik doğrulamasının başarılı olması durumunda oturum bilgisinin isteğe bağlı olarak dosya sistemi, memory veya veritabanı gibi biryerde saklanması gerekmektedir. Zend_Auth objesi bu bilgileri kayıt etmek için varsayılan olarak PHP Session objesini kullanır. Sizler uygulamalarınızda oturum verilerini dilerseniz veritabanı, dosya sistemi veya Zend_Auth_Storage_Interface arayüzüne uygun kendi geliştireceğiniz farklı bir depolama alanında saklayabilirsiniz.

if( $result->isValid() ) {
    $storage = $auth->getStorage();
    $storage->write($authAdapter->getResultRowObject(array('username', 'real_name')));
} else {
    //Oturum basarisizsa birseyler yap.
}

Projenizin herhangibir yerinde oturum durumunu kontrol etmek isterseniz:

$auth = Zend_Auth::getInstance();
if($auth->hasIdentity()) {
  echo $auth->getIdentity();
}

Oturumu sonlandırmak isterseniz:

$auth = Zend_Auth::getInstance();
$auth->clearIdentity();

Zend_Auth kütüphanesini temel düzeyde inceledik. Sizler de bu kütüphaneyi kolayca uygulamalarınıza uyarlayabilirsiniz. Zend_Auth ile ilgili daha fazla bilgi edinmek için http://framework.zend.com/manual/1.12/en/zend.auth.html sayfasını ziyaret edebilirsiniz. Bu blog yazısı ile Zend_Auth kütüphanesini kullanabileceğinizi düşünüyorsanız, diğer kimlik doğrulama adaptörlerinin kullanımı, yeni bir kimlik doğrulama adaptörünün geliştirilmesi ve diğer session storage adaptörlerinin kullanımı konularını incelemenizi öneririm.

Programa Dilleri ve Çatıları

YII Üzerinde ActiveRecord Kullanımı

Ben yazdığım uygulamalarda uzun uzun SQL yazmayı sevmeyenlerdenim. Neden mi ? Pis duruyor arkadaş! Güvenli olsun diye göndereceğim her bir parametreyi teker teker escape etmek zor geliyor. Projelerimde basitliği nedeniyle zaman zaman ActiveRecord u tercih ediyorum. Bugün sizlere YII üzerinde ActiveRecord kütüphanesinin nasıl kullanılacağı konusundan kısaca bahsedeceğim.

Active Record Yenilir mi İçilir mi ?

ActiveRecord, bildiğiniz DbTable türünden bir PHP kütüphanesidir. Veritabanındaki herbir tablo, CActiveRecord sınıfından türeyen bir sınıf ile temsil edilir.

Şöyle bir tablomuz olsa:

CREATE TABLE tbl_post (
    id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
    title VARCHAR(128) NOT NULL,
    content TEXT NOT NULL,
    create_time INTEGER NOT NULL
);

Oturup PDO ile bu tabloya kayıt insert ediyor olsaydık şunları yapmamız gerekirdi.

$sqlQuery = "INSERT INTO `tbl_post` 
	     SET `title` = :title,
		  `content` = :content";
$pdo = new Pdo('mysql; host=127.0.0.1;dbname=mydb', 'dbuser', 'dbpass');
$statement = $pdo->prepare($sqlQuery);
$statement->bindValue(':title', 'sample post');
$statement->bindValue('content', 'post content');
$statement->execute();

Hakikaten kirli bir kod. Şimdi de gelin bu işi ActiveRecord ile yapalım.

$post = new Post();
$post->title = 'sample title';
$post->content = 'post content';
$post->save();

Bitti..! Temiz, güvenli ve stabil.

Güzelmiş… Peki ActiveRecord’u Nasıl Kullanırım ?

AR Tablolarımızla çatır çatır işlem yapabilmek için veritabanı bağlantımızın sular seller gibi açık olması gerekiyor. YII tarafında veritabanı bağlantısını başlangıçta ayağa kaldırmak için konfigürasyonda aşağıdaki değişikliği gerçekleştirmemiz gerekir.
protected/config/main.php:

return array(
    ......
    'components'=>array(
        ......
        'db'=>array(
            'class'=>'CDbConnection',
            'connectionString'=>'mysql:host=localhost;dbname=testdb',
            'username'=>'myusername',
            'password'=>'mypassword',
            'emulatePrepare'=>true,  // YII nin sitesinde bazi SQL sorgulari icin gerekli oldugundan bahsediyor. Muhtemelen parametre iceren SQL sorgularinin dogrudan calistirilmamasi icin kullanilmasi gereken bir parametre.
        ),
    ),
)

Bir de AR sınıflarımızın autoloader a tanıtılması gerekiyor ki PHP new Post() çağrısını aldığında “Post da kim?” demeisn. AR sınıflarımızın protected/models klasörü altında olduğunu varsayarak, konfigürasyonda aşağıdaki değişiklikleri yapmamız gerekir:
protected/config/main.php:

return array(
  ...... 
  'import'=>array(
      'application.models.*',
  ),
  ......
);

Şimdi de tablomuzu temsil edecek AR sınıfını kodlayalım.

class Post extends CActiveRecord
{
    public static function model($className=__CLASS__)
    {
        return parent::model($className);
    }
 
    public function tableName()
    {
        return 'tbl_post';
    }
}

Not: Eğer konfigürasyonda table prefix tanımlamışsanız tableName() metodu içinde tanımladığınız tablo adını ‘{{post}}’ şeklinde tanımlayabilirsiniz. Bu şekilde yapılan tablo tanımlaması konfigrasyondaki table prefix bilgisinden dolayı otomatik olarak tbl_post şeklinde kullanılacaktır. TablePrefix ile ilgili daha fazla bilgi için bu sayfa yı nceleyebilirsiniz.

AR Sınıflarını Kullanarak INSERT İşlemi Gerçekleştirmek:

Girişde gördüğünüz örnekden anlaşılacağı üzere her bir tablo bir sınıfla, tablodaki her bir alan sınıfın attribute u olarak temsil edilir. Veritabanına yeni bir kayıt oluşturmak için bir adet AR sınıfı instance oluşturup, alanlarınıza verilerinizi eşitledikten sonra save() metodunu çağırmanız yeterlidir. Oluşan yeni kaydın kimliği, primarykey olan field attribute una geri döner. Muhteşem, değil mi ? :)

$post = new Post();
$post->title = 'Sample post';
$post->content = 'Post content';
$post->save();

echo $post->id;

Olur da herhangibir alana veritabanı fonksiyonlarından birinin döndüğü bir değeri set etmek isterseniz :

$post->create_time = CDbExpression('Now()');

AR Sınıflarını Kullanarak SELECT İşlemi Gerçekleştirmek:

Arama işlemleri AR base sınıfı (CActiveRecord) üzerinden gerçekleştirilir. AR sınıflarının base sınıfına ulaşmak için model() metodunu kullanacağız. Aşağıda çeşitli arama şekillerini görüyoruz.

// Belirtilen şarta uygun ilk satırı döner.
$post=Post::model()->find($condition,$params);

// Belirtilen birincil anahtar a sahip (PrimaryKey) kaydı döner.
$post=Post::model()->findByPk($postID,$condition,$params);

// Belirtilen niteliklere uygun ilk satırı döner.
$post=Post::model()->findByAttributes($attributes,$condition,$params);

// SQL cümlesinde verilen şarta uygun kayıtlardan ilk satırı döner.
$post=Post::model()->findBySql($sql,$params);

// Belirtilen şarta uyan tüm satırları döner.
$posts=Post::model()->findAll($condition,$params);

// Belirtilen birincil anahtara uygun tüm kayıtları döner. Pratikte birincil anahtarın tek kayıt ile eşleşmesi beklenir.
$posts=Post::model()->findAllByPk($postIDs,$condition,$params);

//Belirtilen niteliklerle eşleşen tüm kayıtları döner.
$posts=Post::model()->findAllByAttributes($attributes,$condition,$params);

//Belirtilen SQL cümlesine uyan tüm kayıtları döner.
$posts=Post::model()->findAllBySql($sql,$params);

Aslanım ActiveRecord seviyoruz seni. Yukarıda gördüğünüz herbir findXXX metodu arma işleminin gerçekleştiği AR sınıfının bir örneğini yada AR sınıflarından oluşan bir kayıt dizisini döner. Gelin hemen birkaç örnek inceleyelim:

$post = Post::model()->find('id=:id', array(':id' => 10);
echo $pos->id;

condition parametresini isterseniz array(‘id’=>’:id’), isterseniz ‘id=:id’ yok ben öyle de istemiyorum derseniz aşağıdaki şekilde gönderebilirsiniz:

$condition = new CDbCriteria();
$condition->select='title, content';
$condition->condition='id=:id';
$condition->params = array(':id' => 10);

$post = Post::model()->find($condiiton); //Bulursak Post tipinde, bulamazsak null veri döner.

Oldu da birden fazla kayıt dönerse:

$posts = Post::model()->findAll('category_id=:category_id', array(':category_id' => 1));
foreach($posts as $post) {
  echo $post->title;
}

Bir de primary key e göre arayalım.

$post = Post::model()->findByPdk(10);
echo $post->title;

Bir de böyle count’lu exists li işler için aşağıdaki metodlarımız var.

// Belirtilen kriterlere uygun kayit sayisini doner.
$n=Post::model()->count($condition,$params);

// Belirtilen SQL cümlesine uyan kayıtların sayısını döner.
$n=Post::model()->countBySql($sql,$params);

// Belirtilen şartlara uygun kayıt olma durumu ile ilgili mantıksal (boolean:true/false) değer döner.
$exists=Post::model()->exists($condition,$params);

AR Sınıflarını Kullanarak UPDATE İşlemi Gerçekleştirmek:

Update işlemi insert işlemi ile aynıdır. Kaydı bulur, alanlara ilgili verileri yazar, save metodunu çağırırız.

$post = Post::model()->findByPk(1);
$post->title = 'Bu bir denemedir';
$post->save();

Şayet birden fazla kaydı güncellemek istiyorsanız aşağıdaki metodları kullanabilirsiniz:

// belirtilen şartla eşleşen tüm kayıtları günceller.
Post::model()->updateAll($attributes,$condition,$params);

// Belirtilen şartlar ve primary key ile eşleşen tüm kayıtları günceller.
Post::model()->updateByPk($pk,$attributes,$condition,$params);

// Bunu ben de yazarken öğrendim. YII de sayaç olarak atadiginiz alanlari 
// arttirmak için (Örneğin makaleyi okuyan kişi sayısı gibi) saveCounter() 
// ve updateCounter() gibi komutlar varmış. saveCounters(), sadece geçerli AR
// sınıfının sayaç alanlarını güncellerken, updateCounters() metodu birden çok 
// kaydın sayıcılarını güncelleyebiliyormuş.
// Daha detaylı bilgi için bu yazıyı inceleyebilirsiniz: 
// http://www.yiiframework.com/wiki/282/using-counters-with-activerecord/
Post::model()->updateCounters($counters,$condition,$params);

AR Sınıflarını Kullanarak DELETE İşlemi Gerçekleştirmek:

Insert ve Update işlemini gördükten sonra sanırım delete işleminin nasıl yapıldığını tahmin etmeniz çok zor olmamıştır. Kayıt silme işlemi için kaydı bulmamız ve ilgili kaydın AR sınıfından delete() metodunu çağırmamız yeterlidir.

//kaydımızı buluyoruz.
$post = Post::model()->findByPk(1);
//kaydımızı siliyoruz.
$post->delete()

Şayet birden fazla kaydı silmek istiyorsak ActiveRecord bize aşağıdaki metodları sunuyor.

// Belirtilen kriterlere uygun kayıtları siler.
Post::model()->deleteAll($condition,$params);

// Belirtilen birincil anahtar(lar) a uygun kayıt(lar) ı siler.
Post::model()->deleteByPk($pk,$condition,$params);

Bu yazıda temel işlemler dışında çok fazla detaya girmek istemedim. Detaylı ve güncel bilgi için http://www.yiiframework.com/doc/guide/1.1/en/database.ar adresinden yararlanabilirsiniz. ActiveRecord kullanarak ilişkili tablo yapılarında da çalışabilmeniz mümkündür. İlerleyen günlerde bu konuda da bir blog yazısı yayınlayacağım. Her türlü sorunuz veya öneriniz için ibrahimgunduz34(at)gmail.com adresinden bana ulaşabilirsiniz.