Skip to content

7. Vaka çalışması: Web üzerinde bir ürün veritabanının yönetimi

Bu vaka çalışmasının kodları |ICI|'te mevcuttur.

Hedefler:

  • ürün veritabanını yönetmek için bir sınıf yazmak
  • Bu sınıfa dayalı bir web uygulaması yazmak
  • stil sayfalarını tanıtmak
  • basit web uygulamaları için bir geliştirme metodolojisi taslağı sunmak
  • istemci tarayıcısında JavaScript'i tanıtmak

Kaynak: Bu vaka çalışmasının temelini, Eyrolles yayınevinden çıkan Jean-Philippe Leboeuf'un "Les cahiers du programmeur - PHP/MySQL" adlı kitabı oluşturmaktadır.

7.1. Giriş

Bir esnaf, mağazasında sattığı ürünleri yönetmek istemektedir. Evinde bu işi yapan bir ACCESS uygulaması zaten mevcuttur, ancak web dünyasına adım atma fikri onu cezbetmektedir. Bir internet servis sağlayıcısında hesabı vardır ve bu sağlayıcı, müşterilerinin kişisel klasörlerine PHP komut dosyalarını yüklemelerine izin vermektedir. Bu, müşterilerin dinamik web siteleri oluşturmasına olanak tanır. Ayrıca, bu müşterilerin MySQL hesapları da vardır; bu hesaplar sayesinde, PHP komut dosyalarına veri sağlayabilecek tablolar oluşturabilirler. Böylece, satıcının kullanıcı adı "adarticles" ve şifresi "mdparticles" olan bir MySQL hesabı bulunmaktadır. Satıcı, üzerinde tüm haklara sahip olduğu "dbarticles" adlı bir veritabanına sahiptir. Dolayısıyla satıcımız, ürün yönetimini web'e taşımak için gerekli tüm unsurlara sahiptir. Web geliştirme konusunda yetkin olan sizin yardımınızla, bu maceraya atılmaktadır.

7.2. Veritabanı

Tüccarımız, istediği web ana sayfasının arayüzü için aşağıdaki taslağı hazırlıyor:

Image

İki tür kullanıcı olacaktır:

  • Ürün tablosunda her şeyi yapabilen (ekleme, değiştirme, silme, görüntüleme vb.) yöneticiler. Bu kullanıcılar, yukarıdaki menüdeki tüm öğeleri kullanabilecekler. Özellikle, [Requête SQL] seçeneği aracılığıyla SQL sorgusunu çalıştırabilecekler.
  • Sınırlı haklara sahip normal kullanıcılar (yöneticiler hariç): ekleme, değiştirme, silme ve görüntüleme hakları. Bu haklardan sadece bazılarına sahip olabilirler; örneğin, yalnızca görüntüleme hakkına sahip olabilirler.

Veritabanında aynı haklara sahip olmayan çeşitli kullanıcı türleri bulunduğundan, kimlik doğrulama gereklidir. Bu nedenle ana sayfa bu işlemle başlar. Kimin kim olduğunu ve kimin ne yapma hakkına sahip olduğunu belirlemek için USERS ve DROITS adlı iki tablo kullanılacaktır. USERS tablosu aşağıdaki yapıya sahip olacaktır:

login
Kullanıcının giriş adı, kullanıcıyı benzersiz bir şekilde tanımlar. Bu alan, tablonun birincil anahtarıdır.
mdp
Kullanıcının şifresi (açık metin olarak)
admin 
Kullanıcı yönetici ise 'y' (yes) karakteri, aksi takdirde 'n' (no) karakteri.

Tablonun içeriği şu şekilde olabilir:

Image

DROITS tablosu, USERS tablosunda bulunan yönetici olmayan kullanıcıların sahip olduğu hakları belirtir. Yapısı şu şekildedir:

login
Kullanıcının, kendisini benzersiz bir şekilde tanımlayan kullanıcı adı.
Bu alan, DROITS tablosunun yabancı anahtarıdır ve
USERS tablosundaki login sütununa işaret eder.
table
Kullanıcının erişim haklarına sahip olduğu tablonun adı.
ajouter
kullanıcının tabloya ekleme hakkı varsa 'y' (yes) karakteri,
aksi takdirde 'n' (hayır) karakteri.
modifier
değiştirme hakkı: 'y' veya 'n'
supprimer
silme hakkı: 'y' veya 'n'
consulter
görüntüleme hakkı: 'y' veya 'n'

Tablonun içeriği şu şekilde olabilir:

Image

Notlar:

  • USERS tablosunda bulunan ancak DROITS tablosunda bulunmayan bir kullanıcı U'nun hiçbir hakkı yoktur.
  • Örneğimizde, kullanıcılar yalnızca tek bir tabloya, yani ARTICLES tablosuna erişebileceklerdir. Ancak, öngörülü olan satıcımız, daha sonra uygulamasına yeni tablolar ekleyebilme imkânı elde etmek amacıyla DROITS tablosunun yapısına "table" alanını eklemiştir.
  • Kendi tablolarımızda hakları neden yönetelim ki, MySQL veritabanının bu hakları kendi tablolarında (ve bizden daha iyi bir şekilde) yönetebileceğini varsayıyoruz? Bunun tek nedeni, satıcımızın MySQL veritabanında kullanıcı oluşturma ve onlara haklar verme yetkisine sahip olmamasıdır. Unutmayalım ki, MySQL veritabanı bir barındırma sağlayıcısında barındırılıyor ve satıcı, (neyse ki) herhangi bir yönetim hakkına sahip olmayan, bu veritabanının sadece bir kullanıcısıdır. Ancak, şu anda admarticles kullanıcı adı ve mdparticles şifresiyle eriştiği dbarticles adlı veritabanı üzerinde tüm haklara sahiptir. Uygulamanın tüm tabloları bu veritabanında yer almaktadır.

ARTICLES tablosu, satıcı tarafından satılan ürünlere ilişkin bilgileri barındırır. Yapısı şu şekildedir:

code
ürün kodu - tablonun birincil anahtarı
- tam olarak 4 karakter
nom
ürün adı
prix
fiyatı
stockActuel
Mevcut stok seviyesi
stockMinimum
bu seviyenin altına düştüğünde,
yeniden stoklama siparişi verilmesi gereken seviye

İlk etapta test amaçlı kullanılacak içeriği şu şekilde olabilir:

Image

7.3. Projenin kısıtlamaları

İşletme sahibi burada yerel bir ACCESS uygulamasını bir web uygulamasına taşıyor. Bu uygulamanın ne hale geleceğini ve nasıl gelişeceğini bilmiyor. Ancak yeni uygulamanın kullanımı kolay ve ölçeklenebilir olmasını istiyor. Bu nedenle, BT danışmanı tablo tasarımı sırasında şu senaryoyu öngörmüştür:

  • farklı yetkilere sahip çeşitli kullanıcılar: bu, işletmecinin belirli görevleri başkalarına devretmesine, ancak onlara yönetici yetkileri vermeden olanak sağlayacaktır
  • gelecekte ARTICLES tablosu dışında başka tablolar da eklenebilir

Aynı danışman başka önerilerde de bulunuyor:

  • Yazılım geliştirmede, sunum katmanları ile işleme katmanlarının net bir şekilde ayrılması gerektiğini biliyor. Bir web uygulamasının mimarisi genellikle şu şekildedir:

Burada kullanıcı arayüzü bir web tarayıcısıdır, ancak bu aynı zamanda ağ üzerinden HTTP isteklerini web hizmetine gönderen ve bu hizmetin gönderdiği sonuçları biçimlendiren bağımsız bir uygulama da olabilir. Uygulama mantığı, kullanıcının isteklerini işleyen komut dosyalarından oluşur; buradaki komut dosyaları PHP'tir. Veri kaynağı genellikle bir veritabanıdır, ancak bir LDAP dizini veya uzak bir web hizmeti de olabilir. Geliştiricinin, bu üç varlık arasında büyük bir bağımsızlık sağlaması önemlidir; böylece bunlardan biri değiştiğinde, diğer ikisinin değişmesi gerekmez veya çok az değişir. Bu durumda, esnafın BT danışmanı şu önerilerde bulunur:

  • Uygulamanın iş mantığını bir PHP sınıfına yerleştireceğiz. Böylece yukarıdaki [Logique applicative] bloğu aşağıdaki öğelerden oluşacaktır:

[Logique Applicative] bloğunda ise

  • uygulamanın giriş kapısı olan [IE=Interface d'Entrée] bloğu. Bu blok, müşteri türü ne olursa olsun aynıdır.
  • uygulamanın mantığı için gerekli sınıfları bir araya getiren [Classes métier] bloğu. Bunlar istemciden bağımsızdır.
  • [IS1 IS2 ... IS=Interface de Sortie] yanıt sayfası oluşturucuları bloğu. Her bir oluşturucu, belirli bir istemci türü için uygulama mantığı tarafından sağlanan sonuçları biçimlendirmekle görevlidir: tarayıcı veya telefon için HTML kodu, bağımsız uygulama için WAP kodu, XML kodu, vb.

Bu model, istemcilere karşı iyi bir bağımsızlık sağlar. İstemci değişse de veya sonuçların sunum şekli değiştirilmek istense de, oluşturulması veya uyarlanması gerekenler [IS] çıktı oluşturucularıdır.

  • Bir web uygulamasında, sunum katmanı ile işleme katmanı arasındaki bağımsızlık, stil sayfalarının kullanılmasıyla artırılabilir. Stil sayfaları, bir tarayıcı içindeki web sayfasının sunumunu yönetir. Bu sunumu değiştirmek için, ilgili stil sayfasını değiştirmek yeterlidir. İşleme mantığına dokunmaya gerek yoktur. Dolayısıyla burada bir stil sayfası kullanılacaktır.
  • Yukarıdaki şemada, veri kaynağıyla arayüz oluşturacak olan iş sınıfıdır. Varsayıma göre, bu kaynak burada bir MySQL veritabanıdır. Başka bir veritabanına geçişi mümkün kılmak için, veritabanlarının gerçek türlerinden bağımsız olarak veritabanlarına erişim sınıfları sunan PEAR kütüphanesi kullanılacaktır. Böylece, eğer işletmecimiz, şirketine bir Microsoft IIS web sunucusu kurabilecek kadar zenginleşirse, iş sınıfında (hiçbir değişiklik yapmadan veya çok az değişiklikle) MySQL veritabanını SQL Server ile değiştirebilir.

7.4. Ürün sınıfı

Ürün sınıfı şu şekilde tanımlanabilir:

<?php

     // Aşağıdaki tablolardan oluşan bir ürün tabanında çalışan ürün sınıfı
     // ürünler: (kod, ad, fiyat, stockActuel, stockMinimum)
     // kullanıcılar: (kullanıcı adı, şifre, yönetici)
     // yetkiler: (kullanıcı adı, tablo, ekle, düzenle, sil, görüntüle)

     // veritabanında tüm işlemleri gerçekleştirebilmek için kullanıcı adı ve şifreyi girmesi gereken sınıf kullanıcısıdır
     // dolayısıyla veritabanı üzerinde zaten tüm yetkilere sahiptir. Bu da burada
     // burada özel bir güvenlik önlemi alınması gerekmediği anlamına gelir

     // kütüphaneler
  require_once 'DB.php';

  class articles{

           // özellikler
    var $sDSN;                        // bağlantı dizesi
          var $sDatabase;            // veritabanı adı
    var $oDB;                        // veritabanına bağlantı
    var $aErreurs;                // hata listesi
    var $oRésultats;            // select sorgusunun sonucu
        var $connecté;                // veritabanına bağlı olup olmadığını gösteren boole değeri
        var $sQuery;                    // son çalıştırılan sorgu
    var $sUser;                    // bağlantıyı kuran kullanıcının kimliği
    var $bAdmin;                    // kullanıcının yönetici olup olmadığını gösterir (doğru ise)
    var $dDroits;                // kullanıcının yetki dizini: table ->> array(görüntüle, ekle, sil, düzenle)

     // oluşturucu
    function articles($dDSN,$sUser,$sMdp){

             // $dDSN: kurulacak bağlantıyı tanımlayan sözlük
       // $dDSN['sgbd']: bağlanılması gereken SGBD'in türü
       // $dDSN['host']: bunu barındıran ana bilgisayarın adı      
       // $dDSN['database'] : bağlanılması gereken veritabanının adı      
       // $dDSN['admin'] : bağlanılması gereken veritabanının sahibinin kullanıcı adı
       // $dDSN['mdpadmin'] : şifresi
       // $sUser : makale veritabanını kullanmak isteyen kullanıcının kullanıcı adı
       // $sMdp: şifresi

       //, $oDB'te, $dDSN tarafından tanımlanan veritabanına $dDSN['admin'] kimliği altında bir bağlantı oluşturur
       // bağlantı başarılı olursa ve $sUser kullanıcısı kimliği doğrulanırsa  
           //, $bAdmin ve $dDroits'e $sUser kullanıcısının yetkilerini yükler
           //, veritabanına bağlantı dizesini $sDSN'e yazıyor
           // bağlanılacak veritabanının adını $sDataBase'e atar
         // $connecté değerini true olarak ayarlar
       // bağlantı başarısız olursa veya $sUser kullanıcısı doğru şekilde tanımlanmazsa
           // uygun hata mesajlarını $aErreurs listesine ekler
         // gerekirse bağlantıyı kapatır
         //, $connecté değerini "yanlış" olarak ayarlar 

  ...
    }//oluşturucu

    // ------------------------------------------------------------------
    function connect(){
             // merkeze (yeniden) bağlanma
...
    }//bağlan

    // ------------------------------------------------------------------
    function disconnect(){
      // veri tabanıyla bağlantı kesiliyor $sDSN
...
    }//bağlantı kesme

    // -------------------------------------------------------------------
    function execute($sQuery,$bAdmin){
            // $sQuery: yürütülecek istek
       // $bAdmin: yönetici olarak yürütme talebi varsa doğru
...
    }//çalıştır

    // --------------------------------------------------------------------------
    function addArticle($dArticle){
         // bir ürün ekler $dArticle (kod, ad, fiyat, stockActuel, stockMinimum) ürün tablosuna ekler
   ...
    }//ekle          

    // ----------------------------------------------------------------------
    function modifyArticle($dArticle){
             // $dArticle adlı ürünü (kod, ad, fiyat, stockActuel, stockMinimum) ürün tablosundaki bir ürünü
...
    }//güncelleme

    // ----------------------------------------------------------------------
    function deleteArticle($sCode){
             // ürün tablosundan bir ürünü siler
       // kod numarası $sCode olan
...
    }//sil

    // ----------------------------------------------------------------------
    function vérifierArticle(&$dArticle){
         // bir ürünün geçerliliğini kontrol eder $dArticle (kod, ad, fiyat, stockActuel, stockMinimum)
...
    }//kontrol et

    // --------------------------------------------------------------------------
    function selectArticles($dQuery){
             // ürün tablosunda bir select sorgusu yürütür
       // bu sorgunun üç bileşeni vardır
       // $dQuery içindeki sütun listesi['colonnes']
       // $dQuery içindeki filtreleme ['where']
       // $dQuery içindeki görüntüleme sırası['orderby']
...
    }//selectArticles            

        // --------------------------------
    function existeArticle($sCode){
         // $sCode kodlu ürün ürün tablosunda mevcutsa TRUE değerini döndürür
...
    }//existeArticle

    // --------------------------------------
    function existeUser($sUser,$sMdp){
             //, $sUser kullanıcı adıyla $sMdp şifresine sahip kullanıcının varlığını kontrol eder
       // (int $iErreur, string $sAdmin, hashtable $dDroits) değerlerini döndürür
       // $iErreur, veritabanı işleminde herhangi bir hata olması durumunda -1 değerini alır; bu durumda $aErreurs listesi doldurulur
       // Kullanıcı bulunamazsa (kullanıcı yok veya şifre yanlış) $iErreur = 1
       // Kullanıcı mevcut ancak yetki tablosunda herhangi bir yetkisi yoksa $iErreur = 2
       // $iErreur = 3, kullanıcı mevcutsa ve yöneticiyse
       // $iErreur = 0, kullanıcı mevcutsa ancak yönetici değilse
       // $sAdmin="y" ise kullanıcı mevcut ve yönetici ise ($iErreur==3), aksi takdirde boş dizgiye eşittir
       // $dDroits, kullanıcı yönetici değilse ($iErreur==0) kullanıcı hakları sözlüğüdür
       // aksi takdirde boş bir dizidir
       // sözlüğün anahtarları, kullanıcının haklara sahip olduğu tablolardır
       // bu tabloya atanan değer ise, anahtarları haklar olan bir sözlüktür
       // (görüntüleme, ekleme, değiştirme, silme) ve değerler duruma göre 'y' (evet) veya 'n' (hayır) dizeleridir
...
    }//existeUser

    // --------------------------------------
    function getCodes(){
         // kod tablosunu oluşturur
....
    }//getCodes    

  }//sınıflandırır
?>      

Yorumlar

  • "articles" sınıfı, veritabanına erişim için PEAR::DB kütüphanesini kullanır; bu nedenle şu komut verilir:
require_once 'DB.php';

Bu dahil etme işlemi, DB.php komut dosyasının, PHP yapılandırma dosyasındaki include_path seçeneğinin dizinlerinden birinde bulunmasını gerektirir.

  • Uygulama, hangi veritabanına hangi kimlikle bağlanılacağını bilmelidir. Bu bilgiler, $dDSN sözlüğünde verilir. Başlangıçtaki varsayımın, veritabanının adının dbarticles olduğu ve mdparticles şifresine sahip admarticles adlı bir kullanıcıya ait olduğu olduğunu hatırlayalım. Ayrıca, bu uygulamanın farklı haklara sahip birden fazla kullanıcıya izin verdiğini de hatırlayalım. Burada giderilmesi gereken bir belirsizlik var. Bağlantı, admarticles kimliği altında açılıyor ve sonuç olarakdbarticles veritabanındaki tüm işlemler bu kimlik altında gerçekleştirilecektir; zira SGBD MySQL, dbarticles veritabanını yönetmek için yeterli haklara sahip tek kullanıcıdır. Farklı kullanıcıların varlığını “simüle etmek” için, admarticles kullanıcısını, kullanıcı adı ($sUser) ve şifresi ($sMdp) parametre olarak oluşturucuya aktarılan kullanıcının haklarıyla çalıştıracağız. Böylece, ürün veritabanında bir işlem yapmadan önce, kullanıcının ($sUser, $sMdp) bu işlemi gerçekleştirme yetkisine sahip olup olmadığı kontrol edilecektir. Eğer öyleyse, bu işlemi onun adına admarticles kullanıcısı gerçekleştirecektir.
  • Ürün veritabanı yöneticisinin kullanıcı adı ve şifresi, oluşturucuya aktarılmalıdır. Bu, alınabilecek makul bir önlemdir. Bu iki bilgi sınıf koduna "sabit" olarak yazılırsa, sınıfın herhangi bir kullanıcısı kolayca ürün veritabanı yöneticisi gibi davranabilir. Nitekim, PHP sınıfı korunmamaktadır. Ayrıca, üzerinde çalışılan kullanıcının ($sUser, $sMdp) yönetici olup olmadığını belirten sınıfın $bAdmin özniteliği, aşağıdaki örnekte olduğu gibi dışarıdan doğrudan ayarlanabilir:
$oArticles=new articles($dDSN,$sUser,$sMdp)
// burada $sUser, veritabanının yönetici olmayan bir kullanıcısı olarak tanındı
$oArticle->bAdmin=TRUE;
// şimdi $sUser yönetici oldu

PHP, JAVA veya C# değildir ve PHP sınıfı, bir sözlükten biraz daha gelişmiş bir veri yapısıdır, ancak bAdmin özniteliğinin özel veya korumalı olarak tanımlandığı ve dışarıdan değiştirilmesini imkansız kılan gerçek bir sınıfın güvenliğini sunmaz. Sınıfı kullanan kişinin, ürün veritabanı yöneticisinin kullanıcı adı ve şifresini bilmesi gerektiğinden, bu sınıfı yalnızca yönetici kullanabilir. Dolayısıyla, önceki işlem onun için artık hiçbir önemi kalmamıştır. Sınıf, yalnızca ona geliştirme kolaylığı sağlamak amacıyla mevcuttur. Bunun önemli bir sonucu, herhangi bir güvenlik önlemi alınmasına gerek olmamasıdır. Tekrar belirtmek gerekirse, articles sınıfını kullanan kişi, zorunlu olarak ürün veritabanının yöneticisidir.

  • Sınıf, veritabanına bağlantı hatalarını veya diğer tüm hataları, $aErreurs özniteliğini bir veya daha fazla hata mesajıyla doldurarak tek bir şekilde yönetir. Bu nedenle, her işlemden sonra sınıf kullanıcısı bu listeyi kontrol etmelidir.
  • addArticle, updateArticle, deleteArticle, selectArticles ve execute yöntemleri, daha önce sunulan web arayüzü şemasından doğrudan türetilmiştir. Bu yöntemler, aslında sunulan menüdeki seçeneklere karşılık gelir. addArticle ve modifyArticle yöntemleri, eklenecek veya değiştirilecek maddenin verilerinin doğru olup olmadığını kontrol etmek için vérifierArticle yöntemini temel alır. Aynı mantıkla, existeArticle yöntemi, halihazırda mevcut olan bir ürünün eklenmek üzere olup olmadığını kontrol etmemizi sağlar. Kodun birincil anahtar olduğu bir makale tablosu kullanılıyorsa, bu yönteme gerek kalmayabilir. Bu durumda, SGBD yöntemi, yinelenen kayıt nedeniyle ekleme işleminin başarısız olduğunu kendisi bildirecektir. Muhtemelen okunması zor ve İngilizce bir hata mesajıyla bunu bildirecektir.
  • Değiştirilecek veya silinecek bir ürün, benzersiz olan kodu ile tanımlanır. getCodes yöntemi, tüm bu kodları elde etmeyi sağlar.
  • disconnect yöntemi, nesnenin oluşturulması sırasında açılan veritabanı bağlantısını kapatır. Burada, veritabanıyla yeni bir bağlantı kuracak olan connect yönteminin ne işe yaradığı pek anlaşılmamaktadır. Bu, aynı nesneyle bu bağlantıyı istediğiniz zaman açıp kapatmanıza olanak tanır. Bunun yararı ancak web uygulamasıyla birlikte ortaya çıkar. Web uygulaması, bir articles nesnesi oluşturur ve bunu bir oturumda saklar. Olay, ardışık istemci-sunucu iletişimleri boyunca nesnenin çoğu özniteliğini koruyabilse de, açık bağlantıyı temsil eden özniteliği saklayamaz. Bu nedenle, her yeni istemci-sunucu iletişiminde bağlantı yeniden açılmalıdır. Açık bağlantının bir bağlantı havuzunda depolanması ve kalıcı olarak açık kalması için kalıcı bir bağlantı talep edilecektir. Böylece, komut dosyası yeni bir bağlantı talep ettiğinde, bu bağlantı bağlantı havuzundan alınacaktır. Böylece, oturumun açık bağlantıyı saklayabilmiş olması durumunda elde edilecek sonuçla aynı sonuca ulaşılır.
  • existeUser yöntemi, üreticinin $sMdp şifresiyle tanımlanan $sUser kullanıcısının gerçekten var olup olmadığını öğrenmesini sağlar. Eğer varsa, bu yöntem kullanıcının yönetici olup olmadığını (USERS tablosunda belirtildiği üzere) belirler ve bu bilgiyi $bAdmin özniteliğinde saklar. Yönetici değilse, yöntem DROITS tablosundan kullanıcı haklarını alır ve bunları çift indeksli bir sözlük olan $dDroits özniteliğine yerleştirir: $dDroits[$table][$droit] değeri 'y' değerine sahip olur; aksi takdirde 'n' değerine sahip olur.

articles sınıfını yazın. Veritabanına erişim, veritabanının tam türünden bağımsız çalışmayı sağlayan PEAR::DB kütüphanesi kullanılarak yapılacaktır.

7.5. WEB uygulamasının yapısı

Artık ürün veritabanını yönetmek için "iş" sınıfına sahip olduğumuza göre, bunu farklı ortamlarda kullanabiliriz. Burada, bunu bir web uygulamasında kullanılması önerilmektedir. Aşağıdaki farklı sayfalar aracılığıyla bunu inceleyelim:

7.5.1. Uygulamanın ana sayfası

Daha önce tanıtılan ana sayfaya geri dönelim:

1234

Image

Uygulamanın tüm sayfaları, yukarıdaki yapıya sahip olacaktır; bu yapı, dört alanı içeren iki satırlı ve üç sütunlu bir tablo şeklindedir:

  • 1. alan, tablonun ilk satırını oluşturur. Bu alan, başlığa ve isteğe bağlı olarak bir resme ayrılmıştır. Satırdaki üç sütun burada birleştirilmiştir.
  • İkinci satırda, her sütun için birer alan olmak üzere toplam üç alan bulunur:
    • 2. alan menü seçeneklerini içerir. Bu alan da tek sütunlu ve birkaç satırlı bir tablo içerir. Menü seçenekleri tablonun satırlarına yerleştirilir.
    • 3. alan boştur ve yalnızca 2. ve 4. alanları ayırmak için kullanılır. Bu ayrımı sağlamak için farklı bir yöntem de kullanılabilirdi.
    • 4. bölge, sayfanın dinamik kısmını içeren bölgedir. Eylemden eyleme değişen kısım budur; diğerleri aynı kalır.

Bu sayfa şablonunu oluşturan PHP komut dosyası, main.php olarak adlandırılacak ve şu şekilde olabilir:


<html>
  <head>
      <title>Gestion d'articles</title>
      <link type="text/css" href="<?php echo $dConfig['urlPageStyle'] ?>" rel="stylesheet" />
   </head>
  <body background="<?php echo $dConfig['urlBackGround'] ?>">
    <table>
      <tr height="60">
        <td colspan="3" align="left" valign="top" >
          <h1><?php echo $main["title"] ?></h1>
        </td>
      </tr>
      <tr>
        <td>
          <table>
            <tr>
              <td class="menutitle" >
                                    <a href="<?php echo  $main["liens"]["login"] ?>" ?>Authentification</a>
              </td>
            </tr>
            <tr>
                <td><br /></td>
            </tr>
            <tr>
              <td class="menutitle" >
                Utilisation
              </td>
            </tr>
            <tr height="10"></tr>
            <tr>
              <td class="menublock" >
                <img alt="-" src="../images/radio.gif" />
                <a href="<?php echo  $main["liens"]["addArticle"] ?>" ?>
                  Ajouter un article
                </a>
                 </td>
            </tr>
            <tr>
              <td class="menublock" >                    
                <img alt="-" src="../images/radio.gif" />
                <a href="<?php echo $main["liens"]["updateArticle"] ?>">
                  Modifier un article
                </a>
                    </td>
            </tr>
              <td class="menublock" >                
                <img alt="-" src="../images/radio.gif" />
                <a href="<?php echo $main["liens"]["deleteArticle"] ?>">
                  Supprimer un article
                </a>
              </td>
            </tr>
            <tr>
              <td class="menublock" >
                <img alt="-" src="../images/radio.gif" />
                <a href="<?php echo $main["liens"]["selectArticle"] ?>">
                  Lister des articles
                </a>
              </td>
            </tr>
            <tr>
                <td><br /></td>
            </tr>                
            <tr>
              <td class="menutitle" >
                Administration
              </td>
            </tr>
            <tr height="10"></tr>
            <tr>
              <td class="menublock" >                
                <img alt="-" src="../images/radio.gif" />
                <a href="<?php echo $main["liens"]["sql"] ?>" >
                  Requête SQL
                </a>
              </td>
            </tr>
          </table>
        </td>
            <td>  
            <img alt="/" src="../images/pix.gif" width="10" height="1" />
            </td>
            <td>
            <fieldset>
              <legend><?php echo $main["légende"] ?></legend>
            <?php
                include $main["contenu"];
            ?>
          </fieldset>
        </td>
      </tr>
    </table>
  </body>
</html>

Sayfanın parametreli alanları yukarıdaki listede vurgulanmıştır. Şablon sayfa çeşitli şekillerde parametrelenmiştir:

  • aşağıdaki anahtarları içeren $main sözlüğü aracılığıyla:
    • title: sayfanın 1. alanına eklenecek başlık
    • liens: menü sütununda oluşturulacak bağlantıların sözlüğü. Bu bağlantılar, 2. bölgedeki menü seçenekleriyle ilişkilidir
    • içerik: 4. alana görüntülenecek sayfanın URL'si
  • $dConfig sözlüğü aracılığıyla, config.php adlı uygulama yapılandırma dosyasından alınan bilgileri bir araya getirir
  • sayfanın kullandığı stil sayfasının bir parçası olan sınıflar aracılığıyla:
      <link type="text/css" href="<?php echo $dConfig['urlPageStyle'] ?>" rel="stylesheet" />

Sayfa burada aşağıdaki stil sınıflarını kullanır:

  • menutitle: menünün ana seçenekleri için
  • menublock: menünün ikincil bir seçeneği için

Parametrelerden birini değiştirmek, sayfanın görünümünü değiştirir. Dolayısıyla, $main['title'] değerini değiştirmek, alan 1'in başlığını değiştirecektir.

7.5.2. Bir müşteri talebinin tipik işleyişi

Müşteri, şablon sayfanın 2. bölgesindeki bağlantılar aracılığıyla uygulama ile etkileşime girer. Bu bağlantılar aşağıdaki türde olacaktır:

apparticles.php?action=xx&phase=y&PHPSESSID=zzzzzzzzzzzz
action
aşağıdakilerden birini ifade eder:
authentifier
müşterinin kimlik doğrulaması
selectArticles
ürün seçimi (görüntüleme)
updateArticle
ürün düzenleme
deleteArticle
ürün silme
sql
herhangi bir SQL isteğinin gönderilmesi (yönetici)
phase
bir işlem birkaç adımda gerçekleştirilebilir - mevcut adımı belirtir
PHPSESSID
oturumun başladığı anda oluşturulan oturum jetonu - sunucunun önceki iletişimler sırasında oturumda depolanan bilgileri almasını sağlar

Aynı şekilde, formlardaki action özniteliği de aynı biçime sahip olacaktır. Örneğin, ana sayfada 4 numaralı alanda bir oturum açma formu bulunmaktadır. Bu formun HTML etiketi şu şekilde tanımlanmıştır:

<form name="frmLogin" method="post" action="apparticles.php?action=authentifier&phase=1">

İstemcinin isteğinin işlenmesi, apparticles.php adlı uygulamanın ana betiği tarafından gerçekleştirilir. Bu betiğin görevi, istemciye verilecek yanıtı oluşturmaktır. Her zaman aynı şekilde işlem yapar:

  • Eylemin adı ve mevcut aşama sayesinde, isteği uzman bir işlevine yönlendirecektir. Bu işlev, isteği işleyecek ve uygun yanıt sayfasını oluşturacaktır. Müşterinin her isteği için birden fazla olası yanıt sayfası olabilir: sayfa1, sayfa2, ..., sayfa n. Bu sayfalar, işlev tarafından hesaplanması gereken bilgileri içerir. Dolayısıyla bunlar parametreli sayfalardır. Bu sayfalar, page1.php, page2.php, ..., pagen.php komut dosyaları tarafından oluşturulacaktır.
  • Tutarlılık amacıyla, şablon sayfanın 4. bölgesinde görüntülenecek sayfaların değişken kısımları da $main sözlüğüne yerleştirilecektir.

Bir isteğe yanıt olarak sunucunun pagex.php sayfasını istemciye göndermesi gerektiğini varsayalım. Sunucu şu şekilde hareket edecektir:

  • $main sözlüğüne, pagex.php sayfası için gerekli değerleri yerleştirecektir
  • $main'e, şablon sayfanın 4. bölgesinde görüntülenecek sayfanın URL'ini belirten ['contenu']'i yerleştirecek, pagex.php'in URL'i
  • şu komutla şablon sayfanın görüntülenmesini isteyecektir:
include "main.php";

Şablon sayfası, 4. alanda pagex.php komut koduyla görüntülenecek ve bu kod, 4. alanın içeriğini oluşturmak üzere değerlendirilecektir. Unutulmamalıdır ki bu alan, bir tablonun basit bir hücresidir. Bu nedenle, pagex.php tarafından oluşturulan HTML kodunun <HTML>, <HEAD>, <BODY>, ... ile başlamamalıdır. Bu etiketler, şablon sayfasının başında zaten oluşturulmuştur. Örneğin, ana sayfanın 4. alanını oluşturan login.php komut dosyası şöyle görünebilir:


<form name="frmLogin" method="post" action="<?php echo $main["post"] ?>">
    <table>
        <tr>
            <td>login</td>
            <td><input type="text" value="<?php echo $main["login"] ?>" name="txtLogin" class="text"></td>
        </tr>
        <tr>
            <td>mot de passe</td>
            <td><input type="password" value="" name="txtMdp" class="text"></td>
      <td><input type="submit" value="Connexion" class="submit"></td>      
        </tr>
    </table>
</form>

Görüldüğü gibi sayfa:

  • bir forma indirgenmiş olduğu
  • hem $main sözlüğü hem de stil sayfası tarafından yapılandırılmıştır.

7.5.3. Yapılandırma dosyası

Örneğin bir komut dosyasının veya resmin yolunu değiştirmeye karar verdiğimiz için kodla uğraşmak zorunda kalmamak amacıyla, uygulamaları mümkün olduğunca ayarlamak her zaman yararlıdır. Bu nedenle, ana uygulama apparticles.php, başlatıldığında config.php yapılandırma dosyasını yükleyecektir:

     // yapılandırma dosyası yükleniyor
  include "config.php";

Bu dosyaya, PHP için yapılandırma yönergeleri ve küresel değişkenlerin başlatma değerleri eklenecektir:

<?php

     // PHP yapılandırması
  ini_set("register_globals","off");
  ini_set("display_errors","off");
  ini_set("expose_php","off");
    ini_set("session.use_cookies","0");    // çerez yok

     // ürün veritabanı yapılandırması
    $dConfig["DSN"]=array(
        "sgbd"=>"mysql",
        "admin"=>"admarticles",
        "mdpadmin"=>"mdparticles",
        "host"=>"localhost",
        "database"=>"dbarticles"
    );

   // sayfa URL'leri
    $dConfig['urlBackGround']="../images/standard.jpg";  
  $dConfig["urlPageStyle"]="mystyle.css";  
  $dConfig["urlAppArticles"]="apparticles.php";
  $dConfig["urlPageMain"]="main.php";
  $dConfig["urlPageLogin"]="login.php";
  $dConfig["urlPageErreurs"]="erreurs.php";
  $dConfig["urlPageInfos"]="infos.php";
  $dConfig["urlPageAddArticle"]="addarticle.php";
  $dConfig["urlPageUpdateArticle1"]="updatearticle1.php";
  $dConfig["urlPageUpdateArticle2"]="updatearticle2.php";
  $dConfig["urlPageDeleteArticle1"]="deletearticle1.php";
  $dConfig["urlPageDeleteArticle2"]="deletearticle2.php";
  $dConfig["urlPageSelectArticle1"]="selectarticle1.php";
  $dConfig["urlPageSelectArticle2"]="selectarticle2.php";
  $dConfig["urlPageSQL1"]="sql1.php";
  $dConfig["urlPageSQL2"]="sql2.php";
  $dConfig["urlPageSQL3"]="sql3.php";

   // ana sayfa bağlantıları
  $main["liens"]["login"]="$sUrlAppArticles?action=authentifier&phase=0";  
  $main["liens"]["addArticle"]="$sUrlAppArticles?action=addArticle&phase=0";
  $main["liens"]["updateArticle"]="$sUrlAppArticles?action=updateArticle&phase=0";
  $main["liens"]["deleteArticle"]="$sUrlAppArticles?action=deleteArticle&phase=0";
  $main["liens"]["selectArticle"]="$sUrlAppArticles?action=selectArticle&phase=0";
  $main["liens"]["sql"]="$sUrlAppArticles?action=sql&phase=0";

   // $main yapılandırmada kaydedilir
  $dConfig["main"]=$main;    
?>

7.5.4. Şablon sayfaya ait stil sayfası

Sunucunun yanıtının main.php biçiminde tek bir formatı olduğunu gördük. Bu komut dosyasının, sunum efektlerinden yoksun ham bir sayfa ürettiği fark edilebilir. Bu, birkaç nedenden dolayı iyi bir şeydir:

  • Geliştirici, oluşturduğu sayfanın görsel sunumuyla ilgilenmek zorunda kalmaz. Zaten çekici görsel sayfalar oluşturmak için gerekli becerilere sahip olmayabilir. Böylece tamamen koda odaklanabilir.
  • komut dosyalarının bakımı kolaylaşır. Eğer bu komut dosyaları sunum öznitelikleri içeriyor olsaydı, ne kodun yapısı ne de sunumun yapısı net bir şekilde ortaya çıkardı. Sayfaların görsel görünümü genellikle bir grafik tasarımcıya devredilir. Grafik tasarımcı, muhtemelen anlamadığı bir komut dosyasında, değiştirmesi gereken sunum özniteliklerinin nerede olduğunu aramak zorunda kalmaktan hoşlanmazdı.

Yine de sayfaların görsel görünümüne özen göstermek gerekir. Nitekim, internet kullanıcılarını bir siteye çeken şey budur. Burada, sunum bir stil sayfasına devredilmiştir. main.php sayfası, kodunda görüntülenmesi için kullanılacak stil sayfasını belirtir:

  <head>
      <title>Gestion d'articles</title>
      <link type="text/css" href="<?php echo $dConfig['urlPageStyle'] ?>" rel="stylesheet" />
   </head>

Bu belgede kullanılan stil sayfası şudur:

BODY {
    background : url(../images/standard.jpg);
    border : 2px none #FFDAB9;
    font-family : Garamond;
    font-size : 16px;
    margin-left : 0px;
    padding-left : 20px;
}

INPUT {
    background : #EEE8AA;
    border : 1px solid #EE82EE;
    font-family : Garamond;
    font-size : 18px;
}

INPUT.submit{
    font-family : "Times New Roman";
    font-size : 16px;
    background : #FA8072;
    border : 2px double Green;
    font-weight : bold;
    text-align : center;
    vertical-align : middle;
    cursor : pointer;
}

TD.menutitle{
    background-image : url(../images/menugelgd.gif);
    height : 23px;
    text-align : center;
    vertical-align : middle;
    background : url(../images/menugelgd.gif) no-repeat center;
}

TD.menublock{
    background : url(../images/bandegrismenugd.gif) repeat-x;
    text-align : left;
    vertical-align : middle;
}

A {
    font-family : "Comic Sans MS";
    color : #FF7F50;
    font-size : 15px;
    text-decoration : none;
}

A:HOVER {
    background : #FFA07A;
    color : Red;
}

FIELDSET {
    border : 1px solid #A0522D;
    background : #FFE4C4;
    margin : 10px 10px 10px 10px;
    padding-left : 10px;
    padding-right : 10px;
    padding-bottom : 10px;
}

LEGEND{
    background : #FFA500;
}

TH {
    background : #228B22;
    text-align : center;
    vertical-align : middle;
}

TD.libellé{
    border : 1px solid #008B8B;
    color : #339966;
}

H1 {
    font : bold 20px/30px Garamond;
    color : #FF7F50;
    background : #D1E1F8;
    background-attachment : fixed;
    text-align : center;
    vertical-align : middle;
    font-family : Garamond;
}

SELECT.TEXT {
    background : #6495ED;
    text-align : center;
    color : Aqua;
}

Bu stil sayfasının ayrıntılarına girmeyeceğiz. Olduğu gibi kabul edeceğiz. Biraz ileride nasıl oluşturulacağını ve değiştirileceğini göreceğiz. Bunun için yazılımlar mevcuttur. Yine de, sayfada kullanılan sunum özniteliklerinin rolünü belirtelim:

Özellik:
HTML etiketinin sunumunu belirler:
BODY
<BODY>
H1
<H1> (Başlık1)
A
<A> (Bağlantı)
A:HOVER
kullanıcı fareyi bağlantı üzerine getirdiğinde bağlantının görünüm özelliklerini belirler
FIELDSET
<FIELDSET> - bu etiket tüm tarayıcılar tarafından tanınmaz
LEGEND
<LEGEND> - bu etiket tüm tarayıcılar tarafından tanınmaz
INPUT
<INPUT>
INPUT.TEXT
<INPUT class="TEXT">
INPUT.SUBMIT
<INPUT class="SUBMIT">
TH
<TH> (Tablo Başlığı)
TD.menutitle
<TD class="menutitle"> (Tablo Verileri)
TD.menublock
<TD class="menublock">
TD.libellé
<TD class="libellé">

Bir örnek üzerinden bu sunum kurallarının nasıl yazılabileceğini görelim. Bu örnekte, TopStyle http://www.bradsoft.com adresinden ücretsiz olarak indirilebilen URL Lite yazılımını kullanacağız. Stil sayfası yüklendikten sonra, üç bölmeden oluşan bir pencere karşımıza çıkar:

  1. bir metin düzenleme bölümü. Biçimlendirme öznitelikleri, CSS (Cascading Style Sheets) adlı bir standarda uyan stil sayfalarının yazım kurallarını biliyorsanız elle tanımlanabilir.
  2. 2. alan, oluşturulmakta olan özniteliğin düzenlenebilir özelliklerini gösterir. Bu, en basit yöntemdir. Çok sayıda olan biçimlendirme özniteliklerinin tam adlarını bilmek zorunda kalmamızı önler
  3. 3. alan, oluşturulmakta olan özniteliğin görsel görünümünü gösterir

Yukarıdaki 1. alana, INPUT.submit özniteliğini kopyalayıp INPUT.fantaisie özniteliğine yapıştıralım. Bu öznitelik, HTML etiketinin görünümünü belirleyecektir <INPUT class="fantaisie">

  1. alanı kullanarak INPUT.fantaisie özniteliğinin bazı özelliklerini değiştirelim:

Artık, önceki stil sayfasıyla ilişkili bir HTML sayfasında bulunan tüm <INPUT ... class="fantaisie"> etiketleri, yukarıdaki 3. bölgedeki örnek gibi görüntülenecektir.

Stil sayfalarının faydası çok büyüktür. Bunların kullanımı, bir web uygulamasının "görünümünü", uygulamayı tek bir noktadan, yani stil sayfasından değiştirerek değiştirebilmeyi sağlar. Stil sayfaları eski tarayıcılar tarafından tanınmaz. Aşağıdaki <link ..> yönergesi bazı tarayıcılar tarafından yok sayılacaktır:

  <head>
      <title>Gestion d'articles</title>
      <link type="text/css" href="<?php echo $dConfig['urlPageStyle'] ?>" rel="stylesheet" />
   </head>

Uygulamamızda bu, aşağıdaki ana sayfayı verecektir:

Image

Burada, grafik öğeleri içermeyen minimal bir sayfa var. Daha kötüsü de olabilir. Bazı tarayıcı sürümleri stil sayfalarını tanır ancak bunları yanlış yorumlar. Bu durumda, bozuk ve kullanılamaz bir sayfa ile karşılaşabiliriz. Dolayısıyla, istemci tarayıcısının türü sorusu gündeme gelir. İstemci tarayıcısının türünü belirlemeye yardımcı olan teknikler mevcuttur. Ancak bunlar tamamen güvenilir değildir. Bu durumda, farklı tarayıcılar için farklı stil sayfaları yazabilir, hatta stil sayfalarını yok sayan tarayıcılar için stil sayfası içermeyen bir sürüm yazabiliriz. Bu elbette geliştirme işini zorlaştırır. Bu önemli sorun burada göz ardı edilmiştir.

Stil sayfaları sayesinde, uygulamamızın kullanıcılarına kişiselleştirilmiş bir ortam sunmayı düşünebiliriz. Onlara, çeşitli sunum stilleri içeren bir sayfa gösterebiliriz. Kullanıcılar kendilerine en uygun olanı seçebilirler. Bu seçim bir veritabanına kaydedilebilir. Kullanıcı tekrar oturum açtığında, uygulamayı tercih ettiği stil sayfasıyla başlatabiliriz.

7.5.5. Uygulamanın giriş modülü

Müşteriler, uygulamadan yalnızca giriş modülünü göreceklerdir: apparticles.php. Modülün genel işleyişi şu şekildedir:

  • Müşterinin isteği alınır ve analiz edilir. Bu istek, parametrelerle donatılmış olabilir veya olmayabilir. İstek parametreli olduğunda, beklenen parametreler şunlardır: action=[action]&phase=[phase]&PHPSESSID=[PHPSESSID]
  • İstek parametre içermezse veya alınan parametreler beklenenlerle uyuşmazsa, sunucu yanıt olarak kimlik doğrulama sayfasını (kullanıcı adı, şifre) gönderir. Kullanıcı doğru şekilde kimliğini doğruladığında bir oturum oluşturulur. Bu oturum, istemci-sunucu arasındaki tüm iletişim boyunca bilgileri depolamak için kullanılır.
  • Bir istek doğru şekilde tanınırsa, hem eyleme hem de o anda devam eden aşamaya bağlı bir modül tarafından işlenir.
  • Veritabanına tüm erişimler, articles.php iş sınıfı aracılığıyla gerçekleştirilir.
  • Bir isteğin işlenmesi, her zaman main.php sayfasının müşteriye gönderilmesiyle sona erer; bu sayfada, $main['contenu'] içindeURL sayfası, şablon sayfanın 4. alanına yerleştirilmek üzere belirtilmiştir.

apparticles.php komut dosyasının ana yapısı şu şekilde olabilir:

<?php
     // ürün tablosunun yönetimi
  include "config.php";
  include "articles.php";  

  // yapılması gereken işlem
  $sAction=$_POST["action"] ? $_POST["action"] : $_GET["action"] ? $_GET["action"] : "authentifier";
  $sAction=strtolower($sAction);
   // olası aşama
  $sPhase=$_POST["phase"] ? $_POST["phase"] : $_GET["phase"] ? $_GET["phase"] : "0";

  // oturum
  session_start();
  $dSession=$_SESSION["session"];

     // devam eden bir oturum var mı?
  if(! isset($dSession)){
      // kullanıcı kimlik doğrulaması
    if($sAction=="authentifier" && $sPhase=="0") authentifier_0($dConfig);
    if($sAction=="authentifier" && $sPhase=="1") authentifier_1($dConfig);
    if($sAction=="authentifier" && $sPhase=="2") authentifier_2($dConfig);
     // hatalı istek
    authentifier_0($dConfig);        
  }//if - oturum yok

     // oturum alınmaktadır
  $dSession=unserialize($dSession);

     // isteğin işlenmesi
     // ----- kimlik doğrulama
  if($sAction=="authentifier" && $sPhase=="0") authentifier_0($dConfig);
  if($sAction=="authentifier" && $sPhase=="1") authentifier_1($dConfig);
  if($sAction=="authentifier" && $sPhase=="2") authentifier_2($dConfig);  
     // ----- ürün ekleniyor
  if($sAction=="addarticle" && $sPhase=="0") addArticle_0($dConfig,$dSession);
  if($sAction=="addarticle" && $sPhase=="1") addArticle_1($dConfig,$dSession);
  if($sAction=="addarticle" && $sPhase=="2") addArticle_2($dConfig,$dSession);
     // ----- ürün güncelleme
  if($sAction=="updatearticle" && $sPhase=="0") updateArticle_0($dConfig,$dSession);
  if($sAction=="updatearticle" && $sPhase=="1") updateArticle_1($dConfig,$dSession);
  if($sAction=="updatearticle" && $sPhase=="2") updateArticle_2($dConfig,$dSession);
  if($sAction=="updatearticle" && $sPhase=="3") updateArticle_3($dConfig,$dSession);
     // ----- ürün silme
  if($sAction=="deletearticle" && $sPhase=="0") deleteArticle_0($dConfig,$dSession);
  if($sAction=="deletearticle" && $sPhase=="1") deleteArticle_1($dConfig,$dSession);
  if($sAction=="deletearticle" && $sPhase=="2") deleteArticle_2($dConfig,$dSession);
    // ----- ürünlerin görüntülenmesi
  if($sAction=="selectarticle" && $sPhase=="0") selectArticle_0($dConfig,$dSession);
  if($sAction=="selectarticle" && $sPhase=="1") selectArticle_1($dConfig,$dSession);
  if($sAction=="selectarticle" && $sPhase=="2") selectArticle_2($dConfig,$dSession);
     // ----- istek gönderme SQL
  if($sAction=="sql" && $sPhase=="0") sql_0($dConfig,$dSession);
  if($sAction=="sql" && $sPhase=="1") sql_1($dConfig,$dSession);
  if($sAction=="sql" && $sPhase=="2") sql_2($dConfig,$dSession);


    // hata oluştu - kimlik doğrulama sayfası görüntüleniyor
  session_destroy();
  authentifier_0($dConfig,"0");
...
?>

Aşağıdaki noktalara dikkat edilmelidir:

  • Müşterinin özel bir talebini işleyen fonksiyonlar, yanıt sayfasının oluşturulmasıyla ve apparticles.php betiğinin yürütülmesini sonlandıran bir exit komutuyla sona erer. Başka bir deyişle, bu fonksiyonlardan "geri dönülmez".
  • İşlevler bir veya iki parametre kabul eder:
    • $dConfig, config.php yapılandırma dosyasından alınan bilgileri içeren bir sözlüktür. Tüm fonksiyonlar bu sözlüğü kullanır.
    • $dSession, oturum bilgilerini içeren bir sözlüktür. Bu sözlük, yalnızca oturum oluşturulduğunda, yani kullanıcının kimlik doğrulaması başarıyla tamamlandıktan sonra mevcuttur. Bu nedenle, kimlik doğrulama işlevlerinde bu parametre bulunmaz.

7.5.6. Hata sayfası

Her yazılım uygulaması, ortaya çıkabilecek hataları doğru bir şekilde yönetebilmelidir. Web uygulamaları da bu kuralın dışında değildir. Burada, bir hata oluştuğunda, şablon sayfanın 4. bölgesine aşağıdaki erreurs.php sayfasını yerleştireceğiz:

Les erreurs suivantes se sont produites :
<ul>
    <?php
        for($i=0;$i<count($main["erreurs"]);$i++){
            echo "<li>".$main["erreurs"][$i]."</li>\n";
        }//for
    ?>
</ul>
<a href="<?php echo $main["href"] ?>"><?php echo $main["lien"] ?></a>

Bu sayfa, $main['erreurs']'te tanımlanan hata listesini gösterir. Ayrıca, genellikle hata sayfasından önceki sayfaya yönlendiren bir geri dönüş bağlantısı da sunabilir. Bu bağlantı, $main['lien'] ve URL $main['href'] etiketleriyle tanımlanacaktır. Bu bağlantının görünmemesi için $main['lien'] alanına boş bir dize girmeniz yeterlidir. Aşağıda, kullanıcının yanlış oturum açması durumunda görülen bir hata sayfası örneği yer almaktadır:

Image

7.5.7. Bilgi sayfası

Bazen kullanıcıya, örneğin oturum açma işleminin başarılı olduğu gibi basit bir bilgi vermek isteyebiliriz. Bunun için aşağıdaki infos.php sayfasını kullanacağız:

<?php echo $main["infos"] ?>

Bir müşterinin talebine yanıt olarak bir bilgi görüntülemek için,

  • bilgiyi $main['infos']
  • infos.php'teki URL'i $main['contenu']'e aktaracağız

İşte örneğin, kullanıcı doğru bir şekilde oturum açtığında döndürülen bilgiler:

Image

7.6. Uygulamanın işleyişi

Artık yazacağımız uygulamanın genel yapısı hakkında iyi bir fikrimiz var. Geriye, kullanıcının uygulama içindeki akışını, gerçekleştirebileceği eylemleri ve sunucudan aldığı yanıtları sunmak kalıyor. Bunu yaptıktan sonra, bir istemcinin farklı isteklerini işleyen fonksiyonları yazabileceğiz. Aşağıda, kullanıcının bu eylemlerinden bazılarına yanıt olarak kendisine sunulan sayfalar aracılığıyla uygulamanın işleyişini anlatacağız. Her seferinde aşağıdaki noktaları belirteceğiz:

action utilisateur
Görüntülenen yanıtı tetikleyen kullanıcının ilk eylemi
paramètres envoyés
kullanıcının manuel eylemine yanıt olarak istemci tarayıcısı tarafından sunucuya gönderilen parametreler
page réponse
şablon sayfanın 4. bölümünü oluşturan komut dosyası

7.6.1. Kimlik Doğrulama

Uygulamayı kullanabilmek için, kullanıcının aşağıdaki sayfayı kullanarak kimliğini doğrulaması gerekir:

Image

action utilisateur
1 - URL apparticles.php için ilk istek
2 - menüdeki Kimlik Doğrulama seçeneğinin kullanılması
3 - URL articles.php'e hatalı parametrelerle doğrudan başvuru
paramètres envoyés
1 - parametre yok
2 - action=authentifier?phase=0
3 - hatalı parametre listesi
page réponse
login.php

Ana sayfada, [Ajouter un article] bağlantısı şu biçimdedir: action=addarticle?phase=0. Diğer bağlantılar da action=(authentifier, updatearticle, deletearticle, selectarticle, sql) şeklinde aynı biçime sahiptir. Kullanıcı formu doldurur ve [Connexion] düğmesini kullanır:

Image

Yanıt şu şekildedir:

Image

action utilisateur
bouton [Connexion]
paramètres envoyés
action=authentifier?phase=1
page réponse
infos.php

Sayfanın başlığı, kullanıcının oturum açma bilgilerini ve yönetici/kullanıcı haklarını gösterecek şekilde değiştirildi. Ayrıca, 2. bölgedeki tüm bağlantılar, bir oturumun başladığını yansıtacak şekilde değiştirildi. Bu bağlantılara PHPSESSID=[PHPSESSID] parametresi eklendi.

Sunucu, istemciyi tanımlayamadıysa, istemciye farklı bir yanıt gönderilir:

Image

action utilisateur
bouton [Connexion]
paramètres envoyés
action=authentifier?phase=1
page réponse
erreurs.php

[Retour à la page de login] bağlantısı, URL apparticles.php?action=authentifier&phase=2&txtLogin=x adresine yönlendiren bir bağlantıdır. Bu bağlantı, kullanıcıyı giriş sayfasına geri yönlendirir; burada giriş alanı, txtLogin parametresinin değeriyle doldurulur:

Image

action utilisateur
lien [Retour à la page de login]
paramètres envoyés
action=authentifier?phase=2&txtLogin=x
page réponse
login.php

7.6.2. Makale ekleme

[Ajouter un article] menü bağlantısı, şablon sayfasının 4. bölümüne şu sayfayı getirir:

Image

action utilisateur
lien [Ajouter un article]
paramètres envoyés
action=addArticle?phase=0&PHPSESSID=[PHPSESSID]
page réponse
addarticle.php

Kullanıcı alanları doldurur ve [Ajouter] düğmesiyle (submit türünde) tüm verileri sunucuya gönderir. İstemci tarafında herhangi bir doğrulama yapılmaz. Doğrulamayı sunucu gerçekleştirir. Sunucu, aşağıdaki örnekte olduğu gibi bir hata sayfası gönderebilir:

İstek
Yanıt
action utilisateur
bouton [Ajouter]
paramètres envoyés
action=addArticle?phase=1&PHPSESSID=[PHPSESSID]
page réponse
erreurs.php

[Retour à la page d'ajout d'article] bağlantısı, giriş sayfasına geri dönmenizi sağlar:

Talep
Yanıt
action utilisateur
lien [Retour à la page d'ajout d'article]
paramètres envoyés
action=addArticle?phase=2&PHPSESSID=[PHPSESSID]
page réponse
article.php

Ekleme işlemi hatasız bir şekilde gerçekleştirilirse, kullanıcıya bir onay mesajı gönderilir:

Talep
Yanıt
action utilisateur
bouton [Ajouter]
paramètres envoyés
action=addArticle?phase=1&PHPSESSID=[PHPSESSID]
page réponse
infos.php

7.6.3. Makaleleri görüntüleme

[Lister des articles] menü bağlantısı, şablon sayfasının 4. bölümüne şu sayfayı getirir:

Image

action utilisateur
[Lister des articles] menü bağlantısı
paramètres envoyés
action=selectArticle?phase=0&PHPSESSID=[PHPSESSID]
page réponse
select1.php

[colonnes] from articles where [where] orderby [orderby] sorgusu, [colonnes], [where] ve [orderby] değerlerinin bulunduğu makale tablosuna gönderilecektir. Örneğin:

Talep
Yanıt
action utilisateur
bouton [Afficher]
paramètres envoyés
action=selectArticle?phase=1&PHPSESSID=[PHPSESSID]
page réponse
select2.php

İstek hatalı olabilir; bu durumda müşteriye bir hata sayfası gösterilir:

İstek
Yanıt

Her iki durumda da (hata olsun ya da olmasın), [Retour à la page de sélection d'articles] bağlantısı, select1.php sayfasına geri dönmenizi sağlar:

İstek
Yanıt
action utilisateur
lien [Retour à la page de sélection d'articles]
paramètres envoyés
action=selectArticle?phase=2&PHPSESSID=[PHPSESSID]
page réponse
select1.php

7.6.4. Makalelerin düzenlenmesi

[Modifier un article] menü bağlantısı, şablon sayfasının 4. alanına şu sayfayı getirir:

Image

action utilisateur
[Modifier un article] menü bağlantısı
paramètres envoyés
action=updateArticle?phase=0&PHPSESSID=[PHPSESSID]
page réponse
updatearticle1.php

Açılır listeden değiştirilecek makalenin kodunu seçin ve bu koda sahip makaleyi değiştirmek için [OK] komutunu uygulayın:

Talep
Yanıt
action utilisateur
bouton [OK]
paramètres envoyés
action=updateArticle?phase=1&PHPSESSID=[PHPSESSID]
page réponse
updatearticle2.php

Değiştirilecek makalenin sayfasını açtıktan sonra, kullanıcı değişikliklerini yapabilir:

Talep
Yanıt
action utilisateur
bouton [Modifier]
paramètres envoyés
action=updateArticle?phase=2&PHPSESSID=[PHPSESSID]
page réponse
infos.php

Kullanıcı düzenleme sırasında hata yapabilir:

Talep
Yanıt

[Retour à la page de modification d'article] bağlantısı, giriş sayfasına geri dönmenizi sağlar:

Image

action utilisateur
lien [Retour à la page de modification d'article]
paramètres envoyés
action=updateArticle?phase=3&PHPSESSID=[PHPSESSID]
page réponse
updatearticle2.php

7.6.5. Bir makalenin silinmesi

[Supprimer un article] menü bağlantısı, şablon sayfasının 4. bölgesinde aşağıdaki sayfayı görüntüler:

Image

action utilisateur
[Supprimer un article] menü bağlantısı
paramètres envoyés
action=deleteArticle?phase=0&PHPSESSID=[PHPSESSID]
page réponse
deletearticle1.php

Kullanıcı, silinecek makalenin kodunu bir açılır listeden seçer:

İstek
Yanıt
action utilisateur
bouton [OK]
paramètres envoyés
action=deleteArticle?phase=1&PHPSESSID=[PHPSESSID]
page réponse
deletearticle2.php

Kullanıcı, [Supprimer] düğmesiyle makalenin silinmesini onaylar:

Talep
Yanıt
action utilisateur
bouton [Supprimer]
paramètres envoyés
action=deleteArticle?phase=2&PHPSESSID=[PHPSESSID]
page réponse
infos.php

7.6.6. Yönetici istekleri

[Requête SQL] menü bağlantısı, şablon sayfasının 4. bölümüne şu sayfayı yönlendirir:

Image

action utilisateur
[Requête SQL] menü bağlantısı
paramètres envoyés
action=sql?phase=0&PHPSESSID=[PHPSESSID]
page réponse
sql1.php

SQL sorgu metnini giriş alanına yazıp [Exécuter] düğmesini kullanarak sorguyu çalıştırın. Aşağıdaki örnekte gösterildiği gibi, yalnızca bir yönetici bu tür sorguları gönderebilir:

İstek
Yanıt
action utilisateur
bouton [Exécuter]
paramètres envoyés
action=sql?phase=1&PHPSESSID=[PHPSESSID]
page réponse
erreurs.php

[Retour à la page d'émission de requêtes SQL] bağlantısı, giriş sayfasına geri dönmenizi sağlar:

Image

action utilisateur
lien [Retour à la page d'émission de requêtes SQL]
paramètres envoyés
action=sql?phase=2&PHPSESSID=[PHPSESSID]
page réponse
sql1.php

Yöneticiyseniz ve sorgu sözdizimi açısından doğruysa:

İstek

isteğin sonucu şu şekilde alınır:

Yanıt
action utilisateur
bouton [Exécuter]
paramètres envoyés
action=sql?phase=1&PHPSESSID=[PHPSESSID]
page réponse
sql2.php

Tabloların güncellenmesi için sorgular gönderilebilir:

İstek
Yanıt
action utilisateur
bouton [Exécuter]
paramètres envoyés
action=sql?phase=1&PHPSESSID=[PHPSESSID]
page réponse
infos.php

7.6.7. Yapılması gereken iş

Uygulama için gerekli komut dosyalarını ve işlevleri yazın:

kullanıcı adı
tür
rol
apparticles.php
komut dosyası
müşteri taleplerinin işlenmesinin başlangıç noktası
authentifier_0
işlev
action=authentifier&phase=0 parametreleriyle tanımlanan talebi işler
authentifier_1
işlev
action=authentifier&phase=1 parametreli isteği işler
authentifier_2
işlev
action=authentifier&phase=2 parametreli isteği işler
addarticle_0
işlev
action=addArticle&phase=0 parametreleriyle gelen isteği işler
addarticle_1
işlev
action=addArticle&phase=1 parametreleriyle gelen talebi işler
addarticle_2
işlev
action=addArticle&phase=2 parametreleriyle gönderilen talebi işler
updatearticle_0
işlev
action=updatearticle&phase=0 parametreleriyle gönderilen isteği işler
updatearticle_1
işlev
action=updatearticle&phase=1 parametreleriyle gelen isteği işler
updatearticle_2
işlev
action=updatearticle&phase=2 parametreleriyle gelen isteği işler
updatearticle_3
işlev
action=updatearticle&phase=3 parametreleriyle gelen isteği işler
deletearticle_0
işlev
action=deletearticle&phase=0 parametreleriyle gelen isteği işler
deletearticle_1
işlev
action=deletearticle&phase=1 parametreleriyle gelen isteği işler
deletearticle_2
işlev
action=deletearticle&phase=2 parametreleriyle gelen isteği işler
selectarticle_0
işlev
action=selectarticle&phase=0 parametreli isteği işler
selectarticle_1
işlev
action=selectarticle&phase=1 parametreli isteği işler
selectarticle_2
işlev
action=selectarticle&phase=2 parametreli isteği işler
sql_0
işlev
action=sql&phase=0 parametreli isteği işler
sql_1
işlev
action=sql&phase=1 parametreli isteği işler
sql_2
işlev
action=sql&phase=2 parametreleriyle gönderilen isteği işler
main.php
komut dosyası
tip sayfasını oluşturur
login.php
komut dosyası
giriş sayfasını oluşturur
erreurs.php
komut dosyası
hata sayfasını oluşturur
infos.php
komut dosyası
bilgi sayfasını oluşturur
addarticle.php
komut dosyası
makale ekleme sayfasını oluşturur
updatearticle1.php
komut dosyası
makale düzenleme sayfasının 1. sayfasını oluşturur
updatearticle2.php
komut dosyası
bir makalenin düzenlemesinin 2. sayfasını oluşturur
deletearticle1.php
komut dosyası
bir makalenin silinmesi işleminin 1. sayfasını oluşturur
deletearticle2.php
komut dosyası
bir makalenin silinmesine ilişkin 2. sayfayı oluşturur
select1.php
komut dosyası
makale seçiminin 1. sayfasını oluşturur
select2.php
komut dosyası
makale seçkisinin 2. sayfasını oluşturur
sql1.php
komut dosyası
sorgu gönderiminin 1. sayfasını oluşturur
sql2.php
komut dosyası
istek gönderiminin 2. sayfasını oluşturur

7.7. Uygulamayı geliştirme

Şu anda, yapması gerekeni kabul edilebilir bir kullanıcı deneyimi ile yerine getiren bir uygulamamız var. Uygulamayı çeşitli açılardan geliştireceğiz:

  • SGBD
  • güvenliği
  • görünümü
  • performansı

7.7.1. Veritabanı türünü değiştirmek

Çalışmamızda, kullanılan SGBD'in MySQL olduğu varsayılmıştır. SGBD'e geçin ve yapılması gereken tek değişikliğin, config.php yapılandırma dosyasındaki $dDSN değişkeninin tanımında olduğunu gösterin.

7.7.2. Güvenliği artırma

Bir web uygulaması geliştirirken, istemcinin bir tarayıcı olduğu ve bize gönderdiği isteğin, bu istekten önce ona gönderilen form tarafından kontrol edildiği varsayımında asla bulunmamalısınız. Herhangi bir program bir web uygulamasının istemcisi olabilir ve dolayısıyla uygulamaya parametreli veya parametresiz herhangi bir istek gönderebilir. Bu nedenle uygulama her şeyi doğrulamalıdır.

apparticles.php komut dosyasının koduna bakıldığında,

  • oturum olmadan kimlik doğrulama dışında hiçbir işlemin gerçekleştirilemeyeceği görülür. Oturum, yalnızca kullanıcı kimlik doğrulamasını başarıyla tamamladığında mevcuttur. Bir oturumun, oturum jetonu adı verilen ve şu biçimdeki oldukça uzun bir karakter dizisiyle tanımlandığını hatırlayalım: 176a43609572907333118333edf6d1fb. Bu jeton, örneğin aşağıdaki gibi yapılandırılmış bir URL kullanılarak çeşitli şekillerde uygulamaya gönderilebilir:

apparticles.php?PHPSESSID=176a43609572907333118333edf6d1fb. 

Doğru jetonu bulma umuduyla jetonu rastgele değiştirerek yukarıdaki URL'i tekrar tekrar talep eden bir program, olası kombinasyonların sayısı o kadar fazla olduğu için doğru kombinasyonu oluşturana kadar muhtemelen günler sürebilir. O zamana kadar, oturumun süresi sınırlı olduğu için büyük olasılıkla sona ermiş olacaktır. Diğer bir risk ise, ağ üzerinde şifrelenmemiş olarak iletilen jetonun ele geçirilmesidir. Bu risk gerçektir. Bu durumda, sunucu ile istemci arasında şifreli bir bağlantı kullanılabilir.

  • Oturum başlatıldıktan sonra yalnızca belirli eylemler izin verilir. action=tricher&phase=0&PHPSESSID=[PHPSESSID] şeklinde ayarlanmış bir URL jetonu, 'tricher' eylemi izin verilen bir eylem olmadığı için reddedilir. Parametreler (eylem, aşama) tanınmadığında, uygulamamız kimlik doğrulama sayfasını görüntüler.

Ancak uygulama, izin verilen eylemlerin doğru sırayla gerçekleştirilip gerçekleştirilmediğini kontrol etmez. Örneğin, aşağıdaki iki eylem:

  1. action=addArticle&phase=0&PHPSESSID=[PHPSESSID]
  2. action=updateArticle&phase=1&PHPSESSID=[PHPSESSID]

iki izinli eylemdir. Ancak eylem 2'nin eylem 1'i takip etmesine izin verilmez.

İstemci tarayıcısı tarafından talep edilen URL işlemlerinin sırasını nasıl takip edebiliriz?

İki PHP değişkeninden yararlanabiliriz: $_SERVER['REQUEST_URI] ve $_SERVER['HTTP_REFERER]; bunlar, istemci tarayıcıları tarafından HTTP başlıklarında gönderilen iki bilgidir.

$_SERVER['REQUEST_URI]: Bu, istemci tarafından talep edilen URI'tir. Örneğin

/apparticles.php?action=addArticle&phase=0&PHPSESSID=[PHPSESSID]

$_SERVER['HTTP_REFERER]: Bu, tarayıcının şu anda talep ettiği yeni URL'ten (önceki URI) önce tarayıcıda görüntülenen URL'tir. Örneğin, daha önce bahsedilen URI dosyasını görüntüleyen tarayıcı bir sunucuya yeni bir istek gönderirse, bu sunucudaki $_SERVER['HTTP_REFERER'] değişkeninin değeri

http://makine:bağlantı noktası//apparticles.php?action=addArticle&phase=0&PHPSESSID=[PHPSESSID]

Uygulamamızdaki iki eylemin sırayla gerçekleştiğini doğrulamak için şu şekilde ilerleyebiliriz:

  1. adımda:
  • talep edilen URI (URI1) not edilir ve oturuma kaydedilir
  1. adımda:
  • 2. adımdan HTTP-REFERER kodu alınır. Bundan, isteği gönderen tarayıcıda daha önce görüntülenen URL'ten URI (URI2) değeri çıkarılır.
  • Oturumda saklanan ve sunucuya daha önce talep edilen eylemin URI'i olan URI URI1 alınır
  • Eğer 2. eylem 1. eylemin ardından geliyorsa, o zaman URI2 = URI1 olmalıdır. Aksi takdirde, istenen eylemin gerçekleştirilmesi reddedilir ve kimlik doğrulama sayfası gösterilir.
  • Bir sonraki işlemin doğrulanması için, oturumda mevcut işlemin URI ve URI2 değerleri kaydedilir. Ve bu şekilde devam eder.

İşte bir örnek. Kimlik doğrulamasından sonra, [Ajouter un article] bağlantısı seçilir:

Image

Bu sayfanın URL kodu şudur:

http://localhost:81/st/php/articles/gestion/articles8/apparticles.php?action=addArticle&phase=0&PHPSESSID=006a63e6027f16c70b63cdae93405eeb

Tarayıcının [Adresse] alanına doğrudan, URL değerini şu şekilde değiştiriyoruz:

http://localhost:81/st/php/articles/gestion/articles8/apparticles.php?action=deleteArticle&phase=1&PHPSESSID=006a63e6027f16c70b63cdae93405eeb

Böylece kimlik doğrulama sayfasına ulaşırız:

Image

Bunun bir açıklaması var. Tarayıcının adres çubuğuna doğrudan kimlik bilgilerini girerek bir URL talebinde bulunduğumuzda, tarayıcı HTTP_REFERER başlığını göndermez. Bu nedenle uygulamamız, önceki eylemden kalan ve oturumda sakladığı URI başlığını bulamaz. Bunun üzerine yanıt olarak kimlik doğrulama sayfasını geri gönderir.

Bu mekanizma tarayıcılar için etkilidir, ancak programlanmış bir istemci için hiç de etkili değildir. Programlanmış bir istemci, istediği herhangi bir HTTP_REFERER başlığını gönderebilir. Dolayısıyla, aslında o adımı atlamış olmasına rağmen, o adımı tamamladığını söyleyerek "hile" yapabilir. Bu durumda, adımların sırasının doğru bir şekilde izlendiğinden emin olunmalıdır. Dolayısıyla, istenen eylem action=addArticle&phase=1 (giriş) ise, önceki eylem mutlaka action=deleteArticle&phase=0 (giriş sayfasının ilk talebi) veya action=addArticle&phase=2 (hatalı eklemeden sonra girişe geri dönüş) olmalıdır. Aynı şekilde, istenen eylem action=addArticle&phase=2 (ekleme) ise, bir önceki eylem action=addArticle&phase=1 (giriş) olmalıdır. Kullanıcının bu akış sırasını takip etmesini zorunlu kılabiliriz.

İlk mekanizma genel nitelikte olup tüm uygulamalara uygulanabilirken, ikincisi her uygulama için özel kodlama gerektirir ve daha zahmetlidir: kullanıcının yapabileceği tüm eylemler ve bunların sıralamaları gözden geçirilmelidir. Bunlar, aşağıdaki kodda gösterildiği gibi bir sözlükte saklanabilir:

  // kimlik doğrulama
  $dPrec['authentifier']['0']=array();
  $dPrec['authentifier']['1']=array(
         array('action'=>'authentifier','phase'=>'0'),
    array('action'=>'authentifier','phase'=>'2')
  );
  $dPrec['authentifier']['2']=array(
         array('action'=>'authentifier','phase'=>'1'),
  );

   // makale ekleme
  $dPrec['addarticle']['0']=array();  
  $dPrec['addarticle']['1']=array(
         array('action'=>'addarticle','phase'=>'0'),
    array('action'=>'addarticle','phase'=>'2')
  );
  $dPrec['addarticle']['2']=array(
         array('action'=>'addarticle','phase'=>'1'),
  );

   // makale düzenleme
  $dPrec['updatearticle']['0']=array();  
  $dPrec['updatearticle']['1']=array(
         array('action'=>'updatearticle','phase'=>'0'),
  );
  $dPrec['updatearticle']['2']=array(
         array('action'=>'updatearticle','phase'=>'1'),
    array('action'=>'updatearticle','phase'=>'3')
  );
  $dPrec['updatearticle']['3']=array(
         array('action'=>'updatearticle','phase'=>'2'),
  );

   // ürün silme
  $dPrec['deletearticle']['0']=array();  
  $dPrec['deletearticle']['1']=array(
         array('action'=>'deletearticle','phase'=>'0'),
  );
  $dPrec['deletearticle']['2']=array(
         array('action'=>'deletearticle','phase'=>'1'),
  );

      // ürün seçme
  $dPrec['selectarticle']['0']=array();  
  $dPrec['selectarticle']['1']=array(
         array('action'=>'selectarticle','phase'=>'0'),
    array('action'=>'selectarticle','phase'=>'2')
  );
  $dPrec['selectarticle']['2']=array(
         array('action'=>'selectarticle','phase'=>'1'),
  );

      // yönetici talebi
  $dPrec['sql']['0']=array();  
  $dPrec['sql']['1']=array(
         array('action'=>'sql','phase'=>'0'),
    array('action'=>'sql','phase'=>'2')
  );
  $dPrec['sql']['2']=array(
         array('action'=>'sql','phase'=>'1'),
  );

$dPrec['action']['phase'], sözlükte dizin görevi gören eylem ve aşamadan önce gelebilecek eylemleri içeren bir tablodur. Bu öncül eylemler de 'eylem' ve 'aşama' adlı iki anahtara sahip bir sözlükle temsil edilir. Bir eylemin öncesinde herhangi bir eylem gelebiliyorsa, $dPrec['action']['phase'] tablosu boş olacaktır. Sözlükte bir eylemin bulunmaması, o eylemin izin verilmediğini gösterir. Yukarıdaki "kimlik doğrulama" eylemini ele alalım:

  // kimlik doğrulama
  $dPrec['authentifier']['0']=array();
  $dPrec['authentifier']['1']=array(
         array('action'=>'authentifier','phase'=>'0'),
    array('action'=>'authentifier','phase'=>'2')
  );
  $dPrec['authentifier']['2']=array(
         array('action'=>'authentifier','phase'=>'1'),
  );

Yukarıdaki kod, action=authentifier&phase=0 eyleminin herhangi bir eylemden önce gelebileceği anlamına gelir; action=authentifier&phase=1 eyleminin öncesinde action=authentifier&phase=0 veya action=authentifier&phase=2 eylemi gelebilir ve action=authentifier&phase=2 eyleminin öncesinde action=authentifier&phase=1 eylemi gelebilir.

Aşağıdaki fonksiyonu yazınız:

  // ---------------------------------------------------------------
  function enchainementOK(&$dConfig, &$dSession, $sAction, $sPhase){
       // devam eden eylemin ($sAction, $sPhase) önceki eylemi takip edip edemeyeceğini kontrol eder
         // $dSession['précédent'] içinde saklanmıştır
         // izin verilen zincirleme işlemler sözlüğü $dConfig['précédents'] içinde bulunur
         // zincirleme mümkünse TRUE değerini, aksi takdirde FALSE değerini döndürür
....

Bu işlev, ana uygulamanın eylem dizisinin doğru olup olmadığını kontrol etmesini sağlar:

<?php
     // ürün tablosunun yönetimi
  include "config.php";
  include "articles.php";  

  // oturum
  session_start();
  $dSession=$_SESSION["session"];

   // yapılması gereken işlem
  $sAction=$_POST["action"] ? $_POST["action"] : $_GET["action"] ? $_GET["action"] : "authentifier";
  $sAction=strtolower($sAction);
   // eylemin olası aşaması
  $sPhase=$_POST["phase"] ? $_POST["phase"] : $_GET["phase"] ? $_GET["phase"] : "0";

     // devam eden bir oturum var mı?  
  if(! isset($dSession)){
      // kullanıcı kimlik doğrulaması
    if($sAction=="authentifier" && $sPhase=="0") authentifier_0($dConfig);
    if($sAction=="authentifier" && $sPhase=="1") authentifier_1($dConfig);   
    if($sAction=="authentifier" && $sPhase=="2") authentifier_2($dConfig);    
     // anormal eylem
    authentifier_0($dConfig);        
  }//if - oturum yok

     // oturum alınır
  $dSession=unserialize($dSession);

     // eylem dizisi normal mi?
  if( ! enchainementOK($dConfig,$dSession,$sAction,$sPhase)){
     // anormal işlem dizisi
    authentifier_0($dConfig);        
  }//if

     // eylemlerin işlenmesi
  if($sAction=="authentifier"){
   if($sPhase=="0") authentifier_0($dConfig);  
   if($sPhase=="1") authentifier_1($dConfig);  
   if($sPhase=="2") authentifier_2($dConfig);  
  }//if     
  if($sAction=="addarticle"){
...

7.7.3. "Görünümü" geliştirmek

Bu uygulamanın tasarımı sırasında belirlenen koşullardan birinin, uygulamanın geliştirilebilir olması olduğunu hatırlayalım. Birkaç hafta sonra, uygulamanın kullanıcı dostu yapısının iyileştirilmesi gerektiği fark edildiğini varsayalım. Uygulamayı, şablon sayfanın yapısı ve sunumu değişecek şekilde değiştirin. Değişiklikler iki yerde yapılacaktır:

  • Tip sayfanın yapısını tanımlayan main.php komut dosyasında. Bu komut dosyasını güncelleyin.
  • uygulamanın "görünümünü" belirleyen stil sayfasında. Bunu değiştirin.

7.7.4. Performansı iyileştirme

Şu an için hafif bir istemci tarayıcısı tercih ettik: bu tarayıcı sadece sunumdan ibarettir. Ona gönderilen web sayfalarına komut dosyaları ekleyerek, bu tarayıcıya işlem yaptırmak mümkündür. Bu komut dosyaları farklı dillerde yazılabilir; özellikle vbscript ve javascript. Internet Explorer ve Netscape, tarayıcı pazarında yaklaşık 60/40 oranında hakim durumdadır. Ayrıca, IE yalnızca Windows ortamında mevcuttur; örneğin Netscape'in hakim olduğu Unix ortamında bulunmamaktadır. Netscape, VBScript komut dosyalarını yerel olarak çalıştırmazken, her iki tarayıcı da JavaScript komut dosyalarını çalıştırır. Netscape, tarayıcı pazarında hâlâ önemli bir paya sahip olduğundan, VBScript komut dosyalarından kaçınılmalıdır. Bu nedenle, istemci tarafındaki komut dosyalarında genellikle JavaScript kullanılır.

Sunucunun müdahale etmesine gerek olmayan işlemler, istemci tarafındaki komut dosyalarına devredilir. Uygulamamızda, istemci tarayıcısının bir isteği ancak doğruladıktan sonra sunucuya göndermesi faydalı olacaktır. Böylelikle, kullanıcı kimlik doğrulama formundaki [login] alanını boş bırakmışsa, sunucuya kimlik doğrulama isteği göndermek gereksiz hale gelir. Kullanıcıya isteğinin hatalı olduğu konusunda uyarıda bulunmak daha uygun olur:

Image

Bunun, sunucunun login alanının boş olup olmadığını kontrol etmesini engellemeyeceği unutulmamalıdır; çünkü istemci mutlaka bir tarayıcı olmayabilir ve bu durumda önceki doğrulama yapılmamış olabilir. İstemcinin bir tarayıcı olduğunu varsaymak, uygulamanın güvenliği açısından büyük bir risktir.

Tarayıcının sunucuya bilgi gönderdiği farklı anları gözden geçirin ve bu bilgilerin doğrulanabileceği durumlarda, bilgilerin sunucuya gönderilmeden önce tarayıcının geçerliliğini kontrol etmesini sağlayacak bir veya birkaç JavaScript işlevi yazın.

Önceki örneği ele alırsak, kimlik doğrulama sayfasını oluşturan login.php komut dosyası şu şekilde olur:


<script language="javascript">
    function check(){
       // giriş yapıldığı kontrol ediliyor
    with(document.frmLogin){
        champs=/^\s*$/.exec(txtLogin.value);
      if(champs!=null){
          // giriş yok
        alert("Vous n'avez pas indiqué de login");
        txtLogin.focus();
        return;
      }//if
       // veriler mevcut - sunucuya gönderiliyor
      submit();
    }//with
  }//kontrol
</script>   
    
<form name="frmLogin" method="post" action="<?php echo $main["post"] ?>">
    <table>
        <tr>
            <td>login</td>
            <td><input type="text" value="<?php echo $main["login"] ?>" name="txtLogin" class="text"></td>
        </tr>
        <tr>
            <td>mot de passe</td>
            <td><input type="password" value="" name="txtMdp" class="text"></td>
      <td><input type="button" onclick="check()" value="Connexion" class="submit"></td>      
        </tr>
    </table>
</form>    

7.8. Daha fazla bilgi

Son olarak, bu vaka çalışmasını daha derinlemesine incelemek için birkaç öneri sunmak istiyoruz:

  • Bu uygulamanın tipik sayfasının bir sınıf haline getirilip getirilemeyeceğini incelemek ilginç olabilir. Bu sınıf, daha sonra diğer uygulamalarda da kullanılabilir.
  • Uygulamamız tarayıcı tabanlı istemcilere oldukça uygun olmakla birlikte, "Bağımsız Uygulama" türü istemcilere daha az uygundur. Bu tür istemciler:
    • sunucuyla bir TCP bağlantısı kurmalı
    • sunucuyla "iletişim kurmak" HTTP
    • sunucunun yanıtlarını HTML analiz ederek istenen bilgiyi bulmak; çünkü bağımsız istemci, tarayıcılara yönelik sunum kodu HTML ile muhtemelen ilgilenmeyecektir.

Uygulamamızın HTML yerine XML üretmesi ilginç olurdu. Böylece, istemcileri ister (yine de nispeten yeni olan) tarayıcılar ister bağımsız uygulamalar olsun, fark etmez. Sunucunun XML yanıtı herhangi bir sunum bilgisi içermeyecek, sadece içerik içereceğinden, bağımsız uygulamaların aradıkları bilgiyi bulmakta hiçbir zorluk yaşamayacaktır.

  • Makale veritabanına eşzamanlı erişim konusuna kesinlikle dikkat edilmesi gerekir. Açıklığa kavuşturulması gereken en az iki nokta vardır:
  1. Uygulama tarafından kullanılan SGBD, aynı makaleye eşzamanlı erişimi doğru bir şekilde yönetiyor mu? Örneğin, iki kullanıcı aynı anda aynı makaleyi düzenlerse ([Modifier] düğmesine aynı anda basarlarsa) ne olur? Bu durum muhtemelen altta yatan SGBD'e bağlıdır.
  2. Şu anda uygulamamız eşzamanlı erişimleri desteklemiyor. Ancak, beklenmedik durumlar yaşanabilse de veritabanı tutarlı bir durumda kalmalıdır. Şu olay dizisini ele alalım:
      • U1 kullanıcısı bir makaleyi düzenlemeye başlar
      • Kullanıcı U2, biraz sonra aynı makaleyi silmek için sayfaya girer
      • Her iki işlem de istemci-sunucu iletişimi gerektirir. Her bir kullanıcının çalışma tarzına bağlı olarak, U2 kullanıcısı, U1 kullanıcısından önce işini bitirebilir. U1, düzenlemelerini tamamlayıp [Modifier] aracılığıyla onayladığında, yanıt olarak bir bilgi sayfası alacaktır; SGBD, [0 ligne(s) ont été modifiées]'e, düzenlemek istediği sayfanın bu arada silindiğini bildirecektir. Kullanıcı muhtemelen şaşıracaktır. Kullanıcı deneyimi açısından, hatayı daha net bir şekilde belirten bir sayfa görüntülemek şüphesiz daha uygun olacaktır. Ayrıca, bir makalenin güncelleme sürecine girer girmez kullanıcıya o makaleye özel erişim hakkı verilmesi de düşünülebilir. Aynı makaleyi güncellemek isteyen başka bir kullanıcıya, başka bir güncelleme işleminin devam ettiği yanıtı verilir. İlk kullanıcının güncellemesini onaylaması gecikirse bu durum sorun yaratacaktır: diğer kullanıcılar engellenmiş olacaktır. Bu konuda bulunması gereken çözümler, büyük ölçüde kullanılan SGBD'in yeteneklerine bağlı olacaktır. Örneğin, Oracle bu alanda MySQL'e göre daha fazla yeteneğe sahiptir.