8 Eylül 2018 Cumartesi günü PHP başlığında

DemirApp PHP 7 Uygulama Çatısı

Başlarken

DemirApp uygulama çatısı, kendi ihtiyaçlarımdan yola çıkarak yazdığım, basit ve minimal tutmaya çalıştığım bir uygulama. Küçük ve orta çaplı uygulamaları rahatça geliştirebileceğiniz bu PHP çatısında, PHP ile kod yazarken sıkça ihtiyaç duyduğum metod ve fonksiyonları bir araya getirdim. Umarım faydalı olur.

https://github.com/yidemir/App

Gereksinimler

  • PHP 7.1 ve üzeri
  • JSON yardımcısı
  • Apache, NGINX gibi bir sunucu

Kurulum

Kurulum için önerim, composer paket yöneticisini kullanmanızdır. Eğer kullanmıyorsanız, projenin Github kaynağından indirerek de kullanabilirsiniz.

Composer ile kurulum

$ composer require yidemir/app

ve kodlamaya başlayın:

<?php

use Demir\App;

require __DIR__.'/vendor/autoload.php';

App::get('/', function(){
    echo 'Merhaba dünya!';
});

App::run();

Git ile kurulum

$ git clone https://github.com/yidemir/App.git

App dizininin içine index.php dosyası oluşturun ve çalıştırın:

<?php

use Demir\App;

require __DIR__.'/src/App.php';

App::get('/', function(){
    echo 'Merhaba dünya!';
});

App::run();

İndirip, dosya sisteminden kullanım

Github'da yayımlanan sürümler sayfasından projeyi indirip, Git ile kurulum bölümünde bahsedildiği gibi kodlamaya başlayabilirsiniz.

Test

Uygulamayı test etmek için composer ve phpunit gerekmektedir.

$ composer test

Rota İşlemleri

DemirApp uygulamasında GET, POST, PATCH, PUT, DELETE ve ANY olmak üzere 6 istek metodu kullanılabilir (veya kendiniz de oluşturabilirsiniz). Bunlardan ANY metodu, herhangi bir istek metodunu ifade ediyor.

Örnek rota tanımlama:

App::map('GET', '/', function(){
    echo 'Selam!';
});

Bu şekilde rotayı haritalarsak / yolunda ve GET metodunda geri çağırım işlevimiz çalışacak ve ekrana 'Selam!' çıktısını verecektir. Aynı zamanda şöyle de kullanılabilir ve kullanılması önerilir:

App::get('/', function(){
    echo 'Selam!';
});

Yukarıdaki yazılan kod ile bu, aynı işlevi görür. Birden fazla istek metodunu cevaplamak içinse, yine map metodunu kullanabiliriz.

App::map(['GET', 'POST'], '/', function(){
    echo 'Selam!';
});

İlk parametreye dizi halinde gönderip, içerisine istediğimiz istek metodlarını yazdığımızda, cevabımızı ona göre alırız. Bütün isteklere cevap vermek için any metodunu kullanabiliriz:

App::any('/', function(){
    echo 'Selam!';
});

Geri Çağırım Parametresi

Rota tanımlarken, geri çağırım parametresi, anonim bir fonksiyon, normal bir fonksiyon, Sınıf:metod şeklinde bir tanımlama veya doğrudan bir sınıf olabilir.

Anonim Fonksiyon:

App::get('/', function(){
    echo 'Selam!';
});

Normal Fonksiyon:

function home(){
    echo 'Selam!';
}

App::get('/', 'home');

Sınıf:metod işareti

class HomeController
{
    public function index()
    {
        echo 'Selam!';
    }
}

App::get('/', 'HomeController:index');

Sınıf:

class Home
{
    public function __invoke()
    {
        echo 'Selam!';
    }
}

App::get('/', new Home);

Geri Çağırım Dizesi:

class HomeController
{
    public function index()
    {
        echo 'Selam!';
    }
}

App::get('/', [new HomeController, 'index']);

Bu yöntemlerin hepsi kullanılabilir.

Rota İsimlendirme

Rota ve rota grupları tanımlarken, geri çağırım parametresinden sonra gelen parametre, rotanın ismini işaret eder.

