05.Symfony – Routing

05.Symfony – Routing

Symfony Routing e giriş yapmadan önce biraz geçmişe gidelim ve 5+1 (Anasayfa, Hakkımızda, Misyonumuz, Vizyonumuz, Ürünlerimiz yada Hizmetlerimiz ve İletişim olmak üzere ) sayfadan oluşan tamamen statik(HTML) bir site yaptığımızı düşünelim. Bu sayfalar arasında bağlantıyı Link ler ile yönterdik. Eskiden herşey basitti. Böyle basit bir site için de framework kullanmaya ihtiyaç yoktu, hepi topu 5 yada 6 sayfadan oluşan bir site. Zamanla şunu fark ettik, yukarıda saydığım bu 5+1 sayfanın büyük bir kısmı aynıydı, sadece belirli bir kısım değişiyordu. Örneğin Hakkımızda sayfasında, firma hakkında bilgi verirken, İletişim sayfasında ise, bilgi yerine iletişim formu koyuyorduk. Hal böyle olunca, tek bir sayfa yapıp, dinamik kısımları çağırmaya başladık. örneğin index.php ye gelince anasayfa, index.php?Page=Hakkimizda diyince hakkımızda sayfası, index.php?Page=Iletisim deyince de, iletişim sayfasını çağırıyorduk.

 

Tabi her şey ilerledi, müşterilerin, kullanıcıların bir siteden beklentileri değişti. Firma bir ürün satıyorsa, bu ürün ile ilgili Detay sayfası istiyordu, stok durumu, ürünün fiyatı, varsa renk seçenekleri, varsa ebat seçenekleri(Large,Small vs) öğrenmek istiyordu. Bu nedenle 5+1 klasik sayfalar pek işe yaramaz olmuştu. Firma 100 ürün satıyorsa, 100 ürün için birer sayfa yapmak zorundaydık. Sonra ürünlerin kategorisi olabiliyordu. Birde kategorilerle uğraşmaya başlamıştık. 100 tane ürünün olduğu bir siteyi tamamen statik yapıp yönetmek çok zordur. Hele hele 1.000 ürün, yada 5.000 ürün olduğunu düşünsenize …

 

Böylece konumuza basit bir giriş yaptık. Genelde ortalama bir projeye başladığımız da Routing (Yönlendirme) yapısına ihtiyaç duyarız. Ortalama kelimesini özellikle seçtim çünkü kurumsal bir uygulama geliştirecekseniz ya da ciddi bir proje geliştirecekseniz “ihtiyaç” artık “Zorunluluk” olur. Sözün özü, ortalama, kurumsal yada ciddi bir proje geliştirecekseniz, Routing sizin için bir zorunluluktur. Routing yapısını neden kullanmalıyız sorusunun birinci cevabını yukarıda yazdım. Sitemizde ki bağlantıları Yönetme. Peki başka neden yok mu? Var …

 

Diğer nedenlerden birisi de şu: Güzel-Anlamlı ve SEO için faydalı URL ler üretmek için kullanırız. Örneğin index.php?article_id=57 yerine symfony/symfony-generating-a-new-bundle-skeleton/ daha okunabilir, daha anlaşılabilir ve SEO için daha faydalı. Başka? Aslında bu liste uzatılabilir, en genel anlamda yukarıda ki iki cevap neden kullanmamız gerektiğini anlatsa da, illa başka bir cevap isterseniz, Symfony Routing ile Method larıda( GET,POST,PUT,DELETE vs) yönetebilirsiniz.

 

