Symfony ile Serialization – 1

Bir geliştirici olarak hayatınızda illaki bir API oluşturmanız gerekmiştir. Bir API hazırlayabilmek için verilerinizi uygun formatlarda sunmanız gerekiyor. Dolayısıyla bir serialization yapmalısnız.

Eğer Symfony kullanıyor ve serialization yapmak istiyorsanız çoğu yerde JMS Serializer‘ı kullanmanız öneriliyor. Hatta Symfony resmi dökümantasyonunda bile bu bundle tavsiye edilmekte.

Açıkcası ben bir kaç projede kullanmama rağmen JMS Serializer’ı çok sevemedim. Güzel bir plugin ama kendi ihtiyaçlarınıza göre manipule edemiyorsanız sıkıntı var demektir. Ayrıca Normalizer’ı override edilemiyor ve yeniden kullanımı zahmetli. Bu sebepler beni JMS’den soğutmaya yetti.

JMS serializer size kullanımı kolay ve sorunsuz gelebilir. Her şey otomatik olsun diyorsanız elbette. Fakat, özel bir durum gerektiğinde ayarlamasını yapmak kolay olmuyor. Bu sebeble daha esnek bir çözüme ihtiyacınız var. Symfony’de zaten hali hazırda güzel bir serialization componenti var. İşte aradığınız çözüm bu.

Symfony’de zaten hali hazırda güzel bir serialization componenti var.

Symfony zaten bu probleme uygun içerikleri serialize etmenizi sağlayan serializer componentini geliştirdi. Belki kullanımı çok hızlı değil ama genişletilebilir ve esnek olması artı puan. Nedir bu seralizer diyenleriniz için kısa bir özet geçmek istiyorum. Bir seralizer 2 bölümden oluşur. Normalizer ve Encoder.

Normalizer: Objeleri array haline dönüştürmekle sorumludur. (Normalize/Denormalize).
Encoder: Array haline dönen yani normalize olmuş dataları istenilen formata (json, xml, csv, ..) dönüştürmekle sorumludur. (Encode/Decode).

Serializer’ı bir den fazla normalizer ve encoder ile oluşturabilirsiniz. Bu sayede farklı durumlarıda kullanılabilecek bir serializer oluşturursunuz. Daha fazla detaya inmeden bir dökümantasyona göz gezdirmenizi istiyorum. Bu sayede bundan sonraki kısımlarda ne yaptığımı daha iyi anlayabilirsiniz.

İş mantığını Normalizer ile belirlersiniz

Symfony serializer’ı temelde 2 encoder ile gelmekte. JSON ve XML. Sadece ikisi mi var diye üzülmeyin. İsterseniz kendi encoder’ınızı yazıp dahil edebilirsiniz. Örnek: CSV,…

Bir serializaton işleminde ana problem objelerin array’lere dönüştürüldüğü yani normalization kısmındadır. JMS kullanırken annotation’lar ile hangi alanların nasıl dahil edileceğini söylediğiniz gibi. Asıl mesele burda başlıyor. Veri nerede ve nereye nasıl koymak istiyorsun sorusuna cevap verebilmek. Bir objeyi istediğiniz biçimde seralize mı etmek istiyorsun? Yapmanız gereken sadece bu modele uygun bir Normalizer tanımlayın.

Özel bir Normalizer oluşturmak istediğinizde yapmanız gereken NormalizerInterface implement etmektir. 2 metod vardır. İlki supportsNormalization, “Bu sınıfı normalize edebilir misin?” sorusuna cevaptır. İkincisi ise normalize metodu, objeyi array’e nasıl çevirmeniz gerektiğini tanımlarsınız. Bir örnekle devam edelim:

<?php

namespace Acme\Serializer\Normalizer;

use Acme\Model\User;
use Acme\Model\Group;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;

/**
 * User normalizer
 */
