07.Symfony Doctrine — Entity

07.Symfony Doctrine — Entity

Doctrine Projesi çeşitli kitaplıklardan (bileşenler) oluşur. Her Doctrine bileşeni, composer tarafından kurulabilir bir paket olarak dağıtılır ve Packagist.org katalogunda kayıtlıdır. Doctrine, veritabanınızı nesne yönelimli bir şekilde yönetmek için kullanışlı yöntemler sunan açık kaynak kodlu bir PHP kütüphanesidir. İlişkisel veritabanları ile çalışmak için Doctrine, Object Relational Mapper (kısaca ORM) kullanır.

 

Herhangi bir PHP uygulamasının en yaygın ve en zorlu görevlerinden biri, veritabanı işlemleridir. Symfony Framework, veritabanlarıyla çalışacak herhangi bir bileşeni entegre etmese de, Doctrine adlı bir üçüncü parti kütüphaneye sıkı entegrasyon sağlar. Doctrine in tek amacı, veritabanı etkileşimlerini kolay ve esnek yapmak için size güçlü araçlar vermektir. Yukarıda da belirttiğim üzere, Symfony de veritabanı işlemleri için, bir bileşen yoktur. Bunun yerine Symfony Doctrine denilen üçüncü parti uygulamaya full destek verir.

 

Bir önce ki “06.Symfony Doctrine — Giriş” adlı yazım da sizlere Doctrine ile İlişkisel veritabanları ve NoSQL veritabanları arasında ki bağlantıyı dilim döndöğünce anlatmaya çalışmıştım. Doctrine nedir, ilişkisel veritabanlarından neleri destekler, ilişkisel olmayan, NoSQL veritabanlarından hangilerine destek verir, Doctrine nin çalışma mantığı nedir, mimarisi nasıldır ve   benzeri sorulara yanıt vermeye çalışmıştım o yazının sonunda ise Symfony ve Doctrine giriş yapmıştık. Bu yazımda ise meselenin bam teli olduğuna inandığım Entity ye giriş yapacağız. Diğer Framework lerde ki karşılığı ise Model dir.

 

Symfony bize bazı şeyleri yönetmek için Annotations, YamL, XML, ve Klasik PHP olmak üzere dört seçenek sunar. Eğer okuduysanız hatırlarsınız, örneğin “05.Symfony – Routing” adlı yazımda, Symfony de Rotalarımızı yönetmek için Annotations kullanmıştık. Aynı şekilde Symfony de Doctrine, Entity işlemlerimizde de Annotations kullanacağız. Diğerleri de kullanılabilir. Küçük Entity lerde Annotations harika iken, bol ilişkilerin olduğu, bol indeks lerin, özel (uniqe) alanların olduğu büyük Entity lerde ise, Entity dosyamız yorum satırlarıyla doluyor, buda okumayı zorlaştırıyor. Annotations ının buna benzer bazı avantajları ve dezavantajları var. Burayı biraz daha açalım;

Annotations

Bir annotation, Doctrine ORM tarafından önişlem(preprocessed) esnasın da Özel Önem Verilen Bir PHP Yorumudur. Diğer bir deyişle, annotations, çalıştırma zamanında(run-time) Doctrine ORM tarafından okunabilen bir Entity sınıfına eklenen meta veridir(metadata).

Annotations ‘lar, Entity hakkında ayrıntılı bilgiler sağlar ve Doctrine ORM’ye bir Entity nin bir veritabanı tablosunda nasıl eşleyeceğini söyler. (Nasıl Map-Eşleşme Yapılacak)

 

Bir Docblock annotation, eğik çizgi (/) ve iki yıldız işareti (*) ile başlayan bir C ++ tarzı yorumdur. Bu “başlatıcı” karakterler gereklidir, aksi takdirde Doctrine notu/yorumu tanımayacaktır. Docblock annotation örneği aşağıda bulabilirsiniz:

/**
 * Örnek bir Docblock annotation yorumu.
 */

Doctrine, Doctrine\Annotations bileşeninin yardımıyla yukarıda yazdığımız Docblock annotation ı okuyacaktır. Kendisini ilgilendiren birşey varsa yapacak, yoksa yapmayacaktır, ki yoktur.


Aağıda bir adet Doctrine Entity sınıfının temel örneği var. Aşağıda ki örnekte görebileceğiniz gibi bir Annotation özel bir etiket ile başlar ve bu etiket @ dir.

