Symfony Framework

Symfony Çalışma Sistemi

Düz PHP ile basit bir web sayfası

Aşağıdaki kodlarla, bir veritabanına bağlanılarak, bir dosyadan iki farklı alan değeri alınarak ekrana yazılmaktadır.

<?php 
// index.php
  $link = new PDO("mysql:host=localhost;dbname=blog_db", 'myuser', 'mypassword');
  $result = $link->query('SELECT id, title FROM post');
?> 
<!DOCTYPE html>
<html>
    <head>
      <title>List of Posts</title>
    </head>
    <body>
        <h1>List of Posts</h1>
        <ul>
            <?php while ($row = $result->fetch(PDO::FETCH_ASSOC)): ?> 
            <li>
                <a href="/show.php?id=<?php = $row['id'] ?> ">
                    <?php= $row['title'] ?> 
                </a>
            </li>
            <?php endwhile ?> 
        </ul>
    </body>
</html>

<?php 
  $link = null ;
?> 

Tek bir dosya içinde yer alan yukarıdaki kodlar oldukça hızlı bir şekilde yazılabilir ve uygulama oldukça hızlı bir şekilde çalışabilir. Ancak, uygulamanız büyüdükçe, aynı avantajları sağlamak mümkün olmayabilir. Ayrıca, tüm kodların aynı dosyada yer aldığı bir uygulamada bazı sorunlar ortaya çıkabilir.

  1. Hata kontrolü sorunu: Örneğin, veritabanı bağlantısında sorun yaşanırsa ne yapılacağı konusunda bir açıklık yoktur.
  2. Yapılandırma sorunu: Uygulama büyüdükçe, yukarıdaki tek bir dosya büyüdükçe kullanımı kolay olmayacaktır.
  3. Kodları yeniden kullanma zorluğu: Bütün kodlar tek bir dosyada bulunduğundan, web sitesinin diğer sayfalarında uygulama kodlarının herhangi bir parçası tekrar kullanılamaz.

Web sayfasını ekranda gösteren kodları ayırma

Web sitesinin kodunda, uygulamanın mantıksal kısmı ile HTML sayfasını hazırlayan kısmı birbirinden ayırmak oldukça büyük bir avantaj sağlayacaktır.

<?php 
  // index.php
  $link = new PDO("mysql:host=localhost;dbname=blog_db", 'myuser', 'mypassword');

  $result = $link->query('SELECT id, title FROM post');
  
  $posts = array();
  while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
     $posts[] = $row;
  }

  $link = null;

  // HTML kodunu dahil etme
  require 'templates/list.php';
?> 

HTML sayfasını hazırlayarak web sayfasını ekranda gösteren kod artık "templates/list.php" adlı ayrı bir HTML dosyada yer almaktadır.

<!-- templates/list.php -->
<!DOCTYPE html>
<html>
    <head>
      <title>List of Posts</title>
    </head>
    <body>
        <h1>List of Posts</h1>
        <ul>
            <?php foreach ($posts as $post): ?> 
            <li>
                <a href="/show.php?id=<?= $post['id'] ?> ">
                    <?php= $post['title'] ?> 
                </a>
            </li>
            <?php endforeach ?> 
        </ul>
    </body>
</html>

Mevcut duruma göre, tüm uygulama mantığını içeren dosya - index.php - "denetleyici (controller)" olarak bilinir. Controller, kullanıcı girişine işlem yapan ve karşılığında gerekli işlemleri yapan kodları ifade eder.

Bu durumda, controller veritabanından verileri alarak hazırlar ve daha sonra bu verileri kullanıcıya sunmak için bir şablon kullanır. Conroller'ı hiç dikkate almadan, sadece HTML şablon dosyasını değiştirerek verileri farklı bir şekilde ekrana yansıtabilirsiniz.

Uygulama (Domain) mantığının kodlarını ayırma

Uygulama halen tek bir sayfa içermektedir. İkinci bir sayfa aynı veritabanı bağlantısını veya aynı dizi değerlerini kullanması gerektiğinde, mevcut kodlar yeniden düzenlenerek, uygulamanın temel kodları ve veri giriş fonksiyonları model.php adlı bir dosyaya kaydedilerek ayrılır.

<?php 
    // model.php
    function open_database_connection()
    {
        $link = new PDO("mysql:host=localhost;dbname=blog_db", 'myuser', 'mypassword');

        return $link;
    }

    function close_database_connection(&$link)
    {
        $link = null;
    }

    function get_all_posts()
    {
        $link = open_database_connection();

        $result = $link->query('SELECT id, title FROM post');

        $posts = array();
        while ($row = $result->fetch(PDO::FETCH_ASSOC)) {
            $posts[] = $row;
        }
        close_database_connection($link);

        return $posts;
    }
