Tag Archives: Software Development

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

Genel

Django Modellerinde ManyToMany Alanların Belirli Bir Filtreye Göre Limitlenmesi

Sistem genelinde taglayabildiğiniz tüm nesne tipleri ile ilgili tag verilerinin tek bir tabloda tutulduğunu düşünelim. Bu durumda tag kayıtlarını bir şekilde sınıflandırmamız ve ilgili nesnelerin sadece kendisiyle ilgili tipteki taglere sahip olabilmesini sağlamalıyız. Bu yapıyı sağlayabilmek için ManyToManyField tanımlaması yapılırken limit_choice_to parametresi kullanılarak ilişkili tablonun neye göre filtreleneceğini dict olarak tanımlamamız gerekir.

Böylece django admin tarafında da kullanıcının ilgili kaydı editlerken manytomany alanda yalnızca kendisiyle ilgili kayıtları görmesi/seçebilmesi sağlanır.

Aşağıda örnek veri modeli yeralıyor.

from django.db import models


class Tag(models.Model):
    TAG_TYPES = (
        ('article', 'Article'),
        ('video', 'Video'),
    )
    slug = models.SlugField(max_length=50)                                     
    name = models.CharField(max_length=50)   
    tag_type = models.CharField(max_length=30)    


class Article(models.Model):
    slug = models.SlugField(max_length=80)
    title = models.CharField(max_length=80)
    content = models.TextField()
    tags = models.ManyToManyField(Tag, limit_choices_to={'tag_type': 'article'})


class Video(models.Model):
    slug = models.SlugField(max_length=50)
    name = models.CharField(max_length=50)
    url = models.UrlField()
    tags = models.ManyToManyField(Tag, limit_choices_to={'tag_type': 'video'})
Genel

__getattr__ vs. __getattribute__

__getattr__ Kullanımı:
__getattr__, uygulamada bir sınıfın varolmayan bir özelliğine ulaşılmak istendiğinde çağırılen bir magic metoddur.

class Order(object):
    def __init__(self, order_id=None, amount=None, currency=None, *args, **kwargs):
        self.order_id = None
        self.amount = amount
        self.currency = currency

    def __getattr__(self, attr):
        print "%s is called" % attr


order = Order(order_id='00001', amount=100, currency='TRL')
print order.amount
print order.original_amount


Çıktı:

100
original_amount is called
None

__getattribute__ Kullanımı:
__getattribute__, uygulamada bir sınıfın herhangibir özelliğine ulaşılmak istendiğinde çağırılen bir magic metoddur.

class Order(object):
    def __init__(self, order_id=None, amount=None, currency=None, *args, **kwargs):
        self.order_id = None
        self.amount = amount
        self.currency = currency

    def __getattribute__(self, attr):
        print "%s is called" % attr


order = Order(order_id='00001', amount=100, currency='TRL')
print order.amount
print order.original_amount

Çıktı:

amount is called
None
original_amount is called
None
Genel

Python ile Web Crawling

Aslında üstüne pek de yazı yazılası bir konu değil ama yine de belki meraklısının işine yarar :)

Herhangibir dille web sitesi crawle etmek için bilinmesi gereken 3 şey

1- Nasıl http isteği gönderebilirim ?
2- Nasıl xpath sorgusu gerçekleştirebilirim ?
3- Elde ettiğim elemanların özelliklerine nasıl ulaşabilirim ?

Dilerseniz konuyu bu başlıklar altında inceleyelim.

1- Nasıl http isteği gönderebilirim ?

Python ile http request gönderebilmek için requests veya urllib2 kütüphanelerini kullanabilirsiniz.

Ben bu yazıdaki örneklerde urllib2 kullanacağım.

Örnek GET isteği:

	url = 'http://edition.cnn.com/'
	try:
		request = urllib2.Request(url)
		response = urllib2.urlopen(request).read()
	except urllib2.URLError:
		print "Connection failed. Url: %s" % url

2- Nasıl xpath sorgusu gerçekleştirebilirim ?

Python ile xpath sorgusu gerçekleştirmek için xml.ElementTree veya lxml kullanabilirsiniz.

Ben bu yazıdaki örneklerde lxml kullanacağım.

Aşağıdaki örnekde, CNN.com sitesindeki haber kategorilerini sorguluyoruz.
Örnek Xpath sorgusu:

	from lxml import html
	query = '//*[@id="intl-menu"]/li/a'
	root = html.fromstring(response)
	categories = root.xpath(query)

3- Elde ettiğim elemanların özelliklerine nasıl ulaşabilirim ?