<?php
// Örnek bir Entity
namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity
 * @ORM\Table(name="post")
 */
class Post 
{
  /**
   * @ORM\Id
   * @ORM\GeneratedValue
   * @ORM\Column(name="id")   
   */
  protected $id;

  /** 
   * @ORM\Column(name="title", type="string", length=100)   
   */
  protected $title;

  /** 
   * @ORM\Column(name="content", type="text")  
   */
  protected $content;

  /** 
   * @ORM\Column(name="status")  
   */
  protected $status;

  /**
   * @ORM\Column(name="date_created")  
   */
  protected $dateCreated;   
}

 
Yukarıda ki örneği incelersek eğer;
— Yorum satırları yani Annotation /** ile başlamış ve */ ile bitmiş.
— Annotation lar @ (at) sembolü ile başlamış. Örneğin: @ORM\Column(name=”status”)
— Yazılanlar ise @ORM\Column(name=”title”, type=”string”, length=100) Doctrine ‘nin anlayacağı türden.

Dolayısıyla bu Entity bizim için geçerli bir Entity dir.

Symfony Doctrine Entity

Artık Annotation nedir, nasıl başlar, nasıl biter, @ (at) sembolü nedir biliyoruz. Sanırım kendi örnek Entity mizi artık yazabiliriz. Başlamadan önce Proje dizininiz de bulunan(ki bende bu E:\Projects\Kitap).env dosyanızı kontrol edin.



# This file is a "template" of which env vars need to be defined for your application
# Copy this file to .env file for development, create environment variables when deploying to production
# https://symfony.com/doc/current/best_practices/configuration.html#infrastructure-related-configuration

###> symfony/framework-bundle ###
APP_ENV=dev
APP_SECRET=9d2f2b90a0c0d6d33fa23de37592d887
#TRUSTED_PROXIES=127.0.0.1,127.0.0.2
#TRUSTED_HOSTS=localhost,example.com
###< symfony/framework-bundle ###

###> symfony/swiftmailer-bundle ###
# For Gmail as a transport, use: "gmail://username:[email protected]"
# For a generic SMTP server, use: "smtp://localhost:25?encryption=&auth_mode="
# Delivery is disabled by default via "null://localhost"
MAILER_URL=null://localhost
###< symfony/swiftmailer-bundle ###

###> doctrine/doctrine-bundle ###
# Format described at http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url
# For an SQLite database, use: "sqlite:///%kernel.project_dir%/var/data.db"
# Configure your db driver and server_version in config/packages/doctrine.yaml
DATABASE_URL=mysql://db_user:[email protected]:3306/db_name
###< doctrine/doctrine-bundle ###

Yukarı da ki kodu inceleyin ve şu satırı arayın;
###> doctrine/doctrine-bundle ### Bu satırın altında veritabanı ayarlarımızı yapacağımız kısım olan;
DATABASE_URL=mysql://db_user:[email protected]:3306/db_name satırı var. Bulduysanız bu kısmı değiştirin.


Ben XamPP Kullanıyorum. Veritabanına bağlanacağım kullanıcı root ve root kullanıcısının bir şifresi yok yani null. Testlerimi yapacağım veritabanımın adıda kitap_db olacak. Ben ayarlarımı buna göre yapacağım. Aşağıda yaptığım ayarları görebilirsiniz.


# This file is a "template" of which env vars need to be defined for your application
# Copy this file to .env file for development, create environment variables when deploying to production
# https://symfony.com/doc/current/best_practices/configuration.html#infrastructure-related-configuration

###> symfony/framework-bundle ###
APP_ENV=dev
APP_SECRET=9d2f2b90a0c0d6d33fa23de37592d887
#TRUSTED_PROXIES=127.0.0.1,127.0.0.2
#TRUSTED_HOSTS=localhost,example.com
###< symfony/framework-bundle ###

###> symfony/swiftmailer-bundle ###
# For Gmail as a transport, use: "gmail://username:[email protected]"
# For a generic SMTP server, use: "smtp://localhost:25?encryption=&auth_mode="
# Delivery is disabled by default via "null://localhost"
MAILER_URL=null://localhost
###< symfony/swiftmailer-bundle ###