?> 

Artık, controller (index.php) çok basit bir yapıya kavuşur.

<?php 
  // index.php
  require_once 'model.php';

  $posts = get_all_posts();

  require 'templates/list.php';
?> 

Bu durumda, controller'ın tek görevi, uygulamanın model katmanından (model) veri almak ve bu verileri oluşturmak için bir şablon çağırmaktır. Bu, model-view-controller modelinin çok basit bir örneğidir.

Web sayfasını düzenleyen kodları ayırma

Mevcut durumda, uygulamayı hemen her şeyi farklı sayfalarda yeniden kullanma olanağı sağlayacak, çeşitli avantajlar ve fırsatlar ortaya çıkaran üç ayrı bölüme ayırarak yeniden yapılandırdık. Uygulama kodlarının yeniden kullanılamayacak tek bölümü sayfa düzenini sağlayan kodlardır. Bu sorunu da "templates/layout.php" adlı bir dosya oluşturarak çözebiliriz.

<!-- templates/layout.php -->
<!DOCTYPE html>
<html>
    <head>
        <title><?php = $title ?> </title>
    </head>
    <body>
        <?php = $content ?> 
    </body>
</html>

templates/list.php şablonu templates/layout.php sayfa düzenini içerecek şekilde basitleştirebiliriz.

<!-- templates/list.php -->
<?php  $title = 'List of Posts' ?> 

<?php ob_start() ?> 
    <h1>List of Posts</h1>
    <ul>
        <?php foreach ($posts as $post): ?> 
        <li>
            <a href="/show.php?id=<?php = $post['id'] ?> ">
                <?php = $post['title'] ?> 
            </a>
        </li>
        <?php endforeach ?> 
    </ul>
<?php $content = ob_get_clean() ?> 

<?php include 'layout.php' ?> 

Artık, sayfa düzenini yeniden kullanabilecek şekilde bir sistem oluşturmuş olduk.

Bir blog sayfası gösteren sayfa ekleme

Blog listeleme sayfası yeniden düzenlenerek, kodları daha iyi bir şekilde düzenledik ve tekrar kullanılabilir hale getirdik. Kodları geliştirmek için, id parametresini kullanarak tek bir blog sayfasını gösteren bir blog gösterme sayfası ekleyelim.

Bu amaçla, model.php dosyasında, verilen id değerini kullanarak tek bir blog sayfasını dosyadan okuyan alan yeni bir fonksiyon oluşturalım.

// model.php
function get_post_by_id($id)
{
  $link = open_database_connection();

  $query = 'SELECT created_at, title, body FROM post WHERE  id=:id';
  $statement = $link->prepare($query);
  $statement->bindValue(':id', $id, PDO::PARAM_INT);
  $statement->execute();

  $row = $statement->fetch(PDO::FETCH_ASSOC);

  close_database_connection($link);

  return $row;
}

Sonra, yeni sayfa için conroller işlemini yapacak show.php adlı yeni bir dosya oluşturualım.

// show.php
  require_once 'model.php';

  $post = get_post_by_id($_GET['id']);

  require 'templates/show.php';

Son olarak, tek bir blog sayfasını ekranda gösterecek olan templates/show.php adlı yeni bir şablon dosyası oluşturalım.

<!-- templates/show.php -->
<?php $title = $post['title'] ?> 

<?php ob_start() ?> 
    <h1><?php = $post['title'] ?> </h1>

    <div class="date"><?php = $post['created_at'] ?> </div>
    <div class="body">
        <?php = $post['body'] ?> 
    </div>
<?php $content = ob_get_clean() ?> 

<?php include 'model.php''layout.php' ?> 

İkinci sayfayı çok kolay bir şekilde oluşturduk ve tekrar bir kod yazmak zorunda kalmadık. Ancak halen, bu sayfada Framework tarafından çözülebilecek sorunlar devam etmektedir.Örneğin, olmayan veya geçersiz bir id sorgu parametresi sayfanın kilitlenmesine neden olacağından, bir 404 sayfanın oluşturulması sağlansaydı daha iyi olurdu, ancak bu henüz gerçekten kolaylıkla yapılamaz.

Bir diğer önemli sorun, her controller dosyasının model.php dosyasını içerme zorunluluğudur.

Ön Denetleyici (Front Controller) kullanımı

Çözüm, bir ön denetleyici kullanmaktır. Tüm taleplerin işlem yapıldığı tek bir PHP dosyası. Bir ön denetleyiciyle, uygulama URI'leri biraz değişiklik gösterir, ancak daha esnek hale gelir.

Ön denetleyici olmadan
/index.php          => Blog gönderi listeleme sayfası (index.php çalıştırılır)
/show.php           => Blog gönderi gösterme sayfası (show.php çalıştırılır)