App::get('/homepage', 'App\Controllers\HomeController:index', 'home');

App::url('home'); // '/homepage' değeri dönecektir

Ara Katman İşlevi (Middleware) Ekleme

Tam manasıyla bir ara katman mimarisi olmasa da, basit bir biçimde rotalara ve rota gruplarına ara katman işlevleri, rota isminden sonraki parametrede dize halinde eklenebilir. Örnek:

class CheckAdmin
{
    public function __invoke()
    {
        if ('admin' !== 'user') {
            exit('Please login');
        }
    }
}

function check_admin()
{
    if ('admin' !== 'user') {
        exit('Please login');
    }
}

App::get('/', new Home, 'home', ['check_admin', new CheckAdmin]);

Yukarıdaki örnekte iki tane middleware tanımladık. Birisi fonksiyon diğer sınıf.

Rota Yolu Parametreleri

DemirApp'te rota yolları düzenle ifadelerle (Regular Expressions) oluşturulup, istek zamanı eşleştirilir. Yalnız, sık kullanılan rota yolu parametreleri hali hazırda belirli öbeklerle kullanılabilir.

Karşılaştırma:

:id ve :number öbeği rakamları ifade eder
:string öbeği dizgeyi ifade eder
:slug öbeği kalıcı bağlantıyı ifade eder
:any herhangi bir değeri ifade eder
:all bütün her şeyi ifade eder

Örnekler:

App::get('/post/:id', function($id){
    echo $id;
});
// /post/5 ile eşleşir
// $id değeri 5 olur

App::get('/post/:slug', function($slug){
    echo $slug;
});
// /post/merhaba-dunya-bu-ilk-yazim ile eşleşir
// $slug değer merhaba-dunya-bu-ilk-yazim olur

App::get('/post/tag/:string', function($tag){
    echo strip_tags($tag);
});

// /post/tag/Yeni_Yazılar ile eşleşir
// $tag değeri Yeni_Yazılar olur

App::post('/hello/:any', function($name){
    echo strip_tags($name);
});
// /hello/Demir<script> ile eşleşir
// $name değeri Demir<script> olur,
// güvenlik için süzmek gerekir

App::get('/:all', function($all){
    echo $all;
});
// /merhaba/dunya/ve/url ile eşleşir
// $all değer merhaba/dunya/ve/url olur

App::post('/name/([demir]+)', function($param){
    echo $param;
});
// /name/demir ile eşleşir
// /name/imedr ile eşleşir
// /name/none ile eşleşmez

App::delete('/(post|category)', function($whichOne){
    $module = $whichOne;
});
// /post ile eşleşir
// /category ile eşleşir
// /setting ile eşleşmez

Rota Gruplandırma

DemirApp'te, rotaları gruplandırmak için group metodu kullanılır.

App::group('/admin', function(){
    App::get('/', 'Admin\DashboardController:index');
    App::get('/posts', 'Admin\PostController:index');
});

// '/admin' isteğinde DashboardController:index
// '/admin/posts' isteğinde PostController:index çalışır

Aynı şekilde, geri çağırım işlevinden sonra gelen parametre grup adını, sonraki parametre ise ara katmanları (middleware) alır. Örnek:

function check_admin()
{
    if ('admin' !== 'user') {
        exit('Please login');
    }
}

App::group('/admin', function(){
    App::get('/', 'Admin\DashboardController:index', 'home');
    App::get('/posts', 'Admin\PostController:index', 'posts.index');
}, 'admin.', ['check_admin']);

App::url('admin.posts.index'); // '/admin/posts' değer döner

Kaynak Kontrolcü Tanımlama

DemirApp'te rota tanımlarken, tek tek tüm istek metodlarını, ekle-oku-düzenle-sil (crud) işlevlerini tanımlamaya alternatif bir yöntem olan resource metodu bulunur.

Bu metod üç parametre alır. Birisi rota yolu, diğer geri çağırım işlevi veya sınıf, diğeri ise varsa ara katmanlar. Örneğimizi görelim:

App::resource('/admin/posts', App\Controllers\PostController::class);
// veya
App::resource('/admin/posts' 'App\Controllers\PostController');
// veya
App::resource('/admin/posts', new App\Controllers\PostController);
// birçok şekilde tanımlama yapılabilir
// üçüncü parametre ara katmanları alır