Artık neden Symfony Routing yapısına ihtiyacımız olduğunu anladığınızı düşünüyorum. Her şey ihtiyaçtan doğar, eğer neden ihtiyacınız olduğunu anlarsanız, bu konunun neden önemli olduğunu da kavrarsınız. Eskiden linux sunucularda güzel URL leri yaratmak için, .htaccess dosyalarını kullanırdık. Yeni nesil Framework lerde bu değişiyor. Artık .htaccess ile yönetmektense yeni nesil Framework ler(Laravel, Symfony vb) PHP ile kendileri yönetiyor. Symfony Routing her ne kadar Symfony için geliştirilse de, hem esnek, hem de güçlü ve kolay olduğu için bir çok framework ( Örneğin Laravel gibi) bu eklentiyi kullanır. Bunun dışında kendi yazdığınız projelerinize de dahil edebilirsiniz, yapmanız gereken bu eklentiyi composer yardımıyla kurmak hepsi bu. (Eskiden basit işlemler için .htaccess dosyamızdan basit yönlendirmeler yapıyorduk. Şöyle gelirse buraya, böyle gelirse şu dosyaya git gibi düşünebiliriz. Şimdi ise yapılan şu: Sen gelen bütün istekleri belirtilen dosyaya ki symfony için bu public dizininde ki index.php dir. Gönder. Gelen herşeyi index.php e gönder, gerisine sen karışma…)

 

Symfony – Routing

Genelden, özele gidelim. Genel anlamda artık bir yönlendirme yapısına neden ihtiyacımız olduğunu biliyoruz. Peki Symfony de routing tam olarak ne işe yarıyor?

 
1. Basit yada Karmaşık URL ler oluşturup, bunları Controller dosyalarımıza bağlamak için
2. Controller dosyalarımızda yada template dosyalarımızda ihtiyacımız varsa URL ler oluşturmak için.
3. Oluşturduğumuz Bundlemızın dışında Harici kaynaklardan faydalanmak için
4. Debug/Test işlemlerimiz için

 

Genel anlamda Routing yapısı ve özel de ise Symfony Routing yapısını artık biliyorsunuz. Can alıcı soruya geldik. Tamam ama nasıl ? Symfony Routing leri nasıl kullanırım, nasıl yönetirim. Cevap: annotation, YAML, XML ve PHP olmak üzere 4 şekilde yönetebilirsiniz. Hatta bunların bir kaçını aynı anda da kullana bilirsiniz. Bunların her birini bir örnekle göstereceğim ama önce işin teknik kısmına inelim. Yukarıda bahsetmiştim, modern frameworkler artık url leri oluştururken web sunucusundan(Apache, Nginx) yetki alır ve url oluşturmayı PHP dili ile yaparlar dedim. Şimdi bunun nasıl olduğuna bakalım. Symfony nin resmi sitesine giderseniz, size hem apache hem de nginx için önerdiği ayarları görürsünüz. Nginx i bir kenara bırakıp Apache üzerinden gidersek, Apache ayarlarında şöyle bir satır var.

 

<IfModule mod_rewrite.c>
	Options -MultiViews
	RewriteEngine On
	RewriteCond %{REQUEST_FILENAME} !-f
	RewriteRule ^(.*)$ index.php [QSA,L]
</IfModule>

Yukarıda ki koddan 2 anlam çıkarabilirsiniz. Birincisi ve en önemlisi, eğer sunucunuz apache ise mod_rewirte aktif olmalı. Zaten kod da öyle yazılmış. Eğer mod_rewrite aktif ise IfModule direktifinin içi geçerli olacak. İçinde ise ikinci kısım var. Tüm istekleri index.php dosyasına yönlendir ve index.php ne yapacağına karar versin. (Symfony Resmi Site: Configuring a Web Server)

Böylece bu minik ama önemli ayrıntıyı geçtik. Sitem(BenveAlem ) için örnek verip annotation, YAML, XML ve PHP Routing i bu örnekle açıklayacağım. Sitem de yukarıda ki linklere bakarsanız, Blog diye bir bağlantı var. İçerisine girerseniz bir kaç minik yazı/post görürsünüz, örneğin https://www.benvealem.com/blog/yonetim-bunalimi/ gibi. Domain kısmını atarsak, /blog/yonetim-bunalimi/ mını annotation, YAML, XML ve PHP ile yapacak olsaydık(Her bir örneği görüntülemek için üzerine tıklayın);


Annotation

// Blog ile ilgili Kontroller Dosyaniz
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Routing\Annotation\Route;