###> doctrine/doctrine-bundle ###
# Format described at http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url
# For an SQLite database, use: "sqlite:///%kernel.project_dir%/var/data.db"
# Configure your db driver and server_version in config/packages/doctrine.yaml
DATABASE_URL=mysql://root:@127.0.0.1:3306/kitap_db
###< doctrine/doctrine-bundle ###

DATABASE_URL=mysql://root:@127.0.0.1:3306/kitap_db bilgileriniz doğru olmalı. Benim .env dosyamda ki bilgiler doğru. XamPP ım çalışıyor. Veritabanım çalışıyor. Dolayısıyla ben hazırım.

php bin/console doctrine:database:drop --force

Yukarıda ki komut varsa veritabanınız da kitap_db, onu drop edecek ve size "Dropped database for connection named `kitap_db`" şeklinde mesaj dönecek. Yok ise, böyle bir veritabanı yok diye size hata/uyarı mesajı dönecek. Hata/Uyarı mesajı dönerse, sorun yok.

Varsa drop ettik, yoksa sorun değil. Şimdi oluşturalım. Aşağıda ki komut bize boş bir veritabanı oluşturur.

php bin/console doctrine:database:create

Yukarıda ki veritabanını oluşturma komutunu çalıştırırsanız, eğer veritabanı yoksa, size bir adet kitap_db adlı veritabanını oluşturur ve "Created database `kitap_db` for connection named default" şeklinde mesaj döner. Yanda ki resmi büyütüp sonuçlara bakabilirsiniz. Eğer daha önce aynı isimli bir veritabanı varsa size bir Hata/Uyarı mesajı dönecektir.

Nasıl Entity Oluşturabilirim ?

İki şekilde bir Entity oluşturabilirsiniz. Bunlar sırasıyla;
1. Komut Satırından
2. Manuel Olarak.

Komut Satırından Entity Oluşturma

Bu yöntem hızlıdır, kolaydır. Genelde bunu tercih ederiz. Ama bu bir SON değildir aksine bu genelde sadece BAŞLANGIÇTIR. Çünkü bu yöntem YETERSİZ dir. Neden yetersiz olduğuna kısaca değinim. Bu yöntem hazır bir yöntem olduğu için, size tam olarak Annotation desteği vermez. Bir yada bir kaç alanı indeks, yada unique yapamazsınız. Yada onPrePersist, onPreUpdate gibi diğer özellikleri kullanamazsınız, yada One-To-One, One-To-Many gibi ilişkileri kuramazsınız. Öyleyse neden kullanırız ?

Komut satırını kullanırız çünkü bize hızlıca bir adet Entity ve bir adet Repository dosyası verir. Asıl işi bundan sonra Manuel yaparız 🙂


Konu hakkında Symfony Resmi Sitesine bakabilirsiniz: Generating a New Doctrine Entity Stub


Komut satırından Entity oluşturmak için Symfony Maker Bundle a ihtiyacımız var. Maker Bundle işimizi çok kolaylaştırıyor. Sadece Entity oluşturmuyor, Controller, Repository, Form, View da oluşturuyor. Bizim CRUD yardımcımız. Devam etmeden önce aşağıda ki komutu çalıştırın ve projenize Symfony Maker Bundle eklentisini dahil edin.

composer require symfony/orm-pack symfony/maker-bundle --dev

Şöyle bir Entity oluşturacağız. Diyelim ki bir Kitap sitemiz var, ve bizde satacağımız kitaplar için book adlı bir tablo oluşturacağız. Örneği kısa tutmak için book adlı tabloda aşağıda ki alanlar ve özellikler olacak. Aslında bundan fazlası olmalı. Örneğin bir katabın resmi de olmalı, yada yayınevi de olmalı. Dediğim gibi ben örneği kısa tutmak için bir kaç alan koydum. Tabloyu inceleyin lütfen ...

Field Name Data Type Null Unique Auto Increment
id INTEGER × +++ +++
name VARCHAR × +++ ×
excerpt TEXT × × ×
page INTEGER × × ×
isbn VARCHAR × +++ ×
price DECIMAL × × ×
published_at DATETIME +++ × ×
is_publish BOOLEAN × × ×
author VARCHAR × × ×

İncelemeniz bitti ise, aşağıda ki komutu çalıştırın.

php bin/console make:entity

Yukarıda ki komutu çalıştırdığımızda Symfony bizden bazı bilgiler ister. Bunları sırasıyla yazarsak eğer.

