oksuz / kendy

Kendy is `I have my own framework` project

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Kendy Framework

Kendy bir benim kendi framewörküm var projesidir.

Kurulum

Paket yönetimi ve autoloading için composer kullanmaktayız. Composer kurulumu için bu adresi ziyaret edebilirsiniz.

Kurulumu yaptıktan sonra aşağıdaki komutu verin:

composer install

kurulum esnasında sistem sizden Configuration.php.dist 'de tanımlı olan değerleri isteyecek ve uygulama için gerekli olan konfigurasyonu oluşturacaktır.

Kullanılan 3. Parti Komponentler

Bağımlılık Yönetimi (Dependency Injection)

Proje içerisinde container builder barındırmaktadır. Bu containerBuilder services.json'u okuyarak tanımlı servisleri inşa eder ve Container içine doldurur. Buda bize container olan scope'lar içinde bu servisleri kullanabilmemizi sağlar.

services.json

İki ana düğümden oluşan bir yapıya sahiptir. Bu düğümler parameters ve services'dir. parameters bir objedir key-value tutar, services ise servis objeleri barındıran bir dizidir.

Parametre tanımı:

Aşağıda örnek iki parametre tanımı verilmiştir;

{
  "parameters": {
    "application.environment": "$APP_ENV",
    "log.file": "/var/log/app.log"
  },
}

log.file parametresi'nin karşılığı /var/log/app.log olarak verilmiştir.

Dikkat çekmek istediğimiz bir diğer nokta ise application.environment parametresidir. Burada görmüş olduğunuz gibi $APP_ENV adında bir değişken, değer olarak verilmiştir. Container'in compile zamanında bu değer Application\\Config\\Configuration::APP_ENV sabitinin değerini alacaktır. Dolayısı ile Application\\Config\\Configuration class'i içinde tanımlı constant değerleri, parameters içinde $DEGER olarak kullanabiliriz. Burada dikkat edilmesei gereken iki nokta var

  • Kullanılacak degisken isminin tamami büyük harf olmalı
  • Özel karakter olarak sadece _ kullanılmalı

Kod İçerisinden Parametreye Erişmek:

Mevcut yapıda AbstractController container'a erişim için getContainer() metodunu sunmaktadır. Bu demek oluyor ki, controller içinde aşağıdaki gibi parametrelere erişebiliriz,

<?php
// class declaration

public function testAction()
{
    $env = null;
    if ($this->getContainer()->hasParameter("application.environment")) { // eğer var mi kontrolu gerekli ise bu şekilde yapılabilir
        $env = $this->getContainer()->getParameter("application.environment"); // varsa değeri yok ise null döner
    }
}

// ...

Servis Tanımı:

Bir servisin basit olarak tanımı aşağıdaki gibidir;

  "services": [
    {
      "id": "request",
      "class": "\\Library\\Http\\Request"
    }
  ]

Görüldüğü gibi bir servis nesnesinin olmazsa olmaz iki parametresi var bunlar, id ve class 'dir. id, container üzerinden erişebileceğimiz alias class ise bu alias'a ait class'i temsil etmektedir.

Başka bir servise bağımlı servisler ve constructor injection

Aşağıdaki gibi bir seneryoyu göz önüne alalım;

<?php
class ConsoleLogger implements Logger
{

    public funtion __construct($name = "logger")
    {
        // ...
    }
    
    public function write($message) 
    {
        echo $message;
    }
}


class Application
{
    public function __construct(Logger $logger)
    {
        // ...
    }
}

// init...
$logger = new ConsoleLogger();
$application = new Application($logger);

Yukarıdaki Application sınıfı Logger interface'inden bir nesneye ihtiyac duyuyor.

  • bir logger instance'i oluşturuyoruz
  • bir application instance'i oluşturuyoruz
  • application'a logger'i veriyoruz

Peki ConsoleLogger yerine FileLogger kullanmak istersek ? Kodu modifiye etmek durumunda kalacağız. İşte bu duruma düşmemek ve manuel sınıf çağrıları yapmamak için yapıcı metod üzerinden (constructor) bağımlılığı enjekte edebiliriz. (dependency injection) services.json üzerinde constructor injection aşağıdaki gibi yapılabilmektedir.