class BlogController extends Controller
{
    /**
    * @Route("/blog/", name="blog_list")
    */
    public function list()
    {
    // Tum yazilari listele
    }

    /**
    * @Route("/blog/{slug}", name="blog_show")
    */
    public function show($slug)
    {
    // $slug url de ki dinamik kisim.
    // Örneğimiz de ise yonetim-bunalimi
    // /blog/yonetim-bunalimi sayfasi cagrildiginda bu kisima gelecek.
    }
}

# app/config/routing.yml
blog_list:
    path:     /blog/
    controller: App\Controller\BlogController::list

blog_show:
    path:     /blog/{slug}
    controller: App\Controller\BlogController::show

<!-- config/routes.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<routes xmlns="http://symfony.com/schema/routing"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/routing
        http://symfony.com/schema/routing/routing-1.0.xsd">

    <route id="blog_list" controller="App\Controller\BlogController::list" path="/blog/" >
        <!-- settings -->
    </route>

    <route id="blog_show" controller="App\Controller\BlogController::show" path="/blog/{slug}">
        <!-- settings -->
    </route>
</routes>

// config/routes.php
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
use App\Controller\BlogController;

$routes = new RouteCollection();
$routes->add('blog_list', new Route('/blog/', array(
    '_controller' => [BlogController::class, 'list']
)));
$routes->add('blog_show', new Route('/blog/{slug}', array(
    '_controller' => [BlogController::class, 'show']
)));

return $routes;


 

Yukarıda ki dört örneği inceleyin lütfen. Fark ettiyseniz ısrarla annotation vurguluyorum. Siz hangisini beğenirsiniz bilmiyorum ama ben annotation i anlatacağım. Bu kısımda ufak bir notta eklesem sanırım faydalı olur. Eğer projenizde çok fazla link var ise XML seçeneğini de değerlendirin derim. Yukarıda ki annotation kısmını biraz açacak olursak; https://www.benvealem.com/blog/ adresine gelinir ise list, https://www.benvealem.com/blog/yonetim-bunalimi/ adresine gelinir ise show($slug) eşleştirilecek. Burada $slug dinamik kısım, herşey olabilir. daha doğrusu her post/yazı yada sayfa için özel(unique) olabilir. Hatırlayın lütfen, yukarıda hem Genel hem de Özel de Symfony de Routing yapısını anlattım ve Özelde şunu yazdım. Symfony Routing in amacı oluşturulan URL leri bir Controller dosyası ile eşleştirmek(Mapping). Şuanda yaptığı da bu. blog diye gelirse list, blog/birsey-birseyler diye gelirse, show ile eşleştirecek.

Örneğimizi geliştirerek devam edelim. Diyelim ki blog sayfanızda 100 tane yazı var. Ve siz her bir sayfa da 10 yazı göstermek istiyorsunuz yani pagination(Sayfalama). Ve şöyle birşey yapmak istiyorsunuz /blog/{page}. Daha da açarsak /blog/1 ilk 10 yazıyı /blog/2 ikici 10 yazıyı, toplam da ise her sayfada 10 yazı gösterecek bir yapı kurmak istiyorsunuz. Hemen BlogController dosyanıza gittiniz ve aşağıda ki gibi bir yapı oluşturdunuz.


namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Routing\Annotation\Route;

class BlogController extends Controller
{
    /**
    * @Route("/blog/{page}", name="blog_list")
    */
    public function list($page)
    {
    // Veritabanina git, sorgu yap, $page e gore, sayfala/filtrele
    }

    /**
    * @Route("/blog/{slug}", name="blog_show")
    */
    public function show($slug)
    {
    // Ilgili yaziyi bul, detaylari goster
    }
}

Yukarıda ki kodu DİKKATLİCE inceleyin, Böyle bir kod yazsaydık, yazdığımız kod istediğimiz gibi çalışır mıydı ? URL ler aynı, bir önceki örneğimiz de blog/yonetim-bunalimi/ yazdığımız da, show çağırılıyordu. Şu soruyu kendinize sorun ve kodu inceleyin.

 