With index.php as the front controller
/index.php          => Blog gönderi listeleme sayfası (index.php çalıştırılır)
/index.php/show     => Blog gönderi gösterme sayfası (index.php çalıştırılır)

Web sunucu konfigürasyonundaki yeniden yazma kurallarını kullanarak, index.php ifadesini kullanmadan temiz URL ler (e.g. /show) elde edebiliriz.

Ön denetleyici kullandığınızda, tek bir PHP dosyası (burada index.php) tüm taleplere işlem yapar. Blog gösterme sayfası olan /index.php/show aslında tam URL tanımlamasına bağlı olarak talepleri yönlendiren index.php dosyasını çalıştırır.

Ön Denetleyici oluşturma

Şimdi yapacağımız işlem ile, tüm isteklere işlem yapan tek bir dosya ile güvenlik yönetimi, yapılandırma yükleme ve yönlendirme (routing) gibi işlemleri merkezi hale getirebilirsiniz. Bu uygulamada, index.php, artık blog gönderi listesi sayfasını veya blog yazısı gösterme sayfasını talep edilen URI değerine göre oluşturacak kadar iyi düzenlenmiş olmalıdır.

// index.php
// global kütüphaneleri yükleme ve başlatma
require_once 'model.php';
require_once 'controllers.php';

// isteği dahili olarak yönlendirme
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
if ('/index.php' === $uri) {
    list_action();
}
elseif ('/index.php/show' === $uri && isset($_GET['id'])) {
    show_action($_GET['id']);
}
else {
    header('HTTP/1.1 404 Not Found');
    echo '<html><body><h1>Page Not Found</h1></body></html>';
}

Düzenleme için, her iki denetleyici (önceki index.php ve show.php) şimdi birer PHP fonksiyonu olarak controllers.php adlı ayrı bir dosyaya aktarılır.

// controllers.php
function list_action()
{
  $posts = get_all_posts();
  require 'templates/list.php';
}

function show_action($id)
{
  $post = get_post_by_id($id);
  require 'templates/show.php'';
}

Ön denetleyici olarak index.php, çekirdek kitaplıkların yüklenmesi ve uygulamanın yönlendirilerek iki denetleyiciden (list_action() ve show_action() fonksiyonları) birinin çağrılmasını içeren tamamıyla yeni bir rol üstlenir. Aslında, ön denetleyici, Symfony'nin isteklere işlem yaptığı ve yönlendirdiği gibi görmeye ve harekete etmeye başlamıştır.

Ancak ön kontrolör ve kontrolör terimlerini birbirine karıştırmamaya özen gösterin. Uygulamanızın genelde kodunu çalıştıran sadece tek bir ön denetleyicisi olacaktır. Her sayfa için bir tane olmak üzere, birçok denetleyici fonksiyon bulunacaktır.

Bir ön denetleyicisinin diğer bir avantajı esnek URL'lerdir. Blog gösterme sayfasının URL'sini, yalnızca bir konumdaki kodu değiştirerek /show yerine /read şeklinde değiştirilebilir. Önceden bir dosyanın adının tamamının değiştirilmesi gerekiyordu. Symfony'de URL'ler daha esnektir.

Şimdiye kadar, uygulama tek bir PHP dosyasından organize edilmiş ve kodun yeniden kullanılmasına izin veren bir yapıya dönüştü. Ancak, durum beklentileri karşılmaktan oldukça uzak.

Symfony'de basit bir uygulama

Yukarıdaki kodlarla hazırladığımız yapısal olarak oldukça düzgün bir yapıya kavuşmasına rağmen, hala böyle basit bir uygulama için çok fazla kod içeriyor. Basit bir yönlendirme sistemi ve şablon oluşturmak için ob_start() ve ob_get_clean() fonksiyonlarını kullanarak sistemi geliştirmeye çalıştık. Herhangi bir nedenle, bu Framwork'ü sıfırdan oluşturmak isterseniz, en azından bu sorunları zaten çözen Symfony'nin bağımsız Yönlendirme ve Şablonlama bileşenlerini kullanabilirsiniz.

Ortak problemleri tekrar çözmek yerine, Symfony'nin sizin yerinize bu işlemleri yapmasını sağlayabilirsiniz. Artık, Symfony ile oluşturulmuş basit bir uygulamaya başlayabiliriz.

// src/AppBundle/Controller/BlogController.php
namespace AppBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;

class BlogController extends Controller
{
    public function listAction()
    {
        $posts = $this->get('doctrine')
            ->getManager()
            ->createQuery('SELECT p FROM AppBundle:Post p')
            ->execute();

        return $this->render('Blog/list.html.php', array('posts' => $posts));
    }