App::resource('/posts', 'PostController', ['check_post_owner']);

Kaynak kontrolcü tanımlarken, rota isimlendirmeleri otomatik yapılır. /admin/posts için admin.posts isimlendirmesi yapılır ve sonrasında işlevin adı gelir.

App::resource('/posts', 'PostController'); için oluşturulan rotalar şunlardır.

Metod URI Kontrolcü Aksiyonu Rota ismi
GET /posts index posts.index
GET /posts/create create posts.create
POST /posts store posts.store
GET /posts/:id show posts.show
GET /posts/:id/edit edit posts.edit
PATCH /posts/:id update posts.update
DELETE /posts/:id destroy posts.destroy

Konteyner İşlemleri

DemirApp, içerisinde basit bir konteyner (bağımlılık yöneticisi, dependency injection) bulundurur. Örnekler:

App::container()->set('name', 'Demir');
App::container()->get('name'); // Demir değeri dönecektir
App::container()->has('name'); // true

// Bir sınıf veya işlev tanımlamak isteyebiliriz:
App::container()->set('obj', function(){
    $obj = new stdClass();
    $obj->name = 'Demir';
    $obj->type = 'App';
    return $obj;
});

$object = App::container()->get('obj'); 
echo $object->name; // Demir çıktısı alınır

Konteyner içinde statik halde bir öge de saklanabilir.

App::container()->singleton('pdo', function() : \PDO {
    return new \PDO('sqlite:db.sqlite');
});

var_dump(
    App::container()->get('pdo') === App::container()->get('pdo')
); // true

Konteyner içine tanımlama yaparken, parametre de gönderilebilir. Tanımalama sırasında verilen ilk parametre konteynerin kendisidir, sonrakiler kullanıcı parametreleridir.

App::container()->singleton('pdo', function($container, string $dsn, string $user, string $password) : \PDO {
    return new \PDO($dsn, $user, $password);
});

// çağırırken

$pdo = App::container()->call('pdo', ['mysql:host=localhost;dbname=test', 'root', 'root']);

Kısa Kullanım

İstendiği taktirde getirme ve tanımlama işlevleri daha kısa kullanılabilir:

// Tanımlama:
App::container(['name' => 'Demir']);

// Getirme:
App::container('name'); // Demir

// İkinci parametre yoksa, varsayılan değeri alır:
App::container('nameless', 'DemirApp'); // DemirApp

Bu kullanım, singleton ve call metodları ile uyumlu değildir.

Talep/İstek (Request) İşlemleri

DemirApp'te istek işlemleri basit işlemler yapmak için oldukça kullanışlıdır. Örnekler:

App::request()->params(); // Bütün parametreleri döndürür
App::request()->all(); // params metodu ile aynı işlev
App::request()->param('page'); // page parametresi döner
App::request()->param('page', 5); // page parametresi yoksa 5'tir
App::request('page'); // param metodu ile aynı işlev
App::request('page', 5); // param metodu ile aynı işlev
App::request()->only('title', 'body', 'created_at'); // sadece belirtilen parametreler döner
App::request(['title', 'body', 'created_at']); // only metodu ile aynı işlev
App::request()->has('title'); // boolean döner, title var mı yok mu
App::request()->get('page', 5); // $_GET verisinden değer alır, yoksa varsayılan değer olan 5 döner
App::request()->post('title', 'Başlık'); // $_POST verisinden değer alır, yoksa varsayılan değer olan 'Başlık' döner
App::request()->file('image'); // $_FILES verisinden değer alır
App::request()->method(); // string Geçerli istek metodunu döndürür
App::request()->method('get'); // boolean İstek metodu belirtilen değer mi kontrol eder
App::request()->isAjax(); // boolean Ajax isteği mi kontrol eder

Session İşlemleri

Session metodu basit oturum işlemlerini yönetmemize yarar.

App::session('session-key'); // oturum değeri döner
App::session(['session-key' => 'value']); // oturum değeri belirler

Session işlemlerini gerçekleştirebilmek için evvela oturumun başlatılmış olması gerekir.