class UserNormalizer implements NormalizerInterface
{
    /**
     * {@inheritdoc}
     */
    public function normalize($object, $format = null, array $context = array())
    {
        return [
            'id'     => $object->getId(),
            'name'   => $object->getName(),
            'groups' => array_map(
                function (Group $group) {
                    return $group->getId();
                },
                $object->getGroups()
            )
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function supportsNormalization($data, $format = null)
    {
        return $data instanceof User;
    }
}

Not: Eğer isterseniz burada bir mantık veya karmaşıklık ekleyebilirsiniz. Bu sayede modeli, serializer modelinden ayırdınız. Decoupling sağolsun :)) Normalization sonucu aşağıdaki gibi oldu:

<?php
[
    'id'     => 1,
    'name'   => 'Damla',
    'groups' => [1, 2]
]

Obje İlişkilerini Kontrol Etmek

Bir serialization işleminde belki en çok karşılaşılan durum, normalize ettiğiniz objenin ilişkili olduğu diğer objeleri kontrol etmektir. Normal şartlar altında sadece ana objeyi normalize edebilirsiniz. Aksi takdirde CircularReferenceException alırsınız. Mesela, aşağıdaki gibi bir üyeye bir organizasyonu ilişkilendirdiğinizde serializer componenti exception fırlatır:

$user = new User();
$user->setName('Damla');

$group = new Group();
$group>setName('German Teachers');
$group->setUsers(array($user));

$user->setGroup($group);

echo $serializer->serialize($group, 'json'); // CircularReferenceException

Korkmanıza gerek yok SerializerAwareNormalizer size yardımcı olacak. Implement ettiğiniz Normalizer sınıfı, SerializerAwareNormalizer sınıfını extend ederse, ana serializer’ı bir bağımlılık olarak algılayacaktır. Bu sayede diğer objelerin normalize etme işini bu sınıfa emanet edebilirsiniz. Şimdi örneğimizi güncelleyelim:

<?php

namespace Acme\Serializer\Normalizer;

// ...
use Symfony\Component\Serializer\Normalizer\SerializerAwareNormalizer;

/**
 * User normalizer
 */
class UserNormalizer extends SerializerAwareNormalizer implements NormalizerInterface
{
    /**
     * {@inheritdoc}
     */
    public function normalize($object, $format = null, array $context = array())
    {
        return [
            // ...
            'groups' => array_map(
                function ($object) use ($format, $context) {
                    return $this->serializer->normalize($object, $format, $context);
                },
                $object->getGroups()
            ),
        ];
    }
}

Tek yapmanız gereken ise Group objesini destekleyen Normalizer’ı yazmak:

<?php

// ...

/**
 * Group normalizer
 */
class GroupNormalizer extends SerializerAwareNormalizer implements NormalizerInterface
{
    /**
     * {@inheritdoc}
     */
    public function normalize($object, $format = null, array $context = array())
    {
        return [
            'id'   => $object->getId(),
            'name' => $object->getName(),
        ];
    }

    /**
     * {@inheritdoc}
     */
    public function supportsNormalization($data, $format = null)
    {
        return $data instanceof Group;
    }
}

Sonuç:

<?php
[
    'id'        => 1,
    'firstname' => 'Damla',
    'lastname'  => 'T',
    'groups'    => [
        [
            'id'   => 1,
            'name' => 'German Teachers'
        ],
        [
            'id'   => 2,
            'name' => 'Book Lovers'
        ],
    ],
]

Not: supportsNormalization metodu içinde şunu anlattık: Biz sadece bir obje yerine bir interface’i kontrol etmek istiyoruz. Bunu yaparak bir normalizer buna benzer şekilde modele sahip tüm modellerin üstesinden gelebilir.

Bu makalede serializer nedir, normalize işelmi nasıl yapılır onu anlatmaya çalıştım. Bir sonraki bölümde tüm serialization işlemininin davranışını belirleyen $context değişkenini ve bu işlemlerin bize olan faydasını anlatmaya çalışacağım.

  
  • Yorumunuz için teşekkür ederim.