1. Site adresine blog/1 yazsaydınız, list mu çalışırdı, yoksa show mu ?
2. Site adresine blog/yonetim-bunalimi yazsaydınız, list mu çalışırdı, yoksa show mu ?

 

Symfony Routing de bazı kurallar var, Controller dosyasında ilk eşleştirdiği fonksiyon hangisi ise ona bakar. Bu yapıya göre, list u bulur onu çağırırdı. Yukarıda ki birinci sorunun yanıtı, list, çünkü o kodda daha yukarıda, İkincisi sorunun cevabı ise list. Ayrıca show bu yapıya göre asla çalışmazdı. Siz blog/yonetim-bunalimi yazsanız da, list a giderdi, sorgu yapardı, ya hata üretirdi, yada istenilen bilgiyi veritabanından düzgün çekemezdi, daha da kötüsü, hiçbir yazınızın içeriğine/detayına ulaşamazdınız çünkü show çağrılmazdı. Bu örnekte, Controller dosyanızda ki sıralamanın Symfony Routing de ÖNEMLİ olduğunu öğrendiniz aynı zaman da yukarıda yazdığınız kodun hatalı olduğunu da. Peki doğrusu nedir derseniz; annotation + regular expression. Aşağıda ki örneği inceleyin …


namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Routing\Annotation\Route;

class BlogController extends Controller
{
    /**
    * @Route("/blog/{page}", name="blog_list" , requirements={"page": "\d+"})
    */
    public function list($page)
    {
    // Veritabanina git, sorgu yap, $page e gore, sayfala/filtrele
    }

    /**
    * @Route("/blog/{slug}", name="blog_show")
    */
    public function show($slug)
    {
    // Ilgili yaziyi bul, detaylari goster
    }
}

 

Şu satıra dikkat : @Route(“/blog/{page}”, name=”blog_list” , requirements={“page”: “\d+”}) burada ki \d+ ibaresi, sadece rakamlarla eşleşirse geçerlidir. Yani blog/1 yada blog/11 gelir ise, blog/ tan sonra rakam varsa, list, yoksa, show çalışacak.

 

Peki bu kod tam olarak doğru mu ? Lütfen kodu dikkatlice inceleyin, ve şu 2 soruyu sorun.

 

1. blog/1 yazsaydım ne olurdu ?
2. blog/8 yazsaydım ne olurdu ?
3. blog/ yazsaydım ne olurdu ?

 

Kurgumuza göre blog/1 yazsaydınız, list site ilk 10 kayıdı getirirdi, bu birinci sorunun cevabı, yani sorun yok.

Gelelim ikinci soruya, blog/8 yazsaydınız, hatırlarsanız 100 tane yazımız/postumuz vardı, 80-90 aralığını getirir di(90 hariç), yani yine sorun yok.

Ama üçüncü soruda patlardık. Olması gereken şu: blog yazarsa ilk 10 kaydı getir, blog/1 yazarsa ilk 10 kaydı getir. Oysa bizim kodumuz bu haliyle blog yazarsak patlar. Çünkü hiç Default değer yok. Böylece Symfony Routing de Default değerlere geldik. Aşağıda ki kodu inceleyin …

 


namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Routing\Annotation\Route;

class BlogController extends Controller
{
    /**
    * @Route("/blog/{page}", name="blog_list" , requirements={"page": "\d+"})
    */
    public function list($page = 1)
    {
    // Veritabanina git, sorgu yap, $page e gore, sayfala/filtrele
    }

    /**
    * @Route("/blog/{slug}", name="blog_show")
    */
    public function show($slug)
    {
    // Ilgili yaziyi bul, detaylari goster
    }
}

 

Şu satıra dikkat, public function list($page = 1), böylece eğer bir kişi direk blog yazar ise, $page değişkeninin varsayılan değeri 1 olacak ve ilk 10 kaydı getirecek. Aşağıda ki örnek ise daha kapsamlı, önce örnek kodu yazalım, sonra beraber inceleyelim.



