Category Archives: Programa Dilleri ve Çatıları

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.



     
     
     
    P
     
    Auth
     
     
     
     
     
    949
    

Ö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ı:**



  TESTUSER
  123qwe123
  006641
  P
  000000000001
  Auth
  5105105105105100
  12/18
  000
  55.41
  949
  1

Programa Dilleri ve Çatıları

Ateistler Bunu da Açıklasınlar

php nin konsoldan nasıl syntax check yaptığını ararken, kaynak kodun html olarak renklendiirlmiş halini üretebildiğini öğrendim.

$ php -s Serializer.php > output.html

<?php
namespace Communication\Adapter;

interface AdapterInterface
{
    
/**
     * send a request with given data.
     *
     * @param string $url
     * @param mixed $data
     * @param array $options (optional)
     */
    
public function sendRequest($url$data$options=null);

    /**
     * returns last sent request.
     *
     * @return string
     */
    
public function getLastSentRequest();

    /**
     * returns last received response from provider.
     *
     * @return string
     */
    
public function getLastReceivedResponse();
}


Programa Dilleri ve Çatıları

Python Decoratorleri ve Observer Uygulaması

Bugün bir python projesi için yazdığım kütüphanenin uzak sunucuyla yaptığı hareketleri loglaması için yeni bir geliştirme yapmam gerekti. Malum, henüz dilin acemisi olduğum için ilk olarak observer tasarım deseninin pythondaki uygulama şekillerini incelemeye koyuldum. Yapmak istediğim şey, tam olarak djangodaki signal modülünün benzeri küçük basit bir kütüphane bulup implemente etmek veya hızlıca yazmaktı.

Okuyup uyguladıkca python a hayranlığım bir kat daha artmaya başladı. Daha önce PHPİst etkinliğine gelen arkadaşlar hatırlayacaktır. Orada da observer tasarım desenini anlatırken loglama örneği üzeirnden gitmiştik. Observer tasarım deseninin PHP uyarlamasını kısaca hatırlayalım.

ObservableAbstract :

ObserverInterface :

LoggerObserver:

...ve tabi ObserverInterface den türeyen yeni observer sınıfları, bu sınıfları ObservableAbstract ailesine bağlayan kodlar...

Peki pythonda ne yaptık ? Aynı işi yapmak için yine php tarafında olduğu gibi bir observable sınıfı ve gözlemcilerimizi atamak için kullanacğımız bir adet dekoratör yazdık.

eventmanager/__init__.py:

class ObservableAbstract(object):
	def __init__(self):
		self._observers = {}

	def attach(event_name, listener):
		self._observers.setdefault(event_name, []).append(listener)

	def _trigger_event(event_name, context):
		if event_name in self._observers:
			context.update({'event_name': event_name})
			for observer in self._observers.get(event_name):
				observer(context)

eventmanager/decorators.py:

from eventmanager import ObservableAbstract

def listener(instance, event_name):
	def wrap_func(func):
		if isinstance(isinstance, ObservableAbstract):
			instance.attach(event_name, func)

Observable ailesinden gelen olay tetiklemelerini dinlemek için, gözlemci olarak kullanacağınız fonksiyonların başına listener() dekoratörünü eklemeniz yeterli.

@listener(instance=MyApiInstance, event_name=AFTER_REQUEST)
def log_handler(context):
	with open('transaction.log', 'w+') as file:
		file.write(str(context))	
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ı

Python ile XML Üretimi

Geçtiğimiz günlerde üzerinde çalıştığım bir uygulamada nitelikleri olan elemanlara sahip bir xml oluşturmam gerekiyordu. Internette biraz araştırma yaptıktan sonra markup.py ile tanıştım.

Markup.py, xml oluşturmak için kullanabileceğiniz açık kaynaklı oldukca kullanışlı bir kütüphane. Bu kütüphanenin en son sürümüne http://sourceforge.net/project/showfiles.php?group_id=161108 adresinden ulaşabilirsiniz.

Örnek:

import markup

titles = ( 'Best features of M-theory', 'Best bugs in M-theory', 'Branes and brains' )
universities = ( 'Cambridge', 'MIT', 'Amsterdam' )
dates = ( 'January', 'February', 'March' )