Bir önceki örnde kullandığım lxml kütüphanesini kullanıyorsanız, xpath sorgusu sonuncunda dönen elemanın DOM özelliklerine ulaşmak için get() metodunu, içindeki metne ulaşmak için text özelliğini, alt elemanlarına ulaşmak için getchildren() metodunu kullanabilirsiniz.

for category in categories:
	children = category.getchildren()
	text = children and children[0].text or category.text
	print text, category.get('href')

Son olarak herşeyi bir araya getirelim.

from lxml import html
import urllib2

url = 'http://edition.cnn.com/'
try:
	request = urllib2.Request(url)
	response = urllib2.urlopen(request).read()
except urllib2.URLError:
	print "Connection failed. Url: %s" % url

query = '//*[@id="intl-menu"]/li/a'
root = html.fromstring(response)
categories = root.xpath(query)

for category in categories:
	children = category.getchildren()
	text = children and children[0].text or category.text
	print text, category.get('href')

Elde edeceğimiz sonuç aşağıdaki biçimde olacaktır.

Home /
Video /video/
World /WORLD/
U.S. /US/
Africa /AFRICA/
Asia /ASIA/
Europe /EUROPE/
Latin America /LATINAMERICA/
Middle East /MIDDLEEAST
Money http://money.cnn.com/INTERNATIONAL/
World Sport /SPORT/
Entertainment /SHOWBIZ/
Tech /TECH/
Travel http://travel.cnn.com/
iReport http://ireport.cnn.com/
Genel

Django REST Framework İle RESTFul Web Servisi Geliştirme

django-restframework-logo
Django REST Framework, Django uygulamalarınız için RESTFul web servisi geliştirmenize olanak sağlayan oldukca kullanışlı bir python paketi. Benzeri diğer bir python kütüphanesi olan Tastypie ile karşılaştırdığımda ilk bakışta anlaması/öğrenmesi oldukca kolay göründüğünü söylemeliyim.

Django REST Framework ile RESTFul web servisi geliştirmek, aslında bildiğimiz django view lerini kullanarak uygulama geliştirmeye oldukca benziyor. Kendi view sınıf ve dekoratörleri djangodaki class ve function based viewler gibi kodlama yapmanıza olanak sağlıyor.

Hızlı başlangıç için: şuradaki
Kütüphanenin güncel kaynak kodları için: buradaki
bağlantıları inceleyebilirsiniz.

Dilerseniz hemen ilk örnek uygulamamızı yazmak için yeni bir virtual environment oluşturalım ve aktif hale getirelim.

$ virtualenv myblog-env
$ cd myblog-env
$ source bin/activate

Ben bu örnek için Django 1.6.6 kullanacağım.

$ pip install django==1.6.6

ve… Django REST Framework paketinin kurulumunu gerçekleştirelim. Ben bu örnek için paketin bugünki güncel sürümü olan 2.4.0 sürümünü kuracağım.

$ pip install djangorestframework==2.4.0

Evet… Artık geliştirmeye hazırız. Hemen django projemizi ve uygulamamızı oluşturalım.

$ django-admin.py startproject myblog
$ cd myblog
$ python manage.py startapp blog

Hemen basitce bir Post modeli oluşturalım.

myblog/blog/models.py:

from django.contrib.auth.models import User
from django.db import models


class Post(models.Model):
	title = models.CharField(length=80)
	content = models.TextField()
	author = models.ForeignKey(User)

Uygulamamızı settings.py dosyasındaki INSTALLED_APPS bölümüne ekleyelim.

myblog/myblog/settings.py:

...
INSTALLED_APPS = (
...
	'rest_framework',
	'blog',
...
)
...

Tablolarımızı yaratalım…

$ python manage.py syncdb

An itibarıyla temel bir uygulama geliştirebilecek şartlara sahibiz. Bir adet veri modelimiz var ve bir API vasıtasıyla bu modele veri girişi yaptırmak istiyoruz. Başlarken, Django REST Framework kütüphanesi ile web servisi geliştirmenin, class veya function based viewler ile bildiğimiz django uygulaması geliştirmekten çok da farklı olmadığından söz etmiştik. Bilinen django view leri, sunu katmanından gelen veriyi işleyip HTTPResponse tipinde yeni bir obje dönmekteydi. Django REST Framework tarafında ise kullanıcıdan alınan veriler veya kullanıcı tarafına gönderilen veriler Serializer objeleri üzerinden API viewleri ile haberleşir.

