Symfony2 Formlarini Ozellestirmek ve Bootstrap CSS

Hazırladığım bir projede Symfony2 formlarını özelleştirerek Bootstrap CSS ile entegre etmek istedim. Bunu başarmak için tabii ki Symfony2’de bir formül var: Form Themes. Symfony2 ile oluşturulan formlardaki tüm elemanların formatlarının tutulduğu bir dosya bulunuyor. Bu formatlarda istediğimiz gibi değişiklik yaparak, örneğin <div> tag’leri ekleyerek ve ya sınıflar atayarak Bootstrap ile uyumlu hale getirebiliyoruz.

Bu yazıda Bootstrap CSS’in detayından bahsetmeyeceğim, fakat genel olarak ön yüz tasarımcılarının işini kolaylaştıran, daha da önemlisi yazılım geliştiricileri tasarım yapmaktan kurtaran bir CSS framework’u olduğunu söyleyebiliriz. Twitter tarafından tasarlanan bu framework, kullanım kolaylığı, tarayıcılarla uyumu ve estetik güzellik gibi çok iyi özelliklere sahip.

Boostrap Kurulumu

Öncelikle Bootstrap CSS indirip uygulamamıza dahil edelim. Şu adresten Bootstrap için gerekli dosyaları indirdikten sonra web klasörü altına açıyoruz. Daha sonra kullandığımız layout içerisinde bootstrap için gerekli dosyaları dahil ediyoruz.

# app/Resources/views/base.html.twig
<head><link href="{{ asset('css/bootstrap.min.css') }}" rel="stylesheet"></head>
<body>
<script type="text/javascript" src="{{ asset('js/bootstrap.min.js') }}"></script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script></body>

Bootstrap ile birlikte kullanabileceğiniz bazı javascript eklentileri de bulunuyor, bunlar jQuery gerektirdiğinden bu kütüphaneyi de dahil ediyoruz.

Symfony2 Formlarını Özelleştirmek

Symfony2’de oluşturduğumuz formları sayfada gösterirken bazı fonksiyonlar kullanıyoruz. Örneğin integer tipinde bir alanı integer_widget metodundan faydalanıyoruz. Fakat oluşan çıktının formatını değiştirmek istersek, iki yöntem kullanabiliriz. Birisi sadece ilgili fom için bu metodun çıktısını değiştirmek. Bu her ne kadar daha basit gibi görünse de, aynı işleme başka yerde ihtiyacımız olduğunda eziyet olacaktır. Bu yüzden bu yönteme ek olarak tüm formlarda bir metodun çıktısını değiştirmek için de bir yöntem bulunuyor.

Önce sadece ilgili formda form_row metodunu değiştirelim:

#src\Dubluve\NewBundle\Default\new.html.twig
{% extends '::base.html.twig' %}
{% form_theme form _self %}
{% block form_row %}
{% spaceless %}
    <div class="control-group">
        {{ form_label(form) }}
        {{ form_errors(form) }}
        <div class="controls">{{ form_widget(form) }}</div>
    </div>
  {% endspaceless %}
{% endblock form_row %}
{% block content %}
    {{ form_row(form.email) }}
{% endblock %}

Bu sayfada kullanılan form için form_row metodu farklı bir çıktı oluşturacak, fakat diğer sayfalardaki formlar için çıktı aynı olacaktır.

Eğer bu değişikliğin tüm formlar için geçerli olmasını istiyorsak kendi formatlarımızın bulunduğu bir dosya oluşturmamız gerekiyor. Yeni dosyada çıktısını değiştirmek istediğimiz metodlar olacak, diğerleri ise eskisi gibi çıktı oluşturmaya devam edecek. Yeni dosyayı proje klasörü içerisinde src\Dubluve\NewBundle\Resources\views\Form\bootstrap.html.twig  dosyasını yaratıyoruz.

#src\Dubluve\NewBundle\Resources\views\Form\bootstrap.html.twig
{% extends 'form_div_layout.html.twig' %}
 {% block form_row %}
{% spaceless %}
    <div>
        {{ form_label(form) }}
        {{ form_errors(form) }}
        <div>{{ form_widget(form) }}</div>
    </div>
  {% endspaceless %}
{% endblock form_row %}

Dosyadaki extends ifadesi, form_div_layout.html.twig dosyasını baz aldığımızı belirtiyor. Dolayısıyla değiştirilmeyen form elemanları form_div_layout.html.twig dosyasındaki gibi kalacaktır. Yeni dosyayı  oluşturduktan sonra config.yml dosyasını da aşağıdaki şekilde güncelliyoruz ve bu dosyanın global olarak kullanılacağını belirtiyoruz.