myxml = markup.page( mode='xml' )
myxml.init( encoding='ISO-8859-2' )

myxml.cv.open( )
myxml.talk( titles, university=universities, date=dates )
myxml.cv.close( )

print myxml

Örnek Çıktısı:



	Best features of M-theory
	Best bugs in M-theory
	Branes and brains

Yukarıdaki örnekde Encoding tipi iso-8859-2 olan, cv kök düğümü altında talk isimli 3 adet elemanı olan küçük bir xml ürettik. sıralı arguman olarak verdiğimiz her parametrenin xml elemanının değeri, anahtar kelime argumanı olarak verdiğimiz her parametrenin xml elemanının niteliği olarak oluşturulduğuna dikkatinizi çekmek isterim.

Kütüphaneyle ilgili detaylı bilgiye http://markup.sourceforge.net/ adresinden ulaşabilirsiniz.

Programa Dilleri ve Çatıları

Paranoia Ödeme Kütüphanesi Tanıtımı

Birkaç yıl önce yazdığım bir ödeme kütüphanesini, elini yüzünü biraz düzelttikten sonra kaynağını açmaya karar verdim. Kütüphanemin adı Paranoia!

Paranoia Türkiye’deki bankaların kullandığı ortak altyapı api lerinin tek bir arayüz üzerinden kullanılabilmesi için geliştirilmiş bir kütüphanedir. Implementasyonunun son derece basit olmasına özen gösterdim ancak hedeflediğim noktaya gelmesi için çözülmeyi bekleyen yaklaşık 13-14 issue su var.

Paranoia şu an yalnızca Est altyapısını destekleyen bankalarla çalışabilmektedir. Ancak pek yakında Gvp, Posnet yaltyapılarına ve Turkcell Cüzdan, İşbank parakod gibi alternatif ödeme sistemlerine de destek verecektir.

Paranoia nın mevcut durumu aşağıdaki gibidir.

 ESTGVPPOSNETTurkcell Cüzdanİşbank Parakod
Ön Otorizasyon (Pre-Authorization)HazırPek Yakında…Pek Yakında…Pek Yakında…Pek Yakında…
Post Otorizasyon (Post-Authorization)HazırPek Yakında…Pek Yakında…Pek Yakında…Pek Yakında…
Satış (sales)HazırPek Yakında…Pek Yakında…Pek Yakında…Pek Yakında…
İade (refund)HazırPek Yakında…Pek Yakında…Pek Yakında…Pek Yakında…
İptal (cancel)HazırPek Yakında…Pek Yakında…Pek Yakında…Pek Yakında…
Sipariş tarihcesi sorgulama (inquiry)Pek Yakında…Pek Yakında…Pek Yakında…Pek Yakında…Pek Yakında…
3D-Secure ile satışPek Yakında…Pek Yakında…Pek Yakında…
Puan sorgulama (pointInquiry)Pek Yakında…Pek Yakında…Pek Yakında…
Puan kullanımı (pointUsage)Pek Yakında…Pek Yakında…Pek Yakında…

Paranoia, implementasyonu son derece basit ve açık kaynak kodlu bir kütüphanedir.

Kütüphanenin Zend_Config bağımlılığı bulunmaktadır, kaynak kodlarına https://github.com/ibrahimgunduz34/paranoia adresindeki github reposundan ulaşabilirsiniz.

Ödeme işlemi:

Banka tanımlamaları, kütüphanenin config/payment.ini dosyası içinde, aşağıdaki şekilde gerçekleştirilmektedir:

[production]
isbank.adapter = "Est"
isbank.api_url = ""
isbank.username = ""
isbank.password = ""
isbank.client_id = ""
isbank.mode = "P"

[development : production]
isbank.api_url = "https://testsanalpos.est.com.tr/servlet/cc5ApiServer"
isbank.username = "ISBANKAPI"
isbank.password = "ISBANK07"
isbank.client_id = "700100000"
isbank.mode = "T"

Konfigürasyon verilerini bankaya iletmek üzere okuyoruz.

$config = new \Zend_Config(APPLICATION_PATH . '/config/payment.ini', APPLICATION_ENV);

İşlem sırasında ihtiyacımız olan kütüphaneleri import ediyoruz.