"services": [
    {
        "id": "application",
        "class": "\\App\\Application",
        "args": ["@console.logger"]
    },
    
    {
        "id": "console.logger",
        "class": "\\Logger\\ConsoleLogger"
    },
    
    {
        "id": "file.logger",
        "class": "\\Logger\\FileLogger"
    }
]

ContainerBuilder Application sınıfını oluştururken, args array'ini kontrol edecek ve @ ile başlayan @console.logger id'li bir diğer servisi arayacak, yok ise oluşturmaya çalışacaktır, oluşturduğu taktirde. application'a teslim edecektir. Eğer file.logger'a geçmek istersek, tek yapacağımız @console.logger yerine @file.logger yazmak olacaktır.

ÖNEMLİ NOT: Örneklerde de göreceğiniz üzere args'a verilen servis id'leri @ ile başlamaktadır.

Parametreye Bağımlı Servisler ve Parameter Injection

Yukarıdaki örnekte dikkatinizi bir noktaya çekmek istiyorum, ConsoleLogger'in constructor metodunda $name adında bir parametre var. Bu parametreyi services.json üzerinden vermemiz gayet tabi mümkündür;

{
    "parameters": {
        "logger.name": "logger:$APP_ENV"
    },
    "services": [
        {
            "id": "application",
            "class": "\\App\\Application",
            "args": ["@console.logger"]
        },
        
        {
            "id": "console.logger",
            "class": "\\Logger\\ConsoleLogger",
            "args": ["%logger.name%"]
        },
        
        {
            "id": "file.logger",
            "class": "\\Logger\\FileLogger"
        }
    ]
}

"args": ["%logger.name%"] satırı ile parameters'da tanımlı olan değeri servisimize aktaramamız mümkündür. $APP_ENV ile ilgili duruma Parametre Tanımı bölümünde değinmiştik.

Değerlerini Metodlar Aracılığı ile Alan Servisler ve Setter Injection

Her servis bağlı olduğu değerleri constructor üzerinden almalıdır diye bir durum sizinde tahmin ettiğiniz gibi olamaz. Servis instance'i oluşturulduktan sonra bir metod aracılığı ile injection yapabiliriz. Monolog buna en güzel örneği sunuyor, Monolog instance'i log writer'larini pushHandler metodu ile almakta. Bu durumu services.json üzerinden aşağıdaki gibi çözüyoruz.

{
  "parameters": {
    "logger.name": "app:$APP_ENV:logger",
    "log.file": "$APP_PATH/app/var/logs/$APP_ENV.log"
  },

  "services": [
    {
      "id": "logger",
      "class": "\\Monolog\\Logger",
      "args": ["%logger.name%"],
      "calls": [
        {"method": "pushHandler", "args": ["@logger.stream.handler"]}
      ]
    },
    {
      "id": "logger.stream.handler",
      "class": "\\Monolog\\Handler\\StreamHandler",
      "args": ["%log.file%", 200]
    }
  ]
}

Yukarıda görüldüğü gibi logger servisi constructor'da isime gerek duymakta bunu Parameter Injection ile sağlamaktayız. Ayrıca log yazmak için bir Handler'a ihtiyaç duymakta ve bu handler'lari pushHandler metodu ile alıyor.

İşte bu noktada bizde yukarıdaki gibi calls array'ini servis nesnemize ekliyoruz ve içine obje olarak method (zorunlu) ve args (zorunlu değil) keylerinden oluşan bir obje ekliyoruz. ContainerBuilder bu kısmı çözerek gerekli cağrıyı argümanlarla birlikte bizim için yapıyor. Bizim logger'a erişmek için yapmamız gereken tek şey tabiki;

<?php
// ContainerAware scope

getContainer()->get("logger"); // olacaktır :)

Factory Metodu Olan Servisler

Bazı servisler bir static metod üzerinden instance veriyor olabilir. Bunun için factory düğümünü kullanabiliriz. Buna en güzel örnek ise Doctrine DBAL servisidir.

{
  "parameters": {
    "dbal.dsn": "mysql://$DB_USER:$DB_PASS@$DB_HOST/$DB_NAME?charset=utf8",
  },
  "services": [
    {
      "id": "default.database.connection",
      "class": "\\Doctrine\\DBAL\\DriverManager",
      "factory": {
        "method": "getConnection",
        "args": [{"url": "%dbal.dsn%"}]
      }
    },
  ]
}