# Twig Configuration
twig:
    form:
      resources:
        - 'DubluveNewBundle:Form:bootstrap.html.twig'

Bootstrap İle Entegrasyon

Yukarıdaki yöntemleri anlattıktan sonra aslında sadece bootstrap ile değil herhangi bir CSS framework ile entegrasyon oldukça basit. Aslında sadece bootstrap dosyalarını sisteme entegre etmek bile güzel bir görünüm sağlıyor 🙂 Hazırladığım projede tüm formların bootstrap dokümantasyonunda anlatılan horizontal-form formatında olmasını ve hata mesajlarının bootstrap formatında çıkmasını istemiş ve dosyayı buna göre düzenlemiştim. Aşağıda kullandığım dosyayı görebilirsiniz.

#src\Dubluve\NewBundle\Resources\views\Form\bootstrap.html.twig
{% extends 'form_div_layout.html.twig' %}
{% block form_row %}
{% spaceless %}
{% if not form.children %}
<div class="control-group">
{{ form_label(form) }}
{{ form_errors(form) }}
<div class="controls">{{ form_widget(form) }}</div>
</div>
{% else %}
{{ form_errors(form) }}
{{ form_widget(form) }}
{% endif %}
{% endspaceless %}
{% endblock form_row %}
{% block form_label %}
{% spaceless %}
{% if not compound %}
{% set label_attr = label_attr|merge({'for': id}) %}
{% endif %}
{% if required %}
{% set label_attr = label_attr|merge({'class': (label_attr.class|default('') ~ ' required control-label')|trim}) %}
{% endif %}
{% if label is empty %}
{% set label = name|humanize %}
{% endif %}
<label {% for attrname, attrvalue in label_attr %} {{ attrname }}="{{ attrvalue }}"{% endfor %} >{{ label|trans({}, translation_domain) }}</label>
{% endspaceless %}
{% endblock form_label %}
{% block form_errors %}
{% spaceless %}
{% if errors|length > 0 %}
{% for error in errors %}
<div class="alert alert-error">{{
error.messagePluralization is null
? error.messageTemplate|trans(error.messageParameters, 'validators')
: error.messageTemplate|transchoice(error.messagePluralization, error.messageParameters, 'validators')
}}</div>
{% endfor %}
{% endif %}
{% endspaceless %}
{% endblock form_errors %}

Symfony2 Türkçe Belgeleri

Yaklaşık 1 ay önce Symfony2 yazılarıma denk gelen Sedat Kocadoğan’ın benimle iletişime geçmesiyle, Symfony2 için Türkçe dokümantasyon oluşturmak amaçlı bir avuç gönüllü insan olarak bir araya geldik. Benim çok yoğun bir dönemime rastladığı için pek katkıda bulunamasam da Osman Üngür’ün de katkılarıyla dokümantasyonun yarısı tamamlandı. Dokümantasyona buradan ulaşabilirsiniz.

Siz de katkıda bulunmak isterseniz yapmanız gerekenleri yine bu sitede bulabilirsiniz. Ben de çevirinin kalan kısımlarında destek olmaya çalışacağım.

Tamamen gönüllü olarak yapılan bu proje umarım Symfony2 gibi güncel teknolojiyi içeren framework’lerin bilinirliğini arttırır ve ülkemizde üretilen yazılımların kalitesini arttırır.

Symfony2 – Ek Kutuphaneleri Yonetmek

Symfony2 için kullanılan ek kütüphaneleriyle tek bir bilgisayarda çalışırsanız sorun yaşamayabilirsiniz. Fakat canlı bir ortama ve ya başka bir yazılımcının bilgisayarına kurmak istediğinizde, hangi kütüphanenin hangi versiyonunu kurduğunuzu unutabilirsiniz ya da zamanla herkes farklı bir versiyona sahip olabilir. Ayrıca ek kütüphane klasörünüz fazlasıyla şişeceği için kullandığınız git deposunda gereksiz yer kaplar.

Hataları önlemek ve işleri kolaylaştırmak için Symfony2 ile birlikte ek kütüphanelerinizin versiyonlarının yönetildiği bir dosya ve bunun için tek basit bir komutla çalıştırabileğiniz bir program geliştirilmiş. Bu program git versiyon kontrol sistemini kullanıyor, dolayısıyla git sisteminin bilgisayarınızda yüklü olması gerekiyor. Eğer git yüklü değilse Windows’ta yüklemek için şuradaki talimatları izleyebilirsiniz.