    public function showAction($id)
    {
        $post = $this->get('doctrine')
            ->getManager()
            ->getRepository('AppBundle:Post')
            ->find($id);

        if (!$post) {
            // cause the 404 page not found to be displayed
            throw $this->createNotFoundException();
        }

        return $this->render('Blog/show.html.php', array('post' => $post));
    }
}

Dikkat ederseniz, her iki denetleyici fonksiyonu şimdi bir "denetleyici sınıfı" içinde yer almaktadır. Bu yöntem, ilgili sayfaları gruplamak için iyi bir yoldur. Denetleyici fonksiyonlara bazen eylemler adı verilir.

İki denetleyicinin (veya eylemler) her biri, veritabanından nesneleri almak için Doctrine ORM kitaplığını ve şablon oluşturmak ve bir Response nesnesi döndürmek için Templating bileşenini kullanır. Şimdi, list.php şablonu biraz daha basit bir yapıdadır:

<!-- app/Resources/views/Blog/list.html.php -->
<?php $view->extend('layout.html.php') ?> 

<?php $view['slots']->;set('title', 'List of Posts') ?> 

<h1>List of Posts</h1>
<ul>
    <?php foreach ($posts as $post): ?> 
    <li>
        <a href="<?php echo $view['router']->path(
            'blog_show',
            array('id' => $post->getId())
        ) ?> ">
            <?= $post->getTitle() ?> 
        </a>
    </li>
    <?php endforeach ?> 
</ul>

layout.php dosyası neredeyse aynıdır:

<!-- app/Resources/views/layout.html.php -->
<!DOCTYPE html>
<html>
    <head>
        <title><?php = $view['slots']->output(
            'title',
            'Default title'
        ) ?> </title>
    </head>
    <body>
        <?php = $view['slots']->output('_content') ?> 
    </body>
</html>

Symfony motoru (Çekirdek olarak adlandırılır) açıldığında, hangi denetleyicilerin istek bilgisine dayalı olarak çalıştırılacağını bilmesi için bir haritaya ihtiyaç duyar. Bir yönlendirme yapılandırma haritası - app/config/routing.yml - bu bilgiyi okunabilir bir biçimde sağlar:

# app/config/routing.yml
blog_list:
    path:     /blog
    defaults: { _controller: AppBundle:Blog:list }

blog_show:
    path:     /blog/show/{id}
    defaults: { _controller: AppBundle:Blog:show }

Şimdi Symfony tüm sıradan görevleri ele alıyor, ön denetleyici web/app.php oldukça basit olduğundan ve çok az şey yaptığından, ona hiç dokunmanız gerekmeyecektir:

// web/app.php
require_once __DIR__.'/../app/bootstrap.php';
require_once __DIR__.'/../app/AppKernel.php';

use Symfony\Component\HttpFoundation\Request;

$kernel = new AppKernel('prod', false);
$kernel->handle(Request::createFromGlobals())->send();

Ön denetleyicinin tek görevi, Symfony'nin motorunu (Çekirdek olarak adlandırılır) başlatmak ve ona gerekli işlemleri yapması için bir Request nesnesi göndermektir. Symfony çekirdeği yönlendiriciden isteği incelemesini ister. Yönlendirici, gelen URL'yi belirli bir route ile eşleştirir ve çalıştırılması gereken denetleyici de dahil olmak üzere route hakkında bilgi döndürür. Eşleşen rotadaki denetleyici çalıştırılır ve denetleyici içindeki kodunuz uygun Response nesnesi nesnesi oluşturur ve döndürür. HTTP başlık bilgileri ve Response nesnesinin içeriği kullanıcıya geri gönderilir.

Twig ile daha pratik şablonlar oluşturma

Kullanmayı tercih ederseniz, Symfony, standart olarak, şablonları daha hızlı yazmayı ve okumayı daha kolay hale getiren Twig adı verilen bir şablon motoruyla gelir. Bu, örnek uygulamanın daha az kod içerebileceği anlamına gelir! Örneğin, list.html.php şablonunu Twig ile yeniden yazalım:

{# app/Resources/views/blog/list.html.twig #}
{% extends "layout.html.twig" %}

{% block title %}List of Posts{% endblock %}

{% block body %}
    <h1>List of Posts</h1>
    <ul>
        {% for post in posts %}
        <li>
            <a href="{{ path('blog_show', {'id': post.id}) }}">
                {{ post.title }}
            </a>
        </li>
        {% endfor %}
    </ul>
{% endblock %}

Ayrıca, layout.html.php şablonunu da Twig ile aşağıdaki şekilde yazabiliriz:

{# app/Resources/views/layout.html.twig #}
<!DOCTYPE html>
<html>
    <head>
        <title>{% block title %}Default title{% endblock %}</title>
    </head>
    <body>
        {% block body %}{% endblock %}
    </body>
</html>