Kategoriler
Teknik

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 DemirApp;

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 DemirApp;

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', 'AppControllersHomeController: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('/', 'AdminDashboardController:index');
    App::get('/posts', 'AdminPostController: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('/', 'AdminDashboardController:index', 'home');
    App::get('/posts', 'AdminPostController: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', AppControllersPostController::class);
// veya
App::resource('/admin/posts' 'AppControllersPostController');
// veya
App::resource('/admin/posts', new AppControllersPostController);
// 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 DemirApp ö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.

Kategoriler
Teknik

PHP’de Rota Sistemi ile Basit ve Hızlı Uygulamalar

Daha önceden PHP İle Basit Bir Router Nasıl Yapılır başlıklı bir yazı yazmıştım. Bu yazıda nesne yönelimli programlama ile çok basit bir rota sistemi yazmıştık. Şimdiyse bunun daha basit halini yazacağız.

Hemen bir app.php dosyası oluşturup kodlarımızı yazmaya başlayalım. Çok az kod yazacağız ama çok işleve sahip olacak. Kod satırlarında ne işe yaradıklarını, ne için yazdığımızı vs. anlattım. Es geçmemenizi öneririm.

<?php

// Oluşturduğumuz rotaları tutacak değişken
$routes = [];

/**
 * @param string $method Rotanın istek metodu (GET, POST gibi)
 * @param string $path URL yolu (/merhaba mesele)
 * @param callable $callback Anonim işlevimiz yani /merhaba sayfasında çalışacak fonksiyon
 * @return void herhangi bir şey döndürmeyecek
 */
function router_map(string $method, string $path, callable $callback)
{
  // Fonksiyonun dışındaki değişkene erişmek için global'i kullanıyoruz
  global $routes;

  // routes değişkenine yeni eleman ekliyoruz
  $routes[] = [$method, $path, $callback];
}

// Rota sistemini çalıştıracak olan fonksiyon
function router_run()
{
  global $routes;

  // oluşturduğumuz rotaları döngüye sokup tek tek işleyeceğiz
  foreach ($routes as $route) {
    /**
     * Her bir rotanın içindeki verileri list fonksiyonu ile
     * bir değişkene aktarıyoruz. Bu yaptığımız şununla denk:
     * $method = $route[0];
     * $path = $route[1];
     * $callback = $route[2];
     */
    list($method, $path, $callback) = $route;

    if (
      // Eğer tanımlanan method, kullanıcının methodu ile aynıysa
      // yani GET == GET ise sıkıntı yok, devam
      $method == $_SERVER['REQUEST_METHOD'] &&

      // Eğer tanımlanan yol, kullanıcının talep ettiği yol ile aynıysa
      preg_match("@^{$path}$@ixs", $_SERVER['REQUEST_URI'], $params)
    ) {
      // $params değişkenini preg_match fonksiyonu oluşturdu
      // Bu değişken içerisinde varsa parametreler tutulacak
      // yani örneğin: /haber/5 URL'sinde 5 parametre değeridir
      // array_shift fonksiyonu ise $params değişkenindeki ilk 
      // öğeyi kaldırır. Çünkü preg_match parametre verirken 1 tane fazla verir
      array_shift($params);

      // artık fonksiyonumuzu çağırabiliriz
      // aşağıdaki fonksiyon şuna denktir:
      // $callback(...$params);
      return call_user_func_array($callback, $params);
    }
  }

  // Eğer döngüde herhangi bir şey dönmezde
  // sayfa bulunamadı mesajı ver ve uygulamadan çık
    http_response_code(404);
  exit('404 Sayfa Bulunamadı');
}

/**
 * @param string $name View dosyası adı
 * @param array $data View dosyası için çıkarılacak değişkenler
 * @return string
 */
function view_render(string $name, array $data = [])
{
  // View dosyalarının bulunduğu dizin
  $path = __DIR__ . '/views';

  // Eğer dosya mevcutsa
  if (is_file($fullPath = "$path/$name.php")) {

    // Çıktı tamponlamayı başlatalım
    // ob_start() ve ob_get_clean() arasında yazılan kodlar
    // gösterilmez, hafızaya alınır. ob_get_clean bu veriyi döndürür
    ob_start();

    // extract(['isim' => 'Yılmaz']) şeklinde bi kullanım yaparsanız
    // $isim = 'Yılmaz'; yapmış gibi olursunuz. Yani extract fonksiyonu
    // dizedeki verileri değişkene döndürür
    extract($data);

    // Görünüm dosyasını çağıralım
    require $fullPath;

    // Yaptığımız işlemi döndürelim
    return ob_get_clean();
  }
}

// Bütün uygulama kodları bu kadar!

Çekirdek uygulamamızı yaptık. Artık bunu her sayfaya include ederek kullanabileceğiz. İlk örneklerimizi yapalım:

index.php

<?php

require 'app.php';

router_map('GET', '/', function(){
  echo 'Burası giriş sayfası';
});

router_map('GET', '/test_et', function(){
  echo 'Burası test sayfası';
});

// (d+) ifadesi bir rakamı ifade eder
// parantez içinde olmasının sebebi bunu parametre olarak tanımlamak içindir
// /yazi/d+ ile de eşleşir ama parametre olarak çıkmaz
router_map('GET', '/yazi/(d+)', function($id){
  echo 'Burası yazı sayfası<br>';
  echo "$id numaralı yazı gösterilecek";
});

// Form verisini işlemek için POST metodu kullanıyoruz
router_map('POST', '/yazi/kaydet', function(){
  echo 'Burası yazıyı kayıt sayfası';
});

router_run();

Bunu da çalıştırıp testlerimizi yapalım. http://localhost , http://localhost/test_et , http://localhost/yazi/5 sayfalarına erişmeyi deneyelim ve sonuçları gözlemleyelim.

Her şey yolundaysa, doğru yazdınız demektir. Gelelim bir de olayın View yani görünüm yorumlayıcısına. view_render fonksiyonu ile yapacaklarımız oldukça işimize yarayacak. Öncelikle dosyaların bulunduğu dizinde views klasörü oluşturalım ve içerisinde merhaba.php dosyası oluşturalım. İçerisine Merhaba sevgili <?= $name ?>, <?= $age ?> yaşında olduğun söyleniyor! yazıp kaydedelim ve ilk denememizi bir rota oluşturup yapalım:

router_map('GET', '/merhaba_de/(w+)', function($urlDenGelenIsim){
  echo view_render('merhaba', [
    'name' => $urlDenGelenIsim,
    'age' => 35
  ]);
});

Hemen http://localhost/merhaba_de/Yilmaz sayfasına girelim. Türkçe karakter kullanmayalım ama yoksa 404 döner, çünkü regexi bu türkçe karakterli yazmadık.

Bunu da yaptıktan sonra işler sizin hayal gücünüze kalıyor. Bu çok basit bir uygulama. Bunu ufak uygulamalarınızda kullanabilirsiniz de. Ama normal bir uygulama için hiç iyi bir çözüm değil. Geliştirmeniz gerekir. Nasıl geliştirebilirsiniz? En basit örnek şu:

function router_get(string $path, callable $callback)
{
  return router_map('GET', $path, $callback);
}

router_get('/', function(){
  echo 'selam!'
});

Böylelikle GET metodu tanımlarken fazla uğraşmamış olacaksınız. Kalın sağlıcakla. Sorularını sormaktan çekinmeyin, seve seve cevaplarım.

Kategoriler
Teknik

Vue.js Bileşenlerini Sayfa İçinde Doğrudan Kullanmak

Evvela bir Vue bileşeni oluşturalım.

Vue.component('hello', {
  template: '#hello',
  props: ['message'],
})

let app = new Vue({
  el: '#app',
})

Vue bileşenini oluşturduktan sonra, Vue örneğimi oluşturup, #app ID’sine işaret ettik. Vue bileşeni oluştururken vereceğimiz ilk değer, bileşenin adıdır. İkinci değer ise bir obje alır. Burada obje içerisindeki template aşağıda göreceğimiz x-template türündeki script etiketini temsil eder. props ise bileşenin alacağı özellikleri tanımlar.

<div id="app">
  <hello message="Merhaba dünya!" />
</div>

<script type="text/x-template" id="hello">
  <p>{{message}}</p>
</script>

Kolay gelsin, iyi çalışmalar.

Kategoriler
Teknik

PHP’de Blog veya İçerik Yönetim Sistemi Oluşturmak

En popüler içerik yönetim sistemi/blog yazılımı olan WordPress’i bir çoğumuz kullanmıştır kullanmasına ama bazılarımız var ki WordPress’in nasıl çalıştığını merak edip, bazılarımız da eklenti/tema geliştirmeye merak salmışızdır. Tüm bunlardan doğru, karşımıza çıkan binlerce fonksiyon, yöntem, ıvır, zıvır vesaire derken kafamız çorba gibi oluyor. Bu çorbalaşmış kafayı netleştirmek için bu yazıda basit bir blog sistemi nasıl yazılır ondan bahsedeceğim. Elbette ki WordPress alternatifi olmayacak. Sadece işimizi görebilecek, basit ve kullanışlı bir sistem hakkında konuşacağız.

Veritabanı Yapısı

Veritabanı yapısından başlayalım ki sistemi kafamızda oturtmamız daha kolay olsun ve rahatça içselleştirebilelim. Bir blog sistemi aslında en minimal haliyle 2 tablo ile çalışıp güçlü hale getirilebilir. Birincisi içerik tablosu, diğeri ise kategorilerin bulunduğu tablo. Yorum tablosunu atlıyorum, yorum kısmını Disqus veya Facebook yorumlarıyla da çözebiliriz.

İçerikler Tablosu

İçerik tablosu, oluşturmak istediğimiz blog sisteminin kalp niteliğini taşıyan tablo olacaktır. Çünkü her türlü içeriği (blog gönderisi, sayfalar, video içerikleri vs.) burada tutacağız. Hemen nasıl göründüğüne bakalım:

contents tablosu

id slug type title body tags category_id created_at
1 merhaba-dunya post Merhaba Dünya İlk Gönderi, . Lorem lipsum dolor sit amet. Merhaba, Dünya 1 2018-07-18 11:27:53
2 hakkimda page Hakkımda Hakkımda sayfası içeriği 2018-07-18 11:37:12
3 harika-bir-video-cektim video Harika Bir Video Çektim! Youtube embed veya bağlantısı Youtube, Video, Macera 2 2018-07-18 12:42:19

Genel hatlarıyla içerik tablomuz bu şekle sahip olacak. Burada slug sütunu, kalıcı bağlantıyı ifade ediyor. Kullanıcı içeriğe erişirken https://foo.com/kalici-baglanti URL’ini kullanabilmemiz için, veritabanımızda slug sütununun olması şart. Eğer https://foo.com/content.php?id=5 şeklinde bir yapı tercih etmiş olsaydı, slug sütunu koymamıza gerek kalmazdı.

type sütunu ise, içeriğin türünü belirlediğimiz tablo olacak. Ben üç tane içerik türü ekledim, siz hayal gücünüze göre bunu ayarlayabilirsiniz. post blog gönderisini, page blog içindeki bir sayfayı video ise bir video gönderisini temsil ediyor. Mesela buna alternatif/ek olarak gallery eklenip, bir fotoğraf galerisi türü oluşturabilirsiniz.

title gönderinin başlığını, body ise gönderinin içeriğini tutacak olan sütunlar olacak. tags sütununda ise, virgülle ayrılmış biçimde etiketler tutacağız. Böylelikle bir etiket sistemimiz olmuş olacak.

created_at sütunu ise oluşturma tarihini ifade ediyor.

category_id ise kritik bir sütun. Burada mevcut gönderinin hangi kategoriye ait olduğunu belirteceğiz. Bunu yaparken kategorinin adını değilde, ID’sini vereceğiz. Peki vereceğimiz bu ID’yi nerede, nasıl kullanıp ne olduğunu nasıl anlayacağız?

MySQL, SQLite, PostgreSQL gibi veritabanları ilişkisel veritabanı türünde olduğu için, bir tablodaki satırı, başka tablodaki satır veya satırlarla bağlayabiliyor/ilişkilendirebiliyoruz. Şimdi bu blog/içerik yönetim sisteminde, her bir içerik bir adet kategoriye ait olmuş olacak (Sayfa türündeki içerikler herhangi bir kategoriye sahip olmasa da olur).

Kategoriler Tablosu

Kategorileri tuttuğumuz bu tabloda sistem içerisindeki temel taksonomiyi en basit haliyle gerçekleştirmiş olacağız. Daha karmaşık ve spesifik yöntemler var ama, bu yazının konusu basitlik olduğundan fazla detaya girmeyeceğiz. Evvela, tablomuzun yapısın görelim:

categories tablosu

id name description post_count
1 Genel Diğer kategorilere dahil olmayan içerikler 1
2 Eğlence Eğlenceli içeriklerin bulunduğu muazzam bölüm! 1

Şimdi önceki tasarladığımız tablo olan contents tablosuna dönelim ve oradaki category_id sütununa bakalım. Bu sütunda 1 değerini alanların bu tablodaki yani categories tablosundaki ID’si 1 olan satıra ait olduğunu anlıyoruz. 2 değerini alanlar ise, Eğlence kategorisine dahilmiş.

İçerikler tablosundaki 100 tane satır/içerik 1 ID’li kategoriye ait olabilir. Aynı şekilde diğer 35 içerik de 2 ID’li kategoriye ait olabilir. Böylelikle her içerik bir kategoriye ait olmuş olur. Her içeriğin en fazla bir kategorisi olabilir. Birden fazla kategoriye sahip olamaz (buna One To Many ilişkilendirme deniyor). Bu yaptığımız sistemde bu mümkün değil. Çok daha farklı bir ilişkilendirme sisteminde bir içerik birden fazla kategoriye ait olacak şekilde ayarlayabilirdik (WordPress’te birden fazla kategori seçebiliyorsunuz mesela ve bu yöntemin adı Many To Many ilişkilendirme) ama yapmadık. İşleri karıştırmadık. 🙂

Temel olarak, veritabanı yapısı bu şekilde. Peki PHP ile oluşturacağımız rotalar/sayfalar hangi işleri yapacak? Hemen kısaca değinelim.

İçerik Sayfası

İçerik sayfasında, veritabanında slug ile eşleşen veriyi alıp, sayfada işleleyeceğiz. Örnek SQL sorgusu şöyle olacak:

SELECT * FROM contents WHERE slug=?

Veritabanından alacağımı veri, göstereceğimiz içerik olacak. type sütunundaki değere göre de gösterilecek içerik için farklı işlevler gerçekleştirebiliriz.

Kategori Sayfası

Kategori sayfasında, kategori ID’sine göre içerikleri alabiliriz. (Kategoriler için slug da kullanabilirdik) Örnek SQL sorgusunu şöyle görelim:

SELECT * FROM contents WHERE category_id=?

Böylelikle kategoriye ait içerikleri de elde etmiş olduk. Şimdilik anlatacaklarım bundan ibaret. Herhangi bir sorunuz varsa severek cevaplarım. Bir sonraki yazıda görüşmek üzere.

Kategoriler
Teknik

Eloquent ORM’de Erişimci ve Mutatörler

Erişimciler (Accessors)

Erişimciler, veritabanından aldığımız bilgileri/verileri önceden tanımlı olarak biçimlendirmemize olanak tanır. İki tane örnek verelim. Öznitelikleri türkçe olarak örneklendireceğim (elbette ki veritabanını tasarlarken ingilizce kullanmak gerekiyor)

Veritabanımızdaki tabloda ad ve soyad adlı iki adet sütun olduğunu varsayalım. Normal şartlarda veritabanındaki ad soyad bilgisini görüntülemek istediğimizde

Ad Soyad: {{ mb_ucfirst($uye->ad) . ' ' . mb_ucfirst($uye->soyad) }}

şeklinde kullanırız. Ama aşağıdaki gibi bir metodu, modelimize tanımlarsak, işimizi, daha kullanışlı hale getirebiliriz.

public function getAdSoyadAttribute()
{
    return mb_ucfirst($this->ad) . ' ' . mb_ucfirst($this->soyad);
}

Artık şöyle kullanılabilir: Ad Soyad: {{ $uye->ad_soyad }}

Önceden tanımladığımız metod, bize veri işlemede yardımcı oluyor. Benzer bir örneği para birimi için verebiliriz. Veritabanımızda fiyat adlı bir sütun olsun ve içeriği ondalık sayı olarak tutsun. 1325.00 olan bir değeri normal şartlarda görüntülemek istediğimizde ondalık hali yerine şunu göreceğiz: 1325. Şimdi bunu biçimlendirelim.

/**
 * @return string
 */
public function getFiyatAttribute()
{
    return number_format((float) $this->fiyat, 2, ',', '.') . ' TL';
}

Şimdi, veritabanındaki fiyat değerini görüntülemek istediğimizde TL’ye uygun biçimde görünecektir. Yani 1.325,00 TL şeklinde.

 Mutatörler (Mutators)

Erişimcilerin aksine mutatörler gösterilen veriyi işlemek yerine gelen/alınan veriyi işlemekte bize yardımcı olurlar.

Aşağıdaki örnekte 1.325,00 TL olarak gelen veriyi veritabanına kaydetmeden önce 1325.00 haline çevireceğiz.

/**
 * @param string $deger
 * @return void
 */
public function setFiyatAttribute($deger)
{
    $this->attributes['fiyat'] = (float) str_replace(
        [' TL', '.', ','], 
        [null, null, '.'], 
        $deger
    );
}

Biraz daha basitleştirirsek, gelen şifre değerini veritabanına kaydetmeden evvel md5 ile şifrelenmiş haliyle kaydetmek isteyelim. (Ama tabii siz bunu yapmayın, bcrypt ile saklayın)

/**
 * @param string $deger
 * @return void
 */
public function setSifreAttribute($deger)
{
    $this->attributes['sifre'] = md5($deger);
}

Zaman Mutatörleri

Eloquent, varsayılan olarak created_at ve updated_at sütunlarını Carbon (tarih/saat sınıfı) örneğine dönüştürür. $dates sınıf değişkeni üzerine zaman özniteliği adını (aşağıdaki örnekte silinme_tarihi ve yorum_tarihi) ekleyerek geçerli kılabiliyoruz.

class Gonderi extends Model
{
    protected $dates = [
        'created_at',
        'updated_at',
        'silinme_tarihi', // deleted_at
        'yorum_tarihi', // commented_at
    ];
}

 Tarih Formatları

Varsayılan olarak tarih formatı Y-m-d H:i:s olan formatını değiştirmek için kullanılır. $dateFormat sınıf değişkeni aracılığıyla değiştirilir.

class Gonderi extends Model
{
    protected $dateFormat = 'U'; // Unix zaman damgası cinsine döndürür
}

Öznitelik Biçimlendirme

$casts sınıf değişkeni sayesinde model özniteliklerini biçimlendirebiliyoruz (cast). Bu özellik oldukça kullanışlı. Örneğin, vertabanında bir kullanıcının yönetici olup olmadığını yonetici_mi sütunu ile belirliyoruz. MySQL’de boolean veri türü olmadığı için işimizi görecek veri türü Tiny Integer oluyor. Eğer yönetici ise 1 değilse 0 değerini alacağını varsayalım. Veriyi elde etmek istediğimizde yonetici_mi‘in 1 değilde, true olarak biçimlendirmek istiyoruz. Şunu yapmak yeterli geliyor:

class Uye extends Model
{
  protected $casts = [
    'yonetici_mi' => 'boolean',
    'son_giris' => 'timestamp',
    'fiyat' => 'float',
    'yas' => 'integer'
  ]
}

$casts dizisinin ilk elemanında değeri boolean veri türüne biçimlendirmesini söyledik. Diğer elemanlarda da farklı işlemler yaptık. son_giris unix zaman damgasına biçimlenecek. fiyat ondalık sayı tipine biçimlenecek. yas sayı tipine biçimlenecek. Desteklenen biçimlerse şu şekilde: integer, real, float, double, string, boolean, object, array, collection, date, datetime, ve timestamp.

 Dizi ve JSON Biçimlenirme

Dizi biçimlendirme, JSON olarak depolanan veri tiplerinde oldukça kullanışlıdır (MySQL’in 5.7 sürümü JSON veri tipini destekliyor, önceki sürümler için TEXT kullanılabilir).

class Uye extends Model
{
  protected $casts = ['bilgiler' => 'array'];
}

$uye = Uye::findOrFail($id);

$bilgiler = $uye->bilgiler;
$bilgiler['yas'] = 25;
$bilgiler['sehir'] = 'İstanbul';
$uye->bilgiler = $bilgiler;

$uye->save();

Kolay gelsin.


Kaynakça

  1. https://laravel.com/docs/5.6/eloquent-mutators