// ...
class ArticleController extends Controller
{
    /**
    * @Route(
    * "/articles/{_locale}/{year}/{slug}.{_format}",
    * defaults={"_format": "html"},
    * requirements={
    * "_locale": "en|tr",
    * "_format": "html|rss",
    * "year": "\d+"
    * }
    * )
    */
    public function show($_locale, $year, $slug)
    {
    }
}

 

Öncelikle url ye bakın. URL Şöyle olacak http://www.benvealem.com/articles/Locale/Year/Slug.FORMAT bu yapıya örnek verirsek http://www.benvealem.com/articles/en/2013/my-latest-post.html. Artık URL yi belirledik şimdi requirements kısmına bakınız. Örnek te 3 zorunluluk var.

 

1. Dil ya en yada tr olacak,
2. Yıl rakam olacak
3. Formatta, ya html yada rss olacak.

 

Örnek te 3 zorunluluk olmasına karşın aslında 2 zorunluluk var. Çünkü varsayılan format defaults={“_format”: “html”}, satırda atanmış. Hiç birşey girmez isek, yada aksini belirtmez isek HTML olacak. Aksi için ise bir durum var oda .rss;

— Site adresine /articles/en/2010/my-post yazarsanız, formati HTML olur.
— Site adresine /articles/en/2010/my-post.html yazarsanız, formati HTML olur.
— Site adresine /articles/en/2010/my-post.rss yazarsanız, formati RSS olur.
— Site adresine /articles/en/2010/my-post.XML yazarsanız, şuan ki yapıda XML çalışmaz. Format ya HTML olur yada RSS, bunlardan farklı ise, ilk yazılan(HTML) geçerli olur.
 

Bu örnek ise güzel bir örnek, içerisinde @Method da var.


namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Routing\Annotation\Route;

class BlogController extends Controller
{
    /**
    * @Route("/blog/{id}", name="blog_show")
    * @Method("GET")
    */
    public function show($id)
    {
    // Ilgili yaziyi bul, detaylari goster
    }

    /**
    * Deletes a blog entity.
    *
    * @Route("/blog/{id}", name="blog_delete")
    * @Method("DELETE")
    */
    public function delete($id)
    {
    // Delete this sucker then return
    //return $someting;
    }
}

Kıyaslama yapabilmemiz için bu örneğimiz de show ve delete olmak üzere iki tane action var. Yukarıda ki örneğe göre şu soruyu sorun, tarayıcımızı açtık, ve url kısmına https://www.benvealem.com/blog/2 yazdığımız da hangi action çalışırdı ? Cevap bunu kullandığımız Method belirlerdi. Tarayıcımızı açıp url kısmına https://www.benvealem.com/blog/2 yazıp, enter tuşuna bassaydık, burada Method GET olurdu ve show çalışırdı. Henüz bilmiyor olabilirsiniz, önemli de değil. Daha sonra Method larla çalışacağız, ve DELETE methodunu nasıl kullanacağımızı göreceğiz. Amacımız dışına çıkmadan özetlersek, kullandığımız Method DELETE ise, delete, kullandığımız Method GET ise show çalışacaktı.

 

Symfony Routing and _prefix

Son örneğimiz de şu, dikkat ederseniz, biz hep annotation method u ile Symfony Routing işlemlerini yaptık. Şuana kadar ki her örneğimiz de ise, Fonksiyonların başına rota tanımlarımızı yaptık. Peki class ın başına yapsaydık ne olurdu ? Cevap basit, class ın içerisinde ki her rota tanımının önüne bu gelirdi ve hepsi için geçerli olurdu.

 

Bu örnek ise güzel bir örnek, hem içerisinde @Method var hem de class için rota tanımlaması var. Örneğimiz şu, diyelim ki yazarlar diye bir controller yaptık. Ve bu kontrol aşağıda ki işlemleri yapabiliyor olsun;

 

— Yazarları listele
— Yeni yazar ekle
— Güncelle
— Yada bir yazarı sil.

 