Class name of the entity to create or update:Yeni Entity Adı

Hatırlarsanız Entity adı aynı zamanda tablo adı olacak. ( Entity dosyası oluştuktan sonra arzu edersek tablo adını değiştirebiliriz). Devam edelim. Bizden ilk önce oluşturacağımız Entity e isim vermemizi istiyor: Book yazıp Enter tuşuna basın. Enter e bastıktan sonra bizim için bir adet Repository dosyası ve bir adet te Entity oluşturacak. Örneğimize göre gidersek eğer bize bir adet src/Entity/Book.php ve bir adette src/repository/BookRepository.php adlı 2 dosya oluşturacak.


Devam etmeden önce şunu açıklamalıyım. Aşağıda yazacağım sırayla işlemler sürekli kendini tekrar edecek. Ta ki tablomuza tüm alanlarımızı ekleyinceye kadar. Tüm alanlarımızı ekledikten sonra 2 kere Enter tuşuna basacağız. Ve böylece konsoldan Entity/Model oluşturma işlemini bitiririz.

  1. Field Adı: New property name (press to stop adding fields):/örneğin: name
  2. Field Türü: Field type (enter ? to see all types) [string]:/varsayılan:string. ? tuşuna basarsanız kullanabileceğiniz diğer türleride(text, integer vs) görürsünüz.
  3. Field Uzunluğu: Field length [255]:
  4. Field Boş Değer Kabul ediyor mu: Can this field be null in the database (nullable) (yes/no) [no]:/varsayılan: null değer kabul etmiyor.

Bu yukarıda yazdığım 4 adım kendini sürekli tekrar edecek.


-- Bir sonra ki soru ise şu: New property name (press to stop adding fields): Bu soru kendini tekrar edecek, ta ki tablomuza yeni alan eklemeyi bitirinciye kadar. Örneğin: name


-- Bir sonra ki soru ise şu: Field type (enter ? to see all types) [string]: Field türü, string,text, integer (yada smallint, bigint), boolean, float, blob, binary, json (yada json_array), datetime, decimal date, time vb... Birşey yapmayın name için string uygundur. Enter tuşuna basın.


-- Bir sonra ki soru ise şu: Field length [255]: Field uzunluğu name için 255 karakterlik uzunluk uygundur. Enter tuşuna basın.


-- Bir sonra ki soru ise şu: Can this field be null in the database (nullable) (yes/no) [no]: name alanı boş değer kabul ediyormu. Bizde etmiyor. NO değeri uygundur. Enter tuşuna basın.



 


Success!

Next: When you're ready, create a migration with make:migration

Şimdi src/Entity/Book.php dosyanızı PHPStorm da açıp, incelerseniz, şöyle bir satır görürsünüz.

<?php

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity(repositoryClass="App\Repository\BookRepository")
 */
class Book
{
    /**
     * @ORM\Id()
     * @ORM\GeneratedValue()
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", length=255)
     */
    private $name;

    /**
     * @ORM\Column(type="text")
     */
    private $excerpt;

    /**
     * @ORM\Column(type="integer")
     */
    private $page;

    /**
     * @ORM\Column(type="string", length=13)
     */
    private $isbn;

    /**
     * @ORM\Column(type="decimal", precision=15, scale=4)
     */
    private $price;

    /**
     * @ORM\Column(type="datetime", nullable=true)
     */
    private $published_at;

    /**
     * @ORM\Column(type="boolean")
     */
    private $is_publish;

    /**
     * @ORM\Column(type="string", length=255)
     */
    private $author;

    public function getId()
    {
        return $this->id;
    }

    public function getName(): ?string
    {
        return $this->name;
    }

    public function setName(string $name): self
    {
        $this->name = $name;

        return $this;
    }

    public function getExcerpt(): ?string
    {
        return $this->excerpt;
    }

    public function setExcerpt(string $excerpt): self
    {
        $this->excerpt = $excerpt;

        return $this;
    }

    public function getPage(): ?int
    {
        return $this->page;
    }

    public function setPage(int $page): self
    {
        $this->page = $page;

        return $this;
    }

    public function getIsbn(): ?string
    {
        return $this->isbn;
    }

    public function setIsbn(string $isbn): self
    {
        $this->isbn = $isbn;

        return $this;
    }

    public function getPrice()
    {
        return $this->price;
    }