App::session()->start(); // başlatır
App::session()->set('key', 'value'); // belirler
App::session()->get('key', 'default'); // döndürür
App::session()->has('key'); // kontrol eder
App::session()->destroy('key'); // kaldırır
App::session()->destroy(); // bütün verileri kaldırır

Flaş Mesajları

Session metodunun içerisinde bir de flash metodu bulunmaktadır. Flaş mesaj sistemi, yönlendirme yapmadan evvel belirlenip, daha sonra görünüm/view sayfasında görüntülenip kaybolurlar. Örnek:

App::session()->flash('Örnek bir flaş mesajı');
// bu şekilde belirlenen bir flaş mesajın varsayılan türü 'message'dır.
// istediğiniz türde flash mesajları belirleyebilirsiniz
// belirlenen flaş mesajlar, dize içerisinde depolanır
// diğer örnekler

// doğrudan flash metoduyla da işleminizi yapabilirisiniz
App::flash('Başlık alanı gereklidir', 'formError');
App::flash('Gövde alanı gereklidir', 'formError');
// Flash mesajlarını view'da elde etmek için:
$formErrors = App::flash()->get('formError');
// ['Başlık alanı gereklidir', 'Gövde alanı gereklidir']

Flaş sistemi içerisinde iki tane türü mevcut halde bulundurur. Bunlar message ve error türleridir. Şu şekilde kullanılır:

App::flash()->error('Birinci hata');
App::flash()->error('İkinci hata');

$errors = App::flash()->getErrors();
// ['Birinci hata', 'İkinci hata']

App::flash()->message('İşleminiz başarıyla tamamlandı');
$messages = App::flash()->getMessages();
// ['İşleminiz başarıyla tamamlandı']

Hata Yönetimi

DemirApp'te hata yönetimi için hali hazırda 404 sayfa bulunamadı ve istisna işleyicisi bulunmaktadır. Şu şekilde kullanılır:

App::notFound(function(){
    http_response_code(404);
    App::render('404'); // 404 görünümünü yorumlar
    // veya
    // echo '404 Sayfa Bulunamadı';
});

İstisna işleyicisi ise şu şekilde kullanılabilir:

App::error(function($error){
    http_response_code(500);
    if (App::container('debug') === true) {
        echo $error->getMessage();
        echo '<br>';
        echo $error->getFile();
        echo '<br>';
        echo $error->getLine();
    } else {
        echo 'Bir uygulama hatası meydana geldi';
    }
});

JSON Yanıtı

Bir rotada tek bir metod ile JSON yanıtı verilebilir.

App::get('/api/posts', function(){
    $posts = Post::all();
    return App::json($posts);
});

// veya 404 sayfası için:

App::notFound(function(){
    http_response_code(404);
    if (App::request()->isAjax()) {
        return App::json(['message' => '404 page not found', 'status' => 404]);
    } else {
        echo '404 page not found';
    }
});

Yönlendirme

App::get('/', function(){
    App::redirect('homepage');
});

App::get('/home', function(){
    echo 'welcome to homepage';
}, 'homepage');

Parametreli yönlendirme:

App::get('/post/:id', 'PostController:show', 'show.post');
App::redirect('show.post', 5);

URL Oluşturma

DemirApp ile isimlendirilmiş rotalar için URL oluşturulabilir.

App::url('homepage'); // '/'

App::get('/:slug/:slug', 'show_function', 'show.post');
App::url('show.post', 'category-slug', 'post-slug');
// '/category-slug/post-slug'

// diğer bir örnek:
App::post('/comment/save/:id', 'save_comment', 'save.comment');
App::url('save.comment', 5); // '/comment/save/5'

Görünüm (View) İşlemleri

DemirApp'te hali hazırda bir Görünüm sınıfı bulunur. Bu sınıfla görünüm dosyaları yorumlanıp, bloklar oluşturulup görüntülenebilir. Basit bir kullanıma sahiptir. Öncelikle bir view dosyasını nasıl yorumlarız ona bakalım.

App::get('/post/show/:id', function($id){
    $post = Post::find($id);
    App::render('post/show', ['post' => $post]);
});