Esaslı dememin nedeni ise, aslında klasik bir yapıda bunların hepsi var, Listele, Güncelle, Yeni Oluştur ve Sil. Aşağıda ki kodu dikkatlice inceleyin;

 

<?php

namespace App\Controller;

use App\Entity\Author;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\Request;

/**
* Author controller.
*
* @Route("/author")
*/
class AuthorController extends Controller
{
    /**
    * Lists all author entities.
    *
    * @Route("/", name="author_index")
    * @Method("GET")
    */
    public function index()
    {
    //return $someting;
    }

    /**
    * Creates a new author entity.
    *
    * @Route("/new", name="author_new")
    * @Method({"GET", "POST"})
    */
    public function new(Request $request)
    {
    //return $someting;
    }

    /**
    * Finds and displays a author entity.
    *
    * @Route("/{id}", name="author_show")
    * @Method("GET")
    */
    public function show(Author $author)
    {
    //return $someting;
    }

    /**
    * Displays a form to edit an existing author entity.
    *
    * @Route("/{id}/edit", name="author_edit")
    * @Method({"GET", "POST"})
    */
    public function edit(Request $request, Author $author)
    {
    //return $someting;
    }

    /**
    * Deletes a author entity.
    *
    * @Route("/{id}", name="author_delete")
    * @Method("DELETE")
    */
    public function delete(Request $request, Author $author)
    {
    //return $someting;
    }

    /**
    * Creates a form to delete a author entity.
    *
    * @param Author $author The author entity
    *
    * @return \Symfony\Component\Form\Form The form
    */
    private function createDeleteForm(Author $author)
    {
    //return $someting;
    }
}

Şu kısma özellikle dikkat edin;

/**
 * Author controller.
 *
 * @Route("/author")
 */
class AuthorController extends Controller

Yukarıda ki kod nedeniyle, bu class ın içerisinde ki tüm fonksiyonlar yada action lar bundan etkilenirdi, Bu fonksiyonlar (index,new, show, edit, delete) önüne author kelimesini alırdı. Örnekle açıklarsak

    /**
    * Lists all author entities.
    *
    * @Route("/", name="author_index")
    * @Method("GET")
    */
    public function index()
    {
    //return $someting;
    }

burada index sanki / gibi görünüyor yani https://www.benvealem.com/ gibi ama değil. Class ın başında prefix var. Bunun anlamı şu https://www.benvealem.com/author çağrılırsa, index ile eşleşecek. Aynısı diğer fonksiyonlar içinde geçerli, örneğin yeni bir yazar eklememizi sağlayan new a bakalım,

    /**
    * Creates a new author entity.
    *
    * @Route("/new", name="author_new")
    * @Method({"GET", "POST"})
    */
    public function new(Request $request)
    {
    //return $someting;
    }

burada new sanki /new gibi görünüyor yani https://www.benvealem.com/new gibi ama değil. Class ın başında prefix var. Bunun anlamı şu https://www.benvealem.com/author/new çağrılırsa, new ile eşleşecek. Yukarı da belirttiğim gibi, class a rota/prefix tanımlarsanız , ki tanımlayın derim, bu ayar class in içerisinde ki tüm fonksiyonlar için geçerli olur.

Symfony Routing

Böylece Symfony Routing i bitirmiş olduk. Rota tanımları önemli değil ÇOK ÖNEMLİ, Symfony kullanacaksanız Symfony Routing sizin için çok önemli. Elimden geldiğince size bu konuyu önemli olduğu için örneklerle açıklamaya çalıştım. Umarım sizin için faydalı olmuştur. Anlamadığınız yerler olursa, yada zorlandığınız yerler, sorularınızı yorum olarak bırakırsanız, elimden geldiğince sizlere yardımcı olmaya çalışırım. Bir sonra ki yazım, şuana kadar yazdığım tüm Symfony yazılarının özeti olacak ve bir adet te Video olacak. Görsel olarakta açıklayacağım. Bir sonra ki yazımda görüşmek dileğiyle.