Serializer nesneleri, uygulama altında serializers.py isimli python dosyalarında barındırılırlar.

Post modülümüz için yeni bir Serializer objesi oluşturalım.

myblog/blog/serializers.py:

from blog.models import Post
from django.contrib.auth.models import User
from rest_framework import serializers


class UserSerializer(serializer.HyperlinkModelSerializer):
	class Meta:
		model = User
		fields = ('url', 'username', 'email', 'groups')


class PostSerializer(serializer.HyperlinkModelSerializer):
	class Meta:
		model = Post
		fields = ('title', 'content', 'author')

Serializer objeleri doğrudan modeli temsil edebilecekleri gibi serializer.Serializer sınıfından türetilerek özel bir veri tipi için de kişiselleştirilmiş Serializer objeleri oluşturulabilir.

Sıra geldi kullanıcıdan gelen istekleri karşılamaya. Model ve serializer kodlamasını tamamladığımıza göre artık Post modelimiz için yeni bir view(lar) oluşturabiliriz. Django REST Framework başta da belirttiğimiz gibi aynı Django uygulamalarında olduğu gibi class ve function based viewlar oluşturabilmemize izin veriyordu. Ben örnek uygulamamız için kolay okunabilmesi nedeniyle class base view tercih edeceğim.

from blog.serializers import PostSerializer

from django.http import Http404

from rest_framework import views
from rest_framework.response import Response
from rest_framework import status


class PostList(views.APIView):
	def get(self, request, format=None):
		posts = Post.objects.all()
		serializer = PostSerializer(posts, many=True)
		return Response(serializer.data)

	def post(self, request, format=None):
		serializer = PostSerializer(data=request.DATA)
		if serializer.is_valid():
			serializer.save()
			return Response(serializer.data, status=status.HTTP_201_CREATED)
		return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

class PostDetail(views.APIView):
    def get_object(self, pk):
        try:
            return Post.objects.get(pk=pk)
        except Post.DoesNotExist:
            raise Http404

    def get(self, request, pk, format=None):
        post = self.get_object(pk)
        serializer = PostSerializer(snippet)
        return Response(serializer.data)

    def put(self, request, pk, format=None):
        post = self.get_object(pk)
        serializer = PostSerializer(post, data=request.DATA)
        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

    def delete(self, request, pk, format=None):
        post = self.get_object(pk)
        post.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

Gördüğünüz üzere Post modeli için GET, PUT, POST, DELETE isteklerine cevap verebilen detail ve list viewlerimizi oluşturduk.

Django REST Framework class based view lerin method handle etmeyen halleri gibi tanımlayabileceğimiz ViewSet denilen uyarlamaları da kullanabilmemize olanak sunar. ViewSetler farklı kütüphanelerde Resource gibi farklı şekillerde de isimlendirilerler.

from blog.models import Post
from blog.serializers import PostSerializer

from rest_framework import viewsets


class PostViewSet(viewsets.ModelViewSet):
	queryset = Post.objects.all()
	serializer_class = PostSerializer

Bu yazı Django REST Framework kütüphanesini başlangıç seviyesinde tanıtmayı amaçladığından şimdlilik bu bölümü anlatmayacağım. Merak edenler dökümaın şu bölümünü inceleyebilirler.

Class based viewlerimizi oluşturduğumuza göre sıra geldi router a viewlerimizi kayıt etmeye.

myblog/myblog/urls.py:

from django.conf.urls import patterns, url
from rest_framework.urlpatterns import format_suffix_patterns
from blog import views

urlpatterns = patterns('',
    url(r'^post/$', views.PostList.as_view()),
    url(r'^post/(?P<pk>[0-9]+)/$', views.PostDetail.as_view()),
)

urlpatterns = format_suffix_patterns(urlpatterns)

Artık uygulamamız istekleri kabul etmeye hazır. Örnek istek oluşturup yanıtını göstermeye üşendiğim için işin en zevkli kısmınıı size bırakıyorum :)

Django REST Framework konusunda anlatılacak çok şey var. Kullanışlı ve son derece esnek. Detaylı dökümantasyon ve örnekler için Django REST Framework Resmi Web Sitesi‘ni ziyaret edebilirsiniz.

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

Django Framework Üzerinde Sinyal Kullanımı

Django, sinyaller vasıtasıyla uygulama genelinde meydana gelen herhangibir olayı, sinyali dinleyen alıcılara bildirir. Geliştiriciler, djanog ile birlikte gelen hali hazırdaki sinyalleri kullanabildikleri gibi (Bkz. Django ile birlikte gelen hali hazırda sinyaller) kendileri de geliştirdikleri uygulamaya özel sinyaller tanımlayabilirler.

Django uygulamalarında sinyaller, alıcılar vasıtasıyla dinlenir. Django dünyasında alıcılar, bilindik python fonksiyonlarıdır.

Farzı misal bakınız bir sinyal alıcısı:

from django.core.signals import request_finished
from django.dispatch import receiver

@receiver(request_finished)
def my_callback(sender, **kwargs):
    print("Request finished!")

Alıcıları, @receiver dekoratörü ile sinyallere bağlayabildiğiniz gibi

request_finished.connect(my_callback)

şeklinde de bağlayabilirsiniz.

Kullanıcı Tanımlı Sinyaller:

Django, bizlere http isteklerinin durumu ve veritabanı işlemleri ile ilgili dahili sinyaller sağlıyor. Ancak bazı durumlarda geliştirdiğiniz uygulamaya spesifik bazı özel sinyaller tanımlamanız gerekebilmektedir. Böyle durumlarda sizler de Django nun django.dispath modülünde yeralan Signal sınıfını kullanabilirsiniz.

Sinyal tanımlamalarını ilgili uygulamanın altında signals.py dosyası içinde gerçekleştirilir.

Aşağıdaki örnekte, e-ticaret uygulamamızın sipariş tamamlandığında order_completed sinyalini tetiklemesini sağlıyoruz.

öncelikle order_completed sinyalini tanımlayalım:

# order/signals.py

import django.dispath

order_completed = django.dispath.Signal(providing_args=['order'])

Şimdi de sipariş tamamlandığında bu olayı tetikleyelim.

# order/services.py

from order.signals import order_completed

class OrderManager(object):
    def create_order(self, *args, **kwargs):
        # siparisle ilgili islemler...
        #
        # yeni siparis yaratiliyor.
        order = Order.objects.create(order_nr='00001234')

        # ...ve siparis tamamlandi olayini tetikliyoruz.
        order_completed.send(sender=self, order=order)
        return order

Herhangibir sipariş geldiğinde, sipariş order tablosuna yazıldıktan hemen sonra order_completed sinyalinin gönderildiğini görebiliyoruz. Artık diledğimiz bir veya daha fazla alıcı ile bu sinyali dinleyerek bu sinyalle ilgili herhangibir işlemi gerçekleştirebiliriz. Yeri gelmişken Django uygulamalarında sinyallerin senkron çalıştığını söylemekte yarar var. Alıcı tarafında meydana gelen herhangibir darboğaz veya hata, uygulamanın yavaşlamasına veya kırılmasına neden olabilir.

Şimdi de aynı örnek için order_completed olayını dinleyen bir alıcı kodlayalım. Alıcılar konusunda malesef standart bir yaklaşım yok. İnternette görebileceğiniz örneklerde alıcılar şayet modelle ilgili bir alıcıysa ilgili olduğu uygulamının models.py dosyasına sinyal konusu olan model sınıfının hemen altına yazılıyor.

Şahsi görüşüm, sinyal alıcılarının business logic içermesi ve modelle ilgisinin sadece kendisiyle olan bağ olması nedeniyle ayrı bir yerde tutulması gerektiği yönünde. Bu nedenle ben, yeraldığım projelerde sinyal alıcılarını ilgili uygulamanın altında receivers.py içinde barındırmayı tercih ediyorum. Tabi bu durumda alıcıların uygulama genelinde işlevlerini yerine getirebilmeleri için merkezi biryerlerde import edilmeleri gerektiğinden import işlemini urls.py içerisinde gerçekleştiriyorum.

Aşağıdaki örnek alıcı, sipariş tamamlandığında yöneticiye sipariş tamamlandı maili gönderir.

# order/receivers.py

from django.dispatch import receiver
from django.core.mail import mail_admins
from order.signals import order_completed

@receiver(order_completed, 
          dispatch_uid="order.receivers.send_order_notification")
def send_order_notification(sender, order, **kwargs):
    mail_admins(subject='order completed #%s' % order.order_nr,
                message='Received a new order from site. '
                'New order number is: #%s' % order.order_nr)

Alıcının işlevini yerine getirebilmesi için urls.py dosyasında recivers.py ın içerdiği alıcıları import ediyoruz.

# urls.py

from order.receivers import *

# ...

Detaylı bilgi için Django Framework dökümantasyonundaki ilgili bölümü inceleyebilirsiniz.

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ı

Eclipse PDT Auto Complete Sorunu

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