deps Dosyası ve bin/vendors komutu

Öncelikle Symfony2 framework şu adresten ek kütüphaneler olmadan (without vendors) indiriyoruz, arşiv dosyasını açıyoruz. Arşivin açıldığı  klasörde deps ve deps.lock isminde 2 adet dosya göreceksiniz. Deps dosyasındaki her bir blok ek kütüphaneleri göstermekte ve ek kütüphaneler kurmak için kullanacağımız program bu dosyayı okuyor. Deps.lock dosyası ise yüklediğiniz kütüphanenin git tarafından oluşturulan ve o commit’e ait commit-hash bilgisini (burada bahsetmiştim) tutuyor.

Peki yeni bir kütüphane yüklemek için ne yapacağız? Oldukça basit, deps dosyasına yeni bir kayıt gireceğiz. Deps dosyasına göz atarsanız kayıtlar genel olarak şöyledir:

[FOSUserBundle]
 git=git://github.com/FriendsOfSymfony/FOSUserBundle.git
 target=bundles/FOS/UserBundle
 version=1.2.0

Bu blokta [] içindeki bilgi ek kütüphanemizin ismini gösterir. Eğer target parametresiyle herhangi bir klasör belirmediyseniz varsayılan olarak burada verilen isimde bir klasör yaratılır ve dosyalar bu klasöre indirilir.

git=<git_repo_url> kısmına yazılan adres kısım ek kütüphanenini alınacağı adresi gösterir. Bu adrese git ve http/https ile başlayan kütüphane adreslerini girebilirsiniz. Gerekli olan parametreler aslında bu kadar, fakat diğer parametrelere de göz atalım.

target=<vendor_target> kısmında kütüphane dosyalarının indirileceği klasörü giriyoruz. Buraya girilen klasör <proje_klasörü>/vendor altında yaratılıyor.

Son olarak version= <versiyon> kısmında ise bir kütüphane için belli bir versiyon ve ya branch belirtebiliyoruz.

Deps dosyasında kayıtlı olan ek kütüphaneleri indirmek için proje klasörüne geçtikten yapmamız gereken sadece aşağıdaki komutu çalıştırmak:

php bin/vendors install

Bu komuttan sonra eğer vendor klasörünüzde hiçbir klasör görmezseniz ve ya klasörleriniz hepsi boşsa, git sistemini işletim sisteminize düzgün kuramamış olabilirsiniz. Eğer bazı kütüphaneler yüklenemiyorsa tekrar denediğinizde bunlar da yüklenecektir. İlk kez çalıştırdığınızda bu işlem zaman alabilir, sabırlı olun 🙂

Sistem Nasıl İşliyor?

Peki sistem nasıl işliyor, yukarıdaki komutu çalıştırdığımda neler oluyor diyenler için aşağıdaki şemayı hazırladım. Ek kütüphanelerimiz hayırlı olsun 🙂

vendors script flowchart

 

Symfony2- FOSFacebook – Facebook needs the CURL PHP Extension Hatasi

Symfony2’de FOSFaceBookBundle yüklemesi yaptıktan sonra, herhangi bir konsol komutu çağırmaya çalıştığımda aşağıdaki hatayı alıyordum.

[Exception] Facebook needs the CURL PHP extension.

Biraz araştırdığımda, WAMP gibi 3’ü bir yerde kurulumlarda Apache tarafından kullanılan php.ini dosyası ile konsoldan aşağıdaki şekilde çağırdığımız php komutunun farklı php.ini dosyası kullandığını öğrendim.

php app/console container:debug


Yani wamp’ın menüsünü kullanarak curl uzantısını aktifleştirseniz bile aslında konsoldan çağrılan php programının kullandığı php.ini değişmiyor. WAMP için örnek vermek gerekirse, C:\wamp\bin\php\php5.3.8 klasöründeki php.ini dosyasını şu şekilde düzenlemek gerekiyor:

;extension=php_curl.dll
extension=php_curl.dll

Symfony 2 – Veritabanı İçin Başlangıç Verileri Oluşturmak – Doctrine Fixtures

Daha önceki yazımda Symfony2’de veritabanı değişikliklerini yönetmek için database migrations kullanımını anlatmıştım. Bu yazıda yine veritabanında bize çok yardımcı olacak bir özellikten bahsedeceğim.