use \Payment\Factory;
use \Payment\Request;
use \Payment\Exception\UnexpectedResponse;
use \Payment\Adapter\Container\Exception\ConnectionFailed;

Ödeme işlemini gerçekleştirmek için sipariş bilgilerimizi içeren Request nesnesini oluşturuyoruz.

$request = new Request();
$request->setCardNumber('5105105105105100')
        ->setSecurityCode('510')
        ->setExpireMonth(3)
        ->setExpireYear(2014)
        ->setOrderNumber('ORD000001')
        ->setAmount(100.35)
        ->setCurrency('TRL');

Ödeme için kullanacağımız banka adaptörünü yaratıyor ve ödeme işlemini gerçekleştiriyoruz.

$instance = Factory::createInstance($config, 'isbank');
try {
    $response = $instance->sale($request);
    if( $response->isSuccess() ) {
        echo 'Payment is performed successfuly.';   
    } else {
        echo 'Payment is failed.';
    }
} catch(UnexpectedResponse $e) {
    echo 'Provider is responded an unexpected response.';
} catch(ConnectionFailed $e) {
    echo 'Provider connection is failed.';
} catch(Exception $e) {
    echo 'Payment transaction deosn\'t performed. Please try again later.';
}

Ödeme işleminin sonucu \Payment\Response\PaymentResponse olarak dönmektedir. Bu objeden aşağıdaki metodlarla transactionid, orderid, responsecode ve response message gibi bilgileri elde edebilirsiniz.

$response->isSuccess()
$response->getResponseCode()
$response->getResponseMessage()
$response->getTransactionId()
$response->getOrder()

İade işlemi:

İade işlemini gerçekleştirmek için ödeme işlemi sırasında 1. ve 2. adımda gerçekleştirdiğimiz konfigürasyonun okunması ve kullanılacak kütüphanelerin import edilmesi işlemlerini tekrar gerçekleştirmemiz gerekiyor.

Para iadesi yapacağımız sipariş için istek nesnesi oluşturuyoruz.

$request = new Request();
$request->setOrderNumber('ORD000001')
        ->setAmount(100.35) 
        ->setCurrency('TRL');

Siparişin tutarının tamamını iade etmek isterseniz, Amount ve Currency değerlerini göndermenize gerek yoktur.

$instance = Factory::createInstance($config, 'isbank');
try {
    $response = $instance->refund($request);
    if( $response->isSuccess() ) {
        echo 'Refunded transaction is performed successfuly.';   
    } else {
        echo 'Refund transaction is failed.';
    }
} catch(UnexpectedResponse $e) {
    echo 'Provider is responded an unexpected response.';
} catch(ConnectionFailed $e) {
    echo 'Provider connection is failed.';
} catch(Exception $e) {
    echo 'Refund transaction deosn\'t performed. Please try again later.';
}

İptal işlemi:

İade işlemini gerçekleştirmek için ödeme işlemi sırasında 1. ve 2. adımda gerçekleştirdiğimiz konfigürasyonun okunması ve kullanılacak kütüphanelerin import edilmesi işlemlerini tekrar gerçekleştirmemiz gerekiyor.

Para iadesi yapacağımız sipariş için istek nesnesi oluşturuyoruz.

$request = new Request();
$request->setOrderNumber('ORD000001');

İade işlemi satış işlemi dışında diğer tüm kısmi ve tam iade işlemleri için de gerçekleştirilebilmektedir. Siparişinizle ilgili satış işlemi dışında gerçekleştirdiğiniz herhangibir iade işlemi varsa, sipariş numarasıyla birlikte transactionid bilgisini de request objesi ile göndermelisiniz.

$instance = Factory::createInstance($config, 'isbank');
try {
    $response = $instance->cancel($request);
    if( $response->isSuccess() ) {
        echo 'Transaction is cancelled successfuly.';   
    } else {
        echo 'Cancelation is failed.';
    }
} catch(UnexpectedResponse $e) {
    echo 'Provider is responded an unexpected response.';
} catch(ConnectionFailed $e) {
    echo 'Provider connection is failed.';
} catch(Exception $e) {
    echo 'Cancelation deosn\'t performed. Please try again later.';
}
Programa Dilleri ve Çatıları