Burada bir gönderi görüntüleme senaryosu hazırladık. $post değişkeni içerisinde bulunan gönderi değerlerini post/show.php dosyasına gönderdik. render yani yorumlamanın birden fazla yöntemi vardır:

echo App::view('post/show')->with('post', $post);
echo App::view('home')->render();
App::view('home')->show();
App::view('home')->with('name', 'Yılmaz')->show();

Temel Görünüm Ekleme ve Kullanma

Görünüm dosyalarını kullanmadan evvel, bu dosyaların hangi dizinde bulunduğunu belirlemeniz gerekiyor. Bunun için sınıf içerisinde bulunan container metodundan faydalanacağız.

App::container(['views.path' => __DIR__ . '/views/dosyalarinin/dizini']);

Artık görünüm dosyalarının bulunacağı dizini işaretledik. App::render('deneme') ifadesi, views/dosyalarinin/dizini/deneme.php dosyasını yorumlayacaktır.

Blok/Bileşen Tanımalama ve Kullanma

DemirApp ile temel görünüm dosyası oluşturup diğer görünüm dosyalarınızda bunu rahatlıkla kullanabilirsiniz.

views/page-layout.php:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title><?= $this->has('title') ? $this->get('title') : 'DemirApp' ?></title>
</head>
<body>
    <header>Header</header>
    <main><?= $this->get('content') ?></main>
    <footer>Footer</footer>
    <?= $this->get('scripts') ?>
</body>
</html>

has metodu, blok değerinin tanımlanıp tanımlanmadığını kontrol eder. get metodu tanımlanan bloğu döndürür. Tanımlama yapmak için örnekleri görelim:

views/home.php:

<?php $this->layout('page-layout') ?>
<?php $this->set('title', 'Ana Sayfa') ?>
<p>Sayfa içeriği buraya gelecek</p>

<?php $this->start('scripts') ?>
<script>
    console.log('javascript içeriği')
</script>
<?php $this->end() ?>

Burada evvela home.php'nin hangi temel görünümü kullandığını layout metodu ile belirledik. set metodu ile, title değerini tanımladık. Sonrasında start ve end metodlarıyla scripts bloğunu tanımladık.. set ve start metodları arasında ince bir fark vardır. set metodu, ikinci parametresine ne verilirse onu alır. Ama start metodu ile başlatıp end metodu ile bitirdiğimizin arasına ne koyarsak blok içeriği o olur. Bunu çıktı tamponlaması ile yapar (ob_start).

Çıktı tamponlaması yaparak belirlenen blok varsayılan olarak varsa eski blok içeriğine iliştirilir (append). Eğer iliştirmek istemiyorsanız yani üzerine yazmak istiyorsanız end metodunun ilk parametresine false değeri verebilirsiniz. Örn: $this->end(false);

set metodunu varsayılan olarak değeri üzerine yazar. Yazmasını istemiyorsanız üçüncü parametresine true değeri verebilirsiniz. $this->set('title', 'Deneme', true);

Yardımcı Fonksiyonlar

Eğer DemirApp'i composer aracılığıyla kullanıyorsanız, otomatik olarak yüklenen yardımcı fonksiyonları kullanabilirsiniz. Eğer manuel olarak kullanıyorsanız kaynak dosyaları içindeki helpers.php dosyasını sayfaya dahil etmelisiniz. Aşağıda genel kullanım örnekleri mevcuttur.

session()->start();
session(['is_logged_in' => false]);
session('is_logged_in');

container()->set('views.path', __DIR__ . '/views');

app()->get('/', function(){
    render('home');
});

// app() fonksiyonu Demir\App örneğini döndürür

app()->notFound(function(){
    if (request()->isAjax()) {
        return json(['status' => 404]);
    } else {
        return view('errors/notFound')->show();
    }
});

app()->post('/save', function(){
    if (empty(request()->post('title'))) {
        flash()->error('Başlık alanı gereklidir');
        redirect('post.create');
    }
});

// URL oluşturma
// url('post.show', 5);

run();

Soruları aşağıdaki yorum bölümünden sorarsanız severek cevaplarım. Umarım işinize yarar. Sevgiler.


Yorumlar