Geliştirme sırasında veritabanı sıklıkla değişir, özellikle database migration yapısı kullanmıyorsanız tabloları tekrar yaratmak zorunda kalırsınız. Benzer şekilde veritabanı yapınıza yeni tablolar eklenebilir, sistemin düzgün işlemesi için gerekli veriler (örneğin parametre tabloları) olabilir ya da ekibinize yeni yazılımcılar katılabilir. Bu gibi durumlarda tabloları kullanan ekranlar için tekrar test verileri girmek gerekir. İşte bu test verisi girme işlemini otomatik olarak gerçekleştirmek istediğimiz zaman, Doctrine ORM’de yer alan database fixtures özelliğini kullanabiliriz.

Kurulum

Symfony 2’de database fixtures özelliğini kullanmak için DoctrineFixturesBundle paketini yüklemek gerekiyor. Bunun için proje klasörümüzde yer alan deps dosyasına aşağıdaki bilgileri ekliyoruz:

[doctrine-fixtures]
git=http://github.com/doctrine/data-fixtures.git

[DoctrineFixturesBundle]
git=http://github.com/symfony/DoctrineFixturesBundle.git
target=/bundles/Symfony/Bundle/DoctrineFixturesBundle

Bu eklemeleri yaptıktan sonra komut satırından ilgili proje klasörüne gelip ek kütüphanelerin kurulumunu sağlayan ilgili komutu çalıştırıyoruz:

php bin\vendors install

Eğer kurulum sorunsuz tamamlandıysa {proje_klasoru}/vendor/doctrine-fixtures klasörünü olmalı.

Şu aşamada paketin kaynak kodunu projeye ekledik ama uygulamamız bu paketi henüz kullanmıyor. Bu nedenle paketi yüklemek için gerekli kodları ilgili sayfalara eklemek gerekiyor. Önce ilgili isim alanının (namespace) uygulama tarafından tanınması için {proje_klasoru}\app\autoload.php dosyasına aşağıdaki kodları ekliyoruz:

$loader->registerNamespaces(
       array( // ...
             'Doctrine\\Common\\DataFixtures' => __DIR__.'/../vendor/doctrine-fixtures/lib',
             'Doctrine\\Common' => __DIR__.'/../vendor/doctrine-common/lib',
             // ... ));

Burada dikkat edilmesi gereken nokta, Doctrine\\Common\\DataFixtures satırının Doctrine\\Common satırından önce gelmesi gerektiğidir. Symfony’de kullanılan isim alanları, yukarıdaki registerNamespaces() metodundaki sırasıyla kontrol edilir, bu nedenle ilk olarak Doctrine\\Common satırı gelirse, bir alt isim alanını gösteren satır dikkate alınmayacak ve Doctrine\\Common\\DataFixtures isim alanı kayıt edilemeyecektir. Yani her zaman daha özel, yani daha alt seviye isim alanlarını gösteren satırlar daha önce gelmelidir.

Fixture Yaratalım!

Test verilerini otomatik olarak oluşturacak dosyalar, Symfony’de PHP sınıflarından oluşmaktadır. Bu dosyalar, yani fixture dosyaları, ilgili paketin {paket_klasoru}/DataFixtures/ORM klasörü altında yer almalıdır (örneğin Dubluve/TestBundle/DataFixtures/ORM). Eğer MongoDB kullanmayı düşünüyorsanız  {paket_klasoru}/DataFixtures/MongoDB altında dosyaları tutmalısınız. Yükleyeceğiniz dosyanın adı önemli değil, fakat anlamlı olması açısından, örneğin User nesnesi için LoadUserData.php  olarak isimlendirebilirsiniz.

Peki dosyayı yarattık, içeriği nasıl oluşturacağız? Birçok uygulamada temel ihtiyaç olan kullanıcı nesnesine, geçerli bir kullanıcı için test verisi oluşturalım. Nesnemiz User, sahip olduğu özellikler username ve password ve daha önceden {paket_klasoru}/Entity altında ilgili sınıfı yaratmış olalım. Örnek kod aşağıdaki gibi olmalı:

// src/Dubluve/TestBundle/DataFixtures/ORM/LoadUserData.php
 namespace Dubluve\TestBundle\DataFixtures\ORM;
 use Doctrine\Common\DataFixtures\FixtureInterface;
 use Dubluve\TestBundle\Entity\User;
 use Doctrine\Common\Persistence\ObjectManager;
 class LoadUserData implements FixtureInterface {
   public function load(ObjectManager $manager) {
     $userAdmin = new User();
     $userAdmin->setUsername('admin');
     $userAdmin->setPassword('test');
     $manager->persist($userAdmin);
     $manager->flush();
   }
 }

User nesnesi ve benzer nesneler için fixture sınıfları oluşturduktan sonra tüm bu değişiklikleri veritabanına uygulamak için aşağıdaki komutlardan, mongodb kullanıyorsanız 2. komutu, yoksa sadece ilkini çalıştırıyoruz:

php app/console doctrine:fixtures:load
php app/console doctrine:mongodb:fixtures:load

Meraklısına Detaylar

Yukarıdaki örnek basit anlamda fixture kullanımını anlatıyor. Fakat veritabanınızda muhtemelen birbiriyle ilişkili birçok nesne olacak ve bunları birbiriyle ilişkilendirerek veritabana girmek isteyeceksiniz. Bunun için DoctrineFixtures paketine ilişki kurma ve ard arda fixture çalıştırma özellikler bulunuyor. Aşağıdaki örnekte ilk olarak kullanıcı tablosuna ve kullanıcı grubu tablosuna birer kayıt ekleyip, ara tabloda ilişkilendireğiz.

// src/Dubluve/TestBundle/DataFixtures/ORM/LoadUserData.php
namespace Dubluve\TestBundle\DataFixtures\ORM;

use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Dubluve\TestBundle\Entity\User;
use Doctrine\Common\Persistence\ObjectManager;
class LoadUserData extends AbstractFixture implements OrderedFixtureInterface
{
    public function load(ObjectManager $manager)
    {
        $userAdmin = new User();
        $userAdmin->setUsername('admin');
        $userAdmin->setPassword('test');

        $manager->persist($userAdmin);
        $manager->flush();

        $this->addReference('admin-user', $userAdmin);
    }

    public function getOrder()
    {
        return 1; // the order in which fixtures will be loaded
    }
}

Yukarıdaki sınıfta, daha önceki fixture sınıfından farklı olarak getOrder() fonksiyonunu görüyoruz. Bu fonksiyon, yeni kullanıdığımız  AbstractFixture sınıfından miras geliyor ve fixture sınıfının hangi sıralamada çalıştırılacağını ifade ediyor. Fonksiyondan 1 değerinin dönmesi, ilk olarak bu fixture sınıfının çalıştırılacağını belirtiyor.

Yine farklı bir satır olan $this->addReference(‘admin-user’, $userAdmin) satırı bu fixture sınıfında $userAdmin nesnesini kullanarak veritabanına kaydettiğimiz kayıt için bir referans oluşturuyor. Bu referansı daha sonra başka bir fixture sınıfında kullanacağız.

Kullanıcı grubu için yaratacağımız fixture sınıfı da şu şekilde:

// src/Dubluve/TestBundle/ORM/LoadGroupData.php
namespace Dubluve\TestBundle\DataFixtures\ORM;

use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Dubluve\TestBundle\Entity\Group;
use Doctrine\Common\Persistence\ObjectManager;
class LoadGroupData extends AbstractFixture implements OrderedFixtureInterface
{
    public function load(ObjectManager $manager)
{
        $groupAdmin = new Group();
        $groupAdmin->setGroupName('admin');

        $manager->persist($groupAdmin);
        $manager->flush();

        $this->addReference('admin-group', $groupAdmin);
    }

    public function getOrder()
    {
        return 2;
    }
}

Son olarak da referans oluşturduğumuz bu iki kayıt için ara tabloya kayıt yaratacak fixture sınıfını oluşturuyoruz:

// src/Dubluve/TestBundle/DataFixtures/ORM/LoadUserGroupData.php
namespace Dubluve\TestBundle\DataFixtures\ORM;

use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Dubluve\TestBundle\Entity\UserGroup;
use Doctrine\Common\Persistence\ObjectManager;

class LoadUserGroupData extends AbstractFixture implements OrderedFixtureInterface
{
    public function load(ObjectManager $manager)
    {
        $userGroupAdmin = new UserGroup();
        $userGroupAdmin->setUser($manager->merge($this->getReference('admin-user')));
        $userGroupAdmin->setGroup($manager->merge($this->getReference('admin-group')));

        $manager->persist($userGroupAdmin);
        $manager->flush();
    }

    public function getOrder()
    {
        return 3;
    }
}

Bu sınıfları yarattıktan sonra yukarıda yaptığımız gibi ilgili konsol komutunu çalıştırıp, bu kayıtları veritabanına kaydedebiliriz.

Zevkle yazılım geliştireceğiniz günler dilerim!