Symfony Expression Language

Yazıda Expression Language “EL” olarak geçecektir

Expression Language, kullanıcıların dinamik olarak ifadeleri değerlendirebilecekleri ve karmaşık mantıksal veya matematiksel hesaplamaları gerçekleştirebilecekleri bir araçtır.

Temel olarak string olarak yazılan bir ifadeyi çalıştırılabilir bir kod bloğuna dönüştürür.

Bunu nasıl yapıyor

Kullanıcının kendisinin yazdığı veya dinamik olarak oluşturulan bir ifade oluşturulur. Örneğin, bir string içinde yer alan bir ifade ya da bir değişkenin değeri gibi.

order.totalAmount > 100.00

Expression Language, oluşturulan ifadeyi analiz eder ve dilin syntax ve semantik kurallarına uygunluk kontrolü yapar. Burada AST (Abstract Syntax Tree – Soyut Sözdizimi Ağacı) kullanır.

Eğer ifade geçerli ise, Expression Language ifadeyi değerlendirir ve sonucu üretir. Örneğin, bir matematiksel ifadeyi hesaplar, mantıksal bir ifadeyi doğrular veya yanlışlar, ya da bir değişkenin değerini döndürür.

İfade değerlendirildikten sonra, sonuç üretilir. Sonuç, belirtilen ifadenin sonucuna bağlı olarak bir değer, bir boolean değeri, bir dizi veya başka bir tür olabilir. Varsayılan olarak gelen ifadeler.

Bazı Örnekler

Kendi sayfasında bir kaç basit örnek mevcut fakat daha farklı bir ifade yazmak istedim:

$expression = "array_filter(items,condition)";
$values = [
    "items" => [123, 442, 233, 214, 555],
    "condition" => "value < 250"
];
$expLang = new ExpressionLanguage(null, [new ArrayFilterProvider()]);
$result = $expLang->evaluate($expression,$values);
dump($result); //[123,233,214] değerlerini dönecektir

Burada expression ifademiz array_filter(items,condition) temelde EL componenti içerisinde bulunmuyor, bunu kendim oluşturup ekledim. Bu method iki değer alıyor biri items diğeri condition. $values arrayi içerisinde items ve condition tanımlamarı mevcut. array_filter çalışması için iki değeride alması gerekmektedir. Peki ArrayFilterProvider nedir? Bu genişletilmiş providerı kullanarak EL yeni özellikler kazandırabiliyor ve ihtiyaçlarım doğrultusunda kullanabiliyorum. Fakat items olarak vermem rağmen condition olarak belirttiğim EL ifadesinde value ifadesi mevcut bu ne işe yarıyor ve bunu neden $values arrayi içerisinde tanımlamadım. Çünkü ArrayFilterProvider da kendi içerisinde EL componentini kullanıyor.

class ArrayFilterProvider implements ExpressionFunctionProviderInterface
{
    public function getFunctions()
    {
        $expLanguage = new ExpressionLanguage();
        return [
            new ExpressionFunction('array_filter', function ($array, $condition) {
            }, function ($arguments, $array, $condition) use ($expLanguage){
                return array_filter($array, function ($value) use ($condition,$expLanguage){
                    return $expLanguage->evaluate($condition,[
                        'value' => $value
                    ]);
                });
            }),
        ];
    }
}

ArrayFilterProvider da kullandığım EL sınıfına value olarak condition ı veriyorum. Bu sayede filtre edilecek değerleride bir mantıksal ifadeye bağlayabiliyorum.

Promosyon uygulamalarını, bir nesne üzerinde yapılan işlemleri içeren örneği ise github repomda bulabilirsiniz.

Repodaki uygulama için gerekli açıklamalar yorum satırları olarak eklendi ve sadece örnekleri içermesi hedeflenmiştir.

Dezavatajları neler

  • Güvenlik Riskleri: Kullanıcıya EL ifadeleri yazmasına olanak sağlamak, kötü niyetli kişilerin (komik bir ifade) uygulamanızın işleyişine zarar vermesine veya kritik bilgilerin ele geçirilmesine sebebiyet verebilir. Bu yüzden kullanıcı sadece sizin belirlediğiniz kapsamda ifade yazmalıdır. Nihayetinde mantıksal ve matematiksel ifadeler dışında extra olarak providerlar eklemek durumunda kalacaksınız bu sebeple ciddi bir filtreleme ve doğrulama gerekmektedir.
  • Karmaşıklık: EL karmaşık ifadeler kullanımına oldukça uygundur. Örneğin count(array_filter(cart.getCartItems(),conditionOne)) ?discountCartItem(array_filter(cart.getCartItems(),conditionOne),discountOne) :count(array_filter(cart.getCartItems(),conditionTwo)) ?discountCartItem(array_filter(cart.getCartItems(),conditionTwo),discountTwo) böyle bir ifadenin anlaşılabilirliği ve okunabilirliği zordur. Bu sebeple dikkat edilmesi gerekmektedir(dinamik oluşturulan ifadelerde nasıl olunacaksa).
  • Performans Etkisi: EL, ifadeleri değerlendirmek için ek bir işlem adımı gerektirir ve bu, performans üzerinde bir etkiye neden olabilir. Özellikle büyük veri kümesi üzerinde ifadelerin değerlendirilmesi gereken durumlarda performans kaybı yaşanabilir. Bu nedenle, performans gereksinimleri yüksek uygulamalarda Expression Language kullanımı dikkatlice değerlendirilmelidir. Ayrıca cache kullanılarak daha önce değerlendirilen ifadeler hızlıca işletilir.
  • Hatalı Kullanım Riski: Doğru syntax kullanımı oldukça önemli. Canlı ortamda eğer bir konfigürasyon dosya üzerinde bir EL ifadesi yazıyorsanız bunu önce local de denemekte fayda var. Hatalı kullanıma oldukça müsait bir araç bu sebeple yazılan ifadelerin öncesinde mutlaka test edilmesi gerekmektedir. Dinamik olarak yazılan ifadeler için uygulama içerisinde ifadeyi bir ön test adımına sokup sahte veriler ile işletmek faydalı olacaktır.

Bir kaç fikir

Kullanıcının her hangi bir uygulamadan alınan bir objesi başka bir uygulama için kullanmak.

$modelOrder; // Api ile alınmış ve son sipariş bilgisini içeren obje
$modelMail; // Mail gönderimi için gerekli bilgileri tutan obje

$expressions = [
    'modelTwo.from = "test@test.com"',
    'modelTwo.to = modelOne.customerEmail',
    'modelTwo.subject = modelOne.orderId ~" "~" Yeni Siparis"',
    'modelTwo.content = modelOne.totalAmount ~" tutarında yeni bir sipariş var...."'
];

Gibi bir ifade ile bilinen iki obje arasında belirli kurallara göre (bu kurallar bir arayüz ile dinamik olarak oluşturulabilir) bir süreç işletilerek yeni bir mail objesi oluşturulması sağlandı.

Burada gerçekleşen bir işlem eğer şartı sağlıyorsa (bu şartı sağlayıp sağlamadığı yine EL ile kontrol edilebilir) başka bir işlemi şartı sağlayan objeyi kullanarak veya kullanmadan gerçekleştirebiliriz.

Promosyon, indirim gibi işlemlerde temelde cart, order gibi objelerde gerçekleşen bu süreçleri EL ile kullanıcının belirlediği şartlar ile gerçekleştirebiliriz.

Symfony kendi içerisinde; özellikle güvenlik ve yetkilendirme, rotalarda, twig şablonlarında, servis konteyner tanımlamalarında kullanmaktadır.

Kaynaklar