PhP ile 3 Katmanlı Mimariye Uygun Profesyonel Web Uygulamaları Geliştirme
Uygulamaların yaşam döngüsünün uzun olması, minimum kod yazımı, uygulamanın anlaşılır ve geliştirilebilir olması için üç katmanlı mimarinin(3 tier architecture) artılarından faydalanmak gerekir. Sunum, iş ve veri katmanlarının olması, uygulamanızı birçok açıdan geliştirir. Gerek güvenlik gerekse de yazılım mühendisliği yöntemlerinden faydalanma bakımından çok katmanlı mimariler(N tier architecture) ile uygulama geliştirme, izlenmesi gereken bir yoldur.
Üç katmanlı mimarinin en üstünde sunum katmanı(presentation layer) yatar. Bu katmanda son kullanıcıya gösterilmesi gereken veriler sunulur. Burada herhangi bir işlem yapılmaz. Kullanıcı ile iletişimde(interaction) olan katmandır. O nedenle iyi bir şekilde hazırlanmalı, kullanımı kolay arayüzler(GUI, graphical user interface) ile sunulmalıdır. Web ortamında kullanıcıların gördüğü HTML sayfaları bu katmana örnek verilebilir.
İş katmanı(business layer), bir üst katmandır. Sunum katmanından gelen istekleri hazırlayan ve veri katmanı ile haberleşen yer burasıdır. Bütün iş kuraları(business rules) burada bulunur. Bankacılık uygulamalarında yapılan EFT işlemi için, yeterli bakiye kontrolü, EFT yapılacak hesap geçerliliği gibi kontrollerin yapıldığı yerdir. Web uygulamalarında sıkça görülen geçerlilik(validation) kontrolleri kimi zaman iş katmanı ile birlikte sunum katmanında da yapılır.
Veri katmanı(data layer), iş kurallarından habersiz bir şekilde kendisinden istenen verileri işler, hazırlar. Bu katman bazen bir XML dosyası bazen de Oracle gibi DBMS olabilir. Veritabanı içinden bir veri kümesinin(resultset) gelmesi bu katman tarafından gerçekleştirilir.
Uygulamalarda bu katmanların belirgin bir ölçüde ayrılmasının birçok avantajları vardır. Bunların en başında modülerlik yatar. Örneğin üç katmanlı mimariye uygun bir program oluşturdunuz. Bu uygulama sadece web üzerinden çalışacak olsun. Aradan belli bir zaman geçtikten sonra aynı projenin masaüstü uygulamasını da yapmanızın gerekti. Üç katmanlı mimaride sadece sunum katmanını değiştirerek kolaylıkla masaüstü uygulamanızı gerçekleştirebilirsiniz. Yani web sayfası yerine yapacağınız bir Windows formu işleminizi tamamlayacaktır. Veya uygulamanızın şuan için MSSQL üzerinde çalıştığını ileride Oracle’ye geçtiğinizi varsayalım. Yine sadece yapmanız gereken veri katmanını değiştirmeniz olacaktır. Sürekli değişen ihtiyaçlar göz nünde tutulursa üç katmanlı mimarinin ne derece önemli olduğu görülecektir.
Web üzerinde yapılan çalışmalarda veritabanı vazgeçilmez olmuştur. PhP-MySQL ikilisi gerek ücretsiz olmaları gerekse de destek imkanı nedeniyle webde ilk sıralarda gelen ikililer olarak karşımıza çıkmaktadır. Scripting dillerinden biri olan php ile uygulama geliştirirken 3 katmanlı mimarini kullanılması bir çok programcı için önemli olmayabilir. Ama gelişen gereksinimlere daha kolay adapte olmak istiyorsanız yukarıda belirtilen maddeleri php ile yaptığınız uygulamalarda kullanmanız gerekir. Bu sayede daha etkin bir uygulamalar oluşturabileceksiniz.
3 katmanlı mimari ile oluşturulmuş bir php uygulamasını aşağıda belirteceğim. Örnek uygulama basitçe, kullanıcıdan veri girişi alacak ve girilen kayıtların gösterimini yapacaktır. Veritabanı olarak MySQL kullanılacaktır.
Öncelikle uygulama içinde kullanılacak global değişkenleri bir konfügrasyon dosyasında tanımlayalım. Bu sayede uygulama konfügrasyonu, tek bir yerden kolaylıkla yapılmış olacaktır. Bir çok uygulama ve programlama dili bu tip konfügrasyon dosyalarını XML tabanlı yapmaktadır.(web.config, build.xml)
<?php
class parameters
{
var $dbUser = "root";
var $dbPassword = "0000";
var $dbName = "annual";
var $server = "localhost";
var $mailPrefix = "[YILLIK]";
var $mailSender = "annual_alerts_noreplay@kagitkalem.com";
var $adminMail = "mtekbir@gmail.com";
var $charSet = "ISO-8859-9";
var $setNames = "latin5";
var $cellDelimiter;
var $rowDelimiter;
var $adminFullName = "admin";
var $logError = true;
function parameters()
{
$this->cellDelimiter = sprintf("%c", 17);
$this->rowDelimiter = sprintf("%c", 15);
}
}
?>
Öncelikle en temelde yer alacak katmanımız olan veri katmanını tasarlayalım. Burada yapılması gereken, iş kurallarından minimum şekilde bağımsız yapıp sadece veri işlemlerini tanımlamak olmalıdır:
<?php
class mySQL
{
var $params;
var $errorStatus;
var $errorText;
function mySQL()
{
require_once("parameters.php");
$this->params = new parameters();
}
function openConnection()
{
@mysql_connect($this->params->server, $this->params->dbUser, $this->params->dbPassword) or $this->errorOccured( 'Can not connect to server...' );
@mysql_select_db($this->params->dbName) or $this->errorOccured('Can not load DB...');
}
function closeConnection()
{
return mysql_close();
}
function errorOccured( $errorMessage = 'Error Occured' )
{
$this->logError($errorMessage);
}
function executeQuery( $sqlStatement )
{
$sqlQuery = $this->execute( $sqlStatement );
return $sqlQuery;
}
function getResultSet( $sqlStatement )
{
unset($resultSet );
$resultSet = $this->executeQuery( $sqlStatement );
return $resultSet;
}
function getRecord( $resultSet )
{
$record = mysql_fetch_row( $resultSet );
return $record;
}
function getSingleQueryResult( $sqlStatement )
{
$this->openConnection();
$result = $this->getRecord( $this->getResultSet( $sqlStatement ) );
$this->closeConnection();
return $result[0];
}
function executeScalar( $sqlStatement )
{
$this->openConnection();
$result = $this->executeQuery( $sqlStatement );
$this->closeConnection();
return $result;
}
function execute( $_sql )
{
$this->errorStatus = false;
$this->errorText = "";
unset($resultSet );
mysql_query("SET NAMES ".$this->params->setNames );
$resultSet = mysql_query( $_sql );
if( !$resultSet || mysql_error() )
{
require_once("parameters.php");
$params = new parameters();
if( $params->logError )
{
$this->errorText = mysql_error().$_sql;
$this->errorStatus = true;
}
}
return $resultSet;
}
function protectString( $_string )
{
$safe = $_string;
$safe = str_replace("&", "&", $safe );
$safe = str_replace("'", """, $safe );
$safe = str_replace("\"", "'", $safe );
$safe = str_replace("<", "<", $safe );
$safe = str_replace(">", ">", $safe );
$safe = "'".$safe."'";
return $safe;
}
}
?>
Ara katman olan iş katmanı, sunum ve veri katmanı arasında bulunup bu ikisini birleştiren ve iş kurallarının tamamını barındıran katmandır. Aşağıda da bunun bir örneği belirtilmiştirç
<?php
class business
{
var $params;
var $DB;
function business()
{
require_once("parameters.php");
require_once("mySQL.php");
$this->params = new parameters();
$this->DB = new mySQL();
}
function insertMessage( $message, $from, $timeStamp )
{
$message = $this->DB->protectString( $message );
$from = $this->DB->protectString( $from );
$sql = "insert into annual_himen(mTime, mFrom, mMessage) values ( $timeStamp, $from, $message );" ;
$result = $this->DB->executeScalar( $sql );
if( $this->DB->errorStatus == true ){
$this->logError( $this->DB->errorText );
return false;
}else{
return true;
}
}
function getLastMessages( &$returnString, $lastTimeStamp )
{
$rowDelimiter = $this->params->rowDelimiter;
$cellDelimiter = $this->params->cellDelimiter;
$returnString = "";
$sql = "select mTime, mFrom, mMessage from annual_himen where mTime > $lastTimeStamp;" ;
$this->DB->openConnection();
$resultSet = $this->DB->getResultSet( $sql );
if( $this->DB->errorStatus == true ){
$this->logError( $this->DB->errorText );
}else{
while( $record = $this->DB->getRecord( $resultSet ) ){
$returnString .= date("G:i",$record[0]).$cellDelimiter.$record[1].$cellDelimiter.$record[2].$rowDelimiter;
}
}
$this->DB->closeConnection();
}
function checkActivities( $lastTimeStamp )
{
$count = $this->DB->getSingleQueryResult( "select count(*) from annual_himen where mTime > $lastTimeStamp;" );
if( $this->DB->errorStatus == true ){
$this->logError( $this->DB->errorText );
return 0;
}else{
return $count;
}
}
function logError( $errorMessage )
{
$body = "Sistemde Hata Olustu.<br>Hata içerigi asagidadir:<br><br><b>$errorMessage</b><br>";
$subject = $this->params->mailPrefix."Hata Olustu.";
$this->sendMail( $this->params->mailSender,$this->params->adminMail, $body, $subject, true );
$this->htmlAlert( 'Sistemde bilinmeyen bir hata olustu.Hata Yöneticiye rapor edildi...' );
die();
}
function sendMail( $from, $to, $body, $subject, $isHTML = false, $cc = "", $bcc = "" )
{
ini_set("sendmail_from", $from);
$headers = "";
if( $isHTML == true ){
$headers .= "MIME-Version: 1.0\r\n";
$headers .= "Content-type: text/html; charset=".$params->charSet."\r\n";
}
if( $cc != "" ) {
$headers .= "Cc: $_cc\r\n";
}
if( $bcc != "" ){
$headers .= "Bcc: $_bcc\r\n";
}
$headers .= "From: $_from\r\n";
$headers .= "To: $_to\r\n";
mail ($to, $subject, $body, $headers );
}
function htmlAlert( $message )
{
echo("<html><script>alert('$message')</script></html>");
}
function getLanguage( $lang )
{
require_once("language.php");
if( $lang == "english" ){
$language = new english();
}else if( $lang == "turkish" ){
$language = new turkish();
}else{
$language = new english();
}
return $language;
}
}
?>
Yukarıda bulunan iş katmanına aşağıdaki yardımcı fonksiyonları da ekeyebiliriz.:
<?php
session_start();
if( !isset($HTTP_SESSION_VARS["currentUserFullName"] )) {
header("location:index.php");
exit();
}
$name = $HTTP_SESSION_VARS["currentUserFullName"];
require_once("parameters.php");
require_once("business.php");
header("Content-Type: text/xml; charset=UTF-8");
$params = new parameters();
$worker = new business();
$action = $HTTP_POST_VARS["action"];
if( $action == "getMessages" ){
$_returnString = "";
$time = $HTTP_SESSION_VARS["lastTimeStamp"];
$HTTP_SESSION_VARS["lastTimeStamp"] = time();
if( isset( $HTTP_POST_VARS["duration"] )){
$duration = $HTTP_POST_VARS["duration"];
if( $duration > 24 ){
$time = ( time() - ( 60 * 60 * 24 ) );
}else{
$time = ( time() - ( 60 * 60 * $duration ) );
}
}
$worker->getLastMessages( &$returnString, $time);
$returnString = iconv( $params->charSet, "UTF-8", $returnString );
echo $returnString;
}else if( $action == "sendMessage" ){
if( isset($HTTP_POST_VARS["message"])) {
$message = $HTTP_POST_VARS["message"];
$message = iconv("UTF-8", $params->charSet, $message );
$worker->insertMessage( $message, $name, time() );
}
}else if( $action == "check" ){
$time = $HTTP_SESSION_VARS["lastTimeStamp"];
echo $worker->checkActivities( $time );
}else if( $action == "signout" ){
$message = "Logout HiMen...".$params->cellDelimiter."0".$params->cellDelimiter."0".$params->cellDelimiter."10".$params->cellDelimiter;
echo $worker->insertMessage( $message, $name, time() );
}else if( $action == "changeLang" ) {
if( isset( $HTTP_POST_VARS["lang"] ) ){
$lang = $HTTP_POST_VARS["lang"];
$HTTP_SESSION_VARS["language"] = $lang;
}
}
?>
Son olarak yapılması gereken, bunları kapsayan önyüzler yapmaktır. Önyüzler istenildiği şekilde yapılabilir. JavaScript ve CSS ile desteklenen php sayfaları kullanıcıların doğrudan iletişime gireceği yerler olacaktır.
Yukarıda anlatılan örnek, yakın zamanda gerçekleştirdiğim ve hiMen adını verdiğim projeden alınmadır. Bu projenin tamamına http://himen.sourceforge.net adrsinden erişilebilir.