    public function setPrice($price): self
    {
        $this->price = $price;

        return $this;
    }

    public function getPublishedAt(): ?\DateTimeInterface
    {
        return $this->published_at;
    }

    public function setPublishedAt(?\DateTimeInterface $published_at): self
    {
        $this->published_at = $published_at;

        return $this;
    }

    public function getIsPublish(): ?bool
    {
        return $this->is_publish;
    }

    public function setIsPublish(bool $is_publish): self
    {
        $this->is_publish = $is_publish;

        return $this;
    }

    public function getAuthor(): ?string
    {
        return $this->author;
    }

    public function setAuthor(string $author): self
    {
        $this->author = $author;

        return $this;
    }
}

Burada class ımızın adı Book, henüz özel bir isim vermediğimiz için tablomuzun adı book olacak. Diğer kısımları incelerseniz eğer, az çok ne olduklarını tahmin edersiniz. Repository dosyamızı şimdilik boşverelim. Zamanı gelince elimiz ayağımız olacak. Peki öyleyse devam edelim. Elimde bir adet Entity dosyam var. Bu Entity dosyasını incelediğimiz de ise, tablo adımızın book olduğunu anlıyoruz. Peki gerçekte book diye bir tablo şuan veritabanımda var mı?

Kontrol ederseniz olmadığını görürsünüz, öyleyse oluşturalım. Aşağıda ki komutu çalıştırın ve veritabanınızı kontrol edin.

php bin/console doctrine:schema:update --force

Bu komutu çalıştırırsanız, Doctrine tüm Entity dosyalarınızı tarar. Eğer bir tablo hiç yok ise, o tabloyu oluşturur. Daha önce oluşturulanlar için ise şunu yapar. Önce Entity dosyanızı inceler, sonra o Entity dosyanıza bağlı tabloyu inceler ve karşışatırır. Entity de değişiklik var ise, o değişikliği uygular.

Yukarıda ki komutu çalıştırdığınız da aşağıda ki gibi bir sonuç alırsınız.

E:\BenVeAlem\SymfonyTutorial>php bin/console doctrine:schema:update --force
Updating database schema...
Database schema updated successfully! "1" query was executed

E:\BenVeAlem\SymfonyTutorial>

Database schema updated successfully! "1" query was executed, bize bir adet Entity dosyamızı çalıştırdığını söylüyor. Şimdi veritabanınızı inceler iseniz book tablosunun oluştuğunu görürsünüz.

Yukarıda şöyle birşey dedim "Önce Entity dosyanızı inceler, sonra o Entity dosyanıza bağlı tabloyu inceler ve karşışatırır. Entity de değişiklik var ise, o değişikliği uygular." Ya değişiklik yoksa? Aynı komutu bir daha çalıştırın.

php bin/console doctrine:schema:update --force

Yukarıda ki komutu çalıştırırsanız eğer aşağıda ki gibi bir sonuç alırsınız;

E:\BenVeAlem\SymfonyTutorial>php bin/console doctrine:schema:update --force
Nothing to update - your database is already in sync with the current entity metadata.

E:\BenVeAlem\SymfonyTutorial>

Size kısaca, tüm Entity dosyalarınız ile veritabanınızın eşleştiğini ve bire bir aynı olduğunu söylüyor. Dolayısıyla hiçbirşey yapmadığı.

Özet

Bu yazımda size, Symfony Doctrine de nasıl Entity oluşturacağınızdan bahsettim. Yazım yine uzun oldu, biraz da karışık geldi. Aslında olay çok basit. Öyle sanıyorum ki, bu yazımı daha sade ve daha anlaşılır hale getirmeliyim. Umarım okurken size karışık gelmez. Görüşleriniz benim için önemlidir. Daha önceki yazılarımı okumayan için şunu söyleye bilirim. Tek amacım, kendim gibi insanlara Az-Çok Symfony i tanıtmak.



Umarım sizin için faydalı bir yazı olmuştur. Doctrine üzerine bir iki yazı bence az kalır. Eğer hiç tecrübeniz yok ise, bana güvenin, Doctrine üzerine kitap dahi yazılır. Öyle bir iki örnekle, basitçe geçiştirilecek bir konu değil. Bende elimden geldiğince, detaylarda boğulmadan, bir kaç yazı daha Doctrine ile yazacağım. Bir sonra ki yazım da görüşmek dileği ile.