Yukarıdaki örnekte \\Doctrine\\DBAL\\DriverManager::getConnection metodu ContainerBuilder tarafından çağırılacak ve 1. argüman olarak bir array("url" => "dsn") gönderilecek.

Yeni Bir Action Oluşturmak

  • Öncelikle routes.php dosyasına url pattern'i gidecegi action ve metodu tanımlıyoruz. Örnek:
<?php
Routes::get("/", "Index@index"); // get ile / dizinine gelindiğinde Controller/IndexController::indexAction 'a gidecek
Routes::post("/save", "Posts@kaydet"); // post ile /save dizinine gelindiğinde Controller/PostsController::kaydetAction 'a gidecek
  • Controller class isimlerinde Controller suffix'ini metod isimlerinde ise Action suffixini kullanıyoruz. Aşağıda örnek bir controller tanım verilmiştir:
<?php
// file: app/Controller/IndexController.php
namespace Application\Controller;

use Library\AbstractController;
use Library\Http\JsonResponse;

class IndexController extends AbstractController 
{
    public function indexAction()
    {
        return new JsonResponse();
    }
}
  • Artık controller'a tanımladığımız url üzerinden erişebiliriz.

ÖNEMLİ: Action'lar AbstractResponse tipinde değerler dönmelidir.

Genel Bilgiler

  • Controller'lar üzerinden Repository'lere $this->getRepository("repoName") ile erişebilirsiz.
  • Controller üzerinden global $_GET, $_POST gibi değerlere $this->getRequest() ile erişebilirsiniz.
  • Controller scope'unda $this->getRequest()->query() $_GET'e erişebileceğiniz bir sınıf verir
  • Controller scope'unda $this->getRequest()->params() $_POST'e erişebileceğiniz bir sınıf verir
  • Controller scope'unda $this->getRequest()->server() $_SERVER'e erişebileceğiniz bir sınıf verir
  • Controller scope'unda $this->getRequest()->headers() header'lara erişebileceğiniz bir sınıf verir.
  • Tüm bu sınıflarin get, set ve all metodlari mevcuttur. Örneğin:
<?php
// Controller scope

$query = $this->getRequest()->query();

$query->get("page"); // url/?page=; eğer tanımlı ise değerini değil ise null dönecektir.

// ya da 

$query->get("page", 5); // page degeri tanımlı ise degerini değilse 5 degerini donecektir.

// ya da 

$queryArray = $query->all(); 
$queryArray["page"]; // bu şekilde kullanım undefined index hatasına neden olabilir. bunun önüne geçmek için empty/isset kontrolu yapılmalı ya da get metodu kullanılmalı.


// ayni kosullar params server ve headers içinde geçerlidir.
  • Veritabanı işlemlerini Repositoryler üzerinde yapmaya özen gösteriniz.
  • Birden fazla yerde kullanacağınız işlemler için Helper'ları kullanmaya özen gösteriniz.
  • Text işlemleri Http işlemleri yahut I/O işlemleri gibi ortak kullanılacak işlemler için Util kullanmaya özen gösteriniz. (TextUtil, HttpUtil gibi.)
  • app/lib altında ya da app/App.php üzerinde geliştirme yapmanız durumunda (önerilmez) bu kütüphaneleri kullanan bir çok yer etkilenecektir. Lütfen test yapmayı ihmal etmeyiniz.

Yapılacaklar

  • Şu anda sistem After/Before callback destekliyor. Yani bir route'a girmeden önce dilersek bir fonksiyon çıkarkende bir fonksiyon çalıştırabiliyoruz. Bunun multi olabilmesi lazım.
  • Error yapısı daha da geliştirilebilir.
  • Gelen datanın validasyonu zor oluyor. belki bir form validation sınıfı yazilabilir.
  • Şu an için cache provider yok, cache implementasyonu yapılmalı.
  • Parametrik route'ler şu an regex ile çalışıyor bunu etiket şeklinde yapabiliriz. ( şu an : user/(\w+) -> user/yunus , ileride user/:name -> assert(":name" => "\w+") olabilir )

About

Kendy is `I have my own framework` project


Languages

Language:PHP 100.0%