PHP Dahili Web Sunucusuna Ortam Değişkeni Göndermek

Bu haftasonu zend 1.12 ile uğraştığım bir projede apache konfigürasyonu yapmaya üşendiğim için php nin yerel web sunucusunu kullandım. Ancak apache de virtualhost ayarlarından gönderebildiğim ortam değişkenini (APPLICATION_ENV), komut satırında bir türlü göndermeyi başaramadım. Internette güç bela bununla ilgili kaynak bulunca konuyu sizlerle paylaşmak istedim. Umarım birilerinin işine yarar.
***
PHP’nin dahili web sunucusuna ortam değişkenini göndermek için komut satırında ortam değişkeninizin değerini tanımlayarak -d parametresi ile değişken çözümleme sırasını EGPCS (Environment, Get, Post, Cookie, Server) olarak göndermeniz gerekir.

Örnek kullanım:

$ APPLICATION_ENV=development php -d variable_orders=EGPCS -S localhost:8080 -t public/
Programa Dilleri ve Çatıları

Django Framework Template Etiketlerinde User Objesine Erişmek

Aslında django kullanıcıları için muhtemelen çok basit bir konu ancak benim gibi yeni başlayanlar için sorun olabilecek konulardan biri olduğu için yazıyı sizinle paylaşmak istedim.

Problem:
Django öğrenmek için yazdığım blog projesinde kullanıcıya gösterdiğim blog postunun yeni comment girişi alanını, comment blog postuna veya başka bir comment e yanıt olarak girilebileceğinden inclusion_tag yaptım. Ancak inclusion tag in include ettiği template içinde user objesine ulaşamıyorum.

Çözüm:
Uzun stackoverflow okumlarından sonra inclusion_tag e parametre olarak takes_context göndersem de tag e parametre olarak gelen context içinde user objesinin gelmediğini öğrendim. Bunun yerine view tarafında, ilgili actionda return edilen render fonksiyonuna parametre olarak, context_instance için RequestContext göndermem gerekiyormuş. Böyle olunca tag e parametre olarak gelen context içinde user objesinin yeraldığını gördüm.

Problemi çözmek için aşağıdaki adımları uyguladım:

1. view tarafında django.template içindeki RequestContext objesini import ettim.
myapp/views.py:

from django.template import RequestContext

2. İlgili template tag i kullanacağım view ın aksiyonunda return ettiğim render() fonksiyonuna context_instance olarak RequestContext i gönderdim.
myapp/views.py:

def viewpost(request, post_id):
    post = get_object_or_404(Post.objects.select_related('author'), pk=post_id)
    ...
    return render(
        request, 'blog/post/detail.html', 
        {'post' : post}, 
        context_instance=RequestContext(request)
    )

3. template tag tarafında include edeceğim template e veri olarak user objesini gönderdim.
myapp/templatetags/myapp_extra.py:

@register.inclusion_tag('blog/post/blocks/comment/new.html', takes_context=True)
def show_comment_input(context):
    return {'user' : context['user']}

4. Ve… template tag imin include ettiği template kodu.
blog/post/blocks/comment/new.html:

Write your comment or reply

{% if not user.is_authenticated %}
{% endif %}
{% csrf_token %}

Elimdeki proje bittikten sonra gelen Edit:
Context instance olarak zaten request objesi gönderilmiş ve user objesi context’in içinde halihazırda mevcut. Dolayısıyla inclusion tag tanımlarken aşağıdaki gibi bir tanımlama yapmamıza gerek yok. Zaten takes_context dediğimizde context içeriği include edilen template e push ediliyor.
Aşağıdaki değişikliği yapabilirsiniz:
Eski:

@register.inclusion_tag('blog/post/blocks/comment/new.html', takes_context=True)
def show_comment_input(context):
    return {'user' : context['user']}

Yeni:

@register.inclusion_tag('blog/post/blocks/comment/new.html', takes_context=True)
def show_comment_input(context):
    pass

Kaynaklar:
http://stackoverflow.com/questions/2160261/access-request-in-django-custom-template-tags
http://stackoverflow.com/questions/3337419/django-user-is-authenticated-works-some-places-not-others

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.