Bir önceki yazıda View yani görünüm sınıfını her türlü şemayı kullanabilecek şekle dönüştürdük ve kullandık. Şimdi teker teker public dizini altında oluşturduğumuz index.html dosyasını parçalayalım ve tema dosyalarına dönüştürelim. Daha önceden oluşturduğumuz Template yani şablon klasöründe tek tek ismini vereceğim dosyaları oluşturun ve kodlarını içine kopyalayın.
Bu şablon bizim için basit, hiçbirşeyi olmayan bir html sayfasının kodlarını tutuyor. Burada dikkat ettiyseniz, $title ve $content değişkenlerini tanımlamışız. Bu sayede aynı temayı kullanarak istediğimiz sayfa başlığını ve içeriğini tanımlayabiliriz. Diğer dosyalarda da aynı yöntemi kullanacağız.
Burada yine aynı $title değişkenini kullandığımıza dikkat edin. <nav> etiketiyle belirlediğimiz link listesi kısmı şimdilik o kadar önemli değil. İstersek o bölümü de değişken bi eleman haline getirebiliriz. Hatta yapalım. aynı klasör içinde nav.php isimli bir dosya oluşturalım. İçeriğini şu şekilde değiştirelim:
Elemanımız links diye bir değişkene erişip onun href özelliğine ve textContent yani metin içeriğine erişmeye çalışıyor. Peki henüz böyle bir sınıf var mı? Hayır yok, çünkü daha yazmadım. Daha sonra bunun gibi elemanları da ayrı bir sınıf haline getireceğiz. Şimdilik header.php yazdığımız şekilde kalsın. Daha sonra bunun gibi elemanların sınıflarını yazmaya başladığımızda, varolan şemaları değiştirebiliriz nasılsa.
3. main.php
<main> <aside>
<?= $aside ?>
</aside> <section> <h2>
<?= $title ?>
</h2>
<?= $content ?>
</section> </main>
Burda yine bileşenin başlık ve içeriğini $title ve $content değişkenleriyle belirledik.
// İstediğimiz başlıkla sözlük sınıfından yeni bir nesne oluşturduk $dictionary = new MidoriKocakDictionary("Nesne Yönelimli Programlama Sözlüğü");
// Kelime sınıfından yeni bir nesne oluşturduk. $nesne = new MidoriKocakEntry('nesne', 'aklımızın dışındaki herşey');
// kelimeye başka açıklamalar ekledik. $nesne->addValue('harika bişi'); $nesne->addValue('ingilizce object');
// Kelime sınıfından istediğimiz verilerle başka bir nesne türettik. $şey = new MidoriKocakEntry('şey', 'ismi olmayan nesne');
// bu iki kelimeyi sözlük nesnesine ekliyoruz. // Bu sayede sözlük kelime nesnelerine erişebiliyor. $dictionary->addEntry($nesne); $dictionary->addEntry($şey);
// View yani görünüm sınıfından yeni bir nesne oluşturduk. $dictionaryView = new MidoriKocakView();
// Görünüm sınıfı içindeki data dizisine dictionary anahtarıyla sözlük değişkenimizi ekledik. // Bu sayede, dictionary.php adlı şablon dosyasına, $dictionary yazan yerde, bu nesneye erişim // sağlanacak. Örneğin $dictionary->getTitle() gibi. // Aslında şablonların sınıflara bu şekilde erişmesi doğru olmayabilir, ancak şimdilik böyle bırakalım. $dictionaryView->set('dictionary', $dictionary);
// Görünüm yani view sınıfımıza dictionary.php tema dosyasını kullanarak içeriği vermesini söyledik. // Bu gelen içeriği dictionaryContent adlı bir değişkene atadık ki daha sonra bu veriyi kullanabilelim. $dictionaryContent = $dictionaryView->render('dictionary');
// Elemanları oluşturmaya başladık // Yine görünüm sınıfından yeni bir nesne türettik ve değişkenlerini belirledik. // Burada header yani üst bilgi kısmını oluşturduk. $header = new MidoriKocakView(); $header->set('title', 'Sözlük Uygulaması'); $headerContent = $header->render('header');
// Main elemanını oluşturduk ve değişkenlerini belirledik. $main = new MidoriKocakView(); $main->set('aside', 'aside'); $main->set('title', 'Sözlüklerim'); $main->set('content', $dictionaryContent); $mainContent = $main->render('main');
// Son olarak page yani sayfa diye bir değişken oluşturduk.
$page = new MidoriKocakView();
// Önceki elemanlarda olduğu gibi değişkenleri belirledik. $page->set('title', 'Sözlük Uygulaması');
// Buraya dikkat, burada 3 ayrı hazırlanmış temanın içeriğini birbirine ekledik // Ve bunu content olarak tanımladık ki, layout.php'deki $content değişkeninin // bulunduğu yerde bu birbirine eklenmiş 3 içerik görünsün. $page->set('content', $headerContent . $mainContent . $footerContent);
// layout.php'yi yani sayfa düzeni şablonunu kullanarak çıktı aldık. echo $page->render('layout');
} catch (Exception | Error $e) { echo 'Error on line ' . $e->getLine() . ' in ' . $e->getFile() . ': <b>' . $e->getMessage(); }
Bu kodu web sunucumuzu kullanarak çalıştırdığımızda şöyle bir görüntüyle karşılaşmalıyız.
Aside kısmında sadece aside kelimesini görüntüledik. Bu bir hata değil.
Şimdi kodumuzu satır satır inceleyelim. $dictionaryContent = $dictionaryView->render(‘dictionary’); ifadesine kadar herşey önceki yazıda yaptığımız şeylerle aynı. Önceki yazıda kodlarımızda yorumlar yoktu ancak ben daha kolay anlaşılması için bunları ekledim. Bu kod bloğu içinde daha iyi olabilecek şeyler var. Bunları tek tek anlatacağım. En baştan başlayalım.
Composer aracı, Programımıza yeni bileşenler eklemek, bileşenleri yönetmek ve kolayca kullanmak
app.php dosyasını açtığımızda ilk karşılaştığımız satırlar şu şekilde. Yanlış değil de daha temiz şekilde yapılabilecek şeyler var.
Buradaki sorun, ihtiyaç duyduğumuz bütün sınıfları tek tek dosyanın üzerine yazmak zorunda oluşumuz. Biz 5 adet sınıf kullandığımız için bu pek sorun değil. Ancak çok kapsamlı bir yazılımda onlarca kütüphaneye ihtiyaç duyduğumuzu düşünürsek, atıyorum 64 adet dosyayı buraya tek tek yazmamız zor. Ayrıca sınıfları dosya isimleriyle tek tek çağırmak beni mutlu etmiyor. Bir de, çağırdığımız dosyaların yeni mi, eski mi, programda eriştiğimiz şekilde hata oluşturmadan çalışabileceklerinden emin olamıyoruz. Çünkü başka bir programcı girip o dosyayı darmadağın edebilir. Bu meseleyi composer denen vazgeçilmez aracı kullanarak hallediyoruz. Composer programını tıpkı telefondaki uygulamaları yüklediğiniz AppStore, PlayStore gibi programlar gibi düşünebilirsiniz. Composer sayesinde yazılımınızın ihtiyaç duyduğu kütüphane, uygulama ve sınıfları otomatik olarak ihtiyaç duyduğunuz paket isimlerini sürümleriyle beraber programınızın kök dizininde oluşturacağınız composer.json dosyasına yazacaksınız. composer install komutunu çalıştırdığınız anda composer programı, belirlediğimiz paketleri vendor dizinine göre sürüm numaralarını kullanarak indirecek, ve autoloader.php adında bir dosya oluşturacak. Biz yukarıdaki gibi bağımlılıkları tek tek yazmak yerine sadece bu autoloader.php dosyasını programımızda require komutu ile çağıracağız ve ihtiyaç duyduğumuz uygulama, sınıf ve kütüphanelere tek tanımlamayla erişebileceğiz. Ayrıca bileşenleri güncellemek istediğimizde bunları her bileşenin kodunu kaynağından indirip kopyala yapıştır yapmayla uğraşmak yerine otomatik olarak yapabileceğiz.
Composer Kurulumu
Windows ortamında çalışıyorsanız Composer aracını kurmak için en kolay yöntem https://getcomposer.org/Composer-Setup.exe dosyasını indirip çalıştırmak. Linux ve Mac ortamında çalışıyorsanız, https://getcomposer.org/installer dosyasını indirip, terminal üzerinde `php installer`diyerek çalıştırmanız gerekiyor. Program sizin ortamınıza göre gerekli düzenlemeleri yapıp kurulumu otomatik olarak gerçekleştiriyor. Daha çok ayrıntı için https://getcomposer.org/doc/00-intro.md sayfasına bakmanızı öneririm.
PHP için AppStore: Packagist
Composer ile kullanabileceğimiz diğer bir eşsiz araç ise Packagist.org. Packagist.org sitesi sayesinde istediğimiz konuyla ilgili herhangi bir kütüphane veya uygulamaya kolaylıkla erişebiliyoruz.
Packagist.org
Packagist.org sitesini, Android telefonlardaki Google Play Store veya Apple telefonlardaki AppStore uygulamalarına benzetebiliriz. Kayıtlı tüm php kütüphane, sınıf veya uygulamalarının güncel ve geçmiş sürümlerine buradan ulaşabilir, kelime bazlı arama yapabiliriz. Composer ile bir bileşeni kendi uygulamamızda kullanmak istediğimizde, composer aracı, girdiğimiz uygulamanın isminin packagist.org üzerinde kayıtlı olup olmadığına bakar, ve kayıtlı ise programımızın kök dizininde vendor adlı bir dizin oluşturur. Packagist üzerinde kayıtlı olan eklemek istediğimiz bileşeni, çoğunlukla github üzerinden indirir ve vendor dizinine kaydeder. Ayrıca vendor dizini altında autoload.php adlı bir dosya oluşturur. Biz de kendi uygulamamızı kullanırken tek tek bütün dış veya kendi yazdığımız bileşenleri require ile yukarıda yaptığımız gibi çağırmak yerine, autoload.php require komutu ile çağırarak tek bir seferde erişebiliriz.
Composer kullanımı
Şimdi composer’i nasıl kullanacağımıza bir göz atalım. Windows üzerinde CMD ile, Mac veya Linux ortamında komut satırı ile composer’i composer komutunu kullanarak çağırabildiğimizi farzediyorum.
Şu ana kadar yazdığımız sözlük programının dizini şu şekilde görünüyor olmalı:
Eğer programı şu ana kadar web sunucusunu kullanarak çalıştırabildiyseniz, bir iki dosya farklılığı olması o kadar önemli değil. Yazdığımız sözlük programının dosyalarının olduğu dizine gidelim ve o dizinde şu komutu çalıştıralım:
$> composer require phpunit/phpunit
Burada require ifadesiyle programımızın phpunit adlı bileşene ihtiyaç duyduğunu belirtiyoruz. phpunit nedir diyorsanız, programımızı test etmemize yarayan vazgeçilmez bir test aracı. Ona da bir sonraki bölümde detaylıca değineceğim. Composer vendor adlı bir dizin oluşturacak ve tek tek gerekli bileşenleri o dizine indirecek. İndirmesi, internet bağlantınızın hızına göre uzun sürebilir, telaşa kapılmayın. İşlem bittikten sonra ekrana şöyle bir görüntü gelmesi gerek. Eğer hata aldıysanız, dosya dizin izinlerinizi kontrol etmenizi öneririm. Ayrıca composer kurulumunuzda da sorun olabilir.
Görüntü bu şekildeyse sorun yok.
Şimdi sözlük programımızın olduğu dizine tekrar bakalım.
Composer’den sonra
1 adet yeni dizin ve 2 yeni dosya oluştuğunu görüyoruz. Bunları tek tek anlatayım:
Vendor dizini
Vendor dizini programımızın ihtiyaç duyduğu ve composer tarafından indirilen bileşenlerin tutulduğu dizindir. Ayrıca indirdiğimiz bileşenlerin getirdiği ek araçlar varsa onlar da bu dizinde tutulur. Ayrıca herşey indirilip bittikten sonra, autoload.php adlı, bizim tüm bileşenleri kullanıcı programımıza eklememizi sağlayan dosyayı da oluşturur composer. Şimdi vendor dizinine bir göz atalım:
Vendor dizini içeriği
Burada bin, komut satırı, terminal ya da cmd’den çağırabileceğimiz araçları içeren dizindir. Örneğin, phpunit aracı buraya yüklenmiş durumda. İhtiyaç duyduğumuzda buradan çağırabiliriz.
Composer dizininde composer’in kendi ihtiyaç duyduğu dosyalar bulunuyor. Bu dosyalar autoload.php tarafından erişiliyor. Bence fazla kurcalamaya gerek yok. Üzümünü ye bağını sorma demişler yani information hiding. Ancak composer’in nasıl çalıştığını merak ediyorsanız dosyaların içini açıp bakabilirsiniz. Açık kaynağın güzelliği de burada.
Diğer tüm dizinler, phpunit aracının ihtiyaç duyduğu ek kütüphaneler ve uygulamaları içeriyor.
Burada composer.json dosyasını inceleyeceğiz ancak json formatından bahsetmemiz gerekiyor. Eğer json formatını biliyorsanız JSON bölümünü atlayabilirsiniz.
JSON
JSON kısaltması “JavaScript Object Notation” yani Javascript Nesne Gösterimi anlamına geliyor. Javascript’te nesneleri nasıl tanımlıyorsak, json dosyalarını o şekilde biçimlendiriyoruz. Son zamanlarda JSON biçimi, internet üzerinden veri gönderip almada standart hale geldi diyebiliriz. Çünkü daha önce kullanılan xml formatı aynı veriyi tanımlamak için daha uzun dosyalara ihtiyaç duyuyor. Örnek vereceğim ama önce json’u tanımaya devam edelim.
JSON dosyaları Nesne veya Dizi şeklinde şeklinde olabilirler. Biz nesne tanımlama ile başlayalım. Buradaki JSON örneklerini data.json diye kaydedip istediğimiz json dosyasında hata almadan kullanabiliriz.
{
}
Burada boş bir JSON nesnesi oluşturduk. Eğer https://jsonformatter.curiousconcept.com/ adresine gidip bu kodu yazarsak, kodun valid json olduğunu göreceğiz. Siz de json örnekleri oluşturmak istediğinizde, kodunuzu kontrol etmek için bu aracı kullanabilirsiniz.
Json kodunu test etmek
Json nesneleri verileri “anahtar”:değer şeklinde tutarlar.
Örneğin bir burada çift tırnak kullanarak tanımladığımız kelimeler, anahtar isimlerini belirtirler ve benzersiz olmak durumundadırlar. Yani aynı kişinin birden fazla soyadı alanı olamaz. JSON aslında PHP’de Dizi/Array veri yapısına benziyor bir bakıma.
Değer kısmında kullanabileceğimiz veri tipleri ise kısıtlı. Bir JSON değeri, bir metin, bir sayı, bir nesne, bir dizi, doğru/yanlış şeklinde boolean değer ve null tipinde olabilir. Json formatında değerler javascript nesnelerinde olduğu gibi fonksiyon, tarih ve undefined yani tanımsız tipinde olamazlar. Olurlarsa, JSON okuyan program hata verir. Kodumuz patlar ağlarız. Kaynak: https://www.w3schools.com/js/js_json_syntax.asp
JSON ayrıca veriyi dizi yani array olarak kaydetmemize de imkan veriyor.
Burada metin tipindeki verileri dizi olarak json dosyasına kaydettik. Dikkat. Tek bir json dosyasında, tek bir nesne ya da tek bir dizi olmalı. Eğer birden fazla nesneyi tek dosyada tutmamız gerekseydi nesneleri dizinin içinde tutacaktık.
Şimdi JSON’un nasıl tanımlandığını ve söz dizimi yapısını kısaca anladığımıza göre PHP’de nasıl kullanıyoruz ona bakalım. PHP’de json için kullandığımız iki fonksiyon mevcut. Bunlar json_encode() ve json_decode() yöntemleri. json_encode, bir diziyi geçerli bir JSON metnine dönüştürür. json_decode fonksiyonu ise geçerli bir json metnini, dizi ya da nesneye dönüştürür. Örneğin, $array = json_decode($jsonString, true) dediğimizde json verilerine birebir sahip olan bir php dizisi elde ederiz. Eğer true kullanmadan doğrudan $object = json_decode($jsonString) deseydik, elemanlarına $object->name gibi ifadeler kullanarak erişebileceğimiz nur topu gibi bir nesneye sahip olacaktık. Nesnelerin saklanması, metine çevirilmesi, ekrana basılması, klonlanması ve uyandırılması (metinden tekrar nesne üretilmesi) gibi konulara daha sonra detaylıca değineceğim.
Daha sonra programlar ya da sınıflar arası ya da internet üzerinde veri alışverişi yaparken JSON verisini PHP ile bol bol kullanacağız. (Anahtar kelimeler: AJAX, curl, stream, request, response, JS:promise)
JSON kısmını anladığmıza göre composer.json dosyasına geri dönelim.
composer.json
Composer.json adlı dosya, ithiyaç duyduğumuz bileşenlerin çetelesini tuttuğumuz liste aslında. Açıp bakalım.
{ "require": { "phpunit/phpunit": "^6.0" } }
Burada require olarak tanımlanan değer programımızın ihtiyaç duyduğu bileşenleri listelememize yarıyor. phpunit adlı github kullanıcısının yine phpunit adlı kütüphanesine erişmek istediğimizi belirtmişiz. Değer olarak belirlediğimiz ifade ise ihtiyaç duyduğumuz paketin versiyonu. “^6.0” ifadesiyle 6 ve 7 sürümleri arasındaki herhangi bir sürüm işimizi görür demişiz. Geçmiş bir sürüme ihtiyaç duysaydık, onu da burada ifade edecektik.
Örneğin programımızı birine göndermek isteseydik, vendor klasörünü doğrudan silip sadece kendi yazdığımız kaynak dosyalarını ve composer.json dosyasını daha kısa sürede gönderebilirdik. Bu sayede gönderdiğimiz kişi composer install komutunu kullanarak bileşenleri ayrıca yükleyebilir.
composer.lock
Bu dosya composer aracı tarafından otomatik olarak oluşturulan bir dosya. Vendor dizini içine kaydedilmiş bütün bileşenlerin sürüm numaralarının kaydedildiği bir dosya. Bununla ilgili Davey Shafik şu adreste konuyu şöyle açıklamış:
composer install çalıştığında:
composer.lock dosyası ver mı yok mu bakılır.
Yoksa composer update çalıştırılır ve bir adet yaratılır.
Bu iki dosya ve dizini de anladığımıza göre composer’in nasıl yüklendiğini ve composer ile nasıl bileşenlerin programımıza indirildiğini anlamışız demektir. Bir sonraki yazıda, app.php kodumuzu satır satır incelemeye devam edeceğiz ve işleri nasıl daha iyi yapabileceğimize bakacağız.
2016 December was the worst month of my life. I had a break-up, had physically assaulted by my father for being a transgender woman and lost my job. All of them happened in the same two month period. I had the deepest kind of depression I’ve ever had. Also, psychosomatic pain accompanied all of this.
For the first two weeks after the attack, I did not escape from my bed. As a depressed person, I was used to suicidal thoughts in my mind, but I don’t remember a time when I was closer than this. Being a woman is hard, being a transgender woman is too, and being all of these and having dreams is more difficult.
I applied circa 120 jobs since I quit my job in September 2016, and in this period none of them returned except some invitations to job interviews. One day I was invited to a two-day job trial in Berlin, an all male department and witnessed that they were checking Facebook profiles of woman candidates and making fun of their profile pictures. I was used to the gender bias in office, being yelled for things that male employees were welcomed with compassion, but that was the most extreme case. My father invited me to work with him in the family business, but take heart from my unemployment, for harassing and humiliating and at the end physically attacking.
I was miserable at all, procrastinating, checking only the Facebook and doing nothing. I lost keeping track of hours and days. I lost my appetite; I felt useless and worthless.
I was doing nothing. Some days I was drinking alcohol and getting drunk and feel embarrassed. I was writing whining posts to Facebook and felt the pity in the people’s responses. Worse than that I was wallowing in self-pity. But one day I found out something different. I could continue to live like this and stay ruined every day. Nothing was changing. So, I thought, if nothing changes, and my negative inner voice tells me that nothing is going change with no matter I do, I can do something different without expecting anything. That day, I washed dishes, took an appointment with my therapist, and accepted some guests from the CouchSurfing to force myself to clean the whole house. I was still procrastinating and feeling embarrassed because of procrastination in the Facebook. When I talked about this with my therapist, she told me that it was completely normal.
I asked, “How?”
She said that the procrastination was our brain’s coping mechanism to deal with hard to cope situations. So I understand that I was expecting many things from myself, be perfect, don’t be emotional, be social, fulfil your life, etc. And I decided to take it easy on myself. This video also helped a lot to understand it.
I deleted my facebook account. Not deactivated, but deleted. I was uncomfortable with the idea of myself as a sellable data mining asset. I didn’t like to be alone at home crying, without any message or phone call, but my friend list bloated with 250 people. If someone cares about me, can quickly send me a “How are you?” message. But you cannot expect that effort from the most of your Facebook friends. I was also uncomfortable with all that stupid news, lies, scandals, Donald Trump, Brexit, bombings in Europe, immigration crisis and all of the nonsense fanaticism. Sharing something about some topic you care is doing nothing and deluding yourself that you are doing something tactile, but instead of this, you are just making yourself feel comfortable. One paragraph or 140 character limit of digestible bites and trying to be satisfied with those facebook or twitter posts without any well-thought-out idea or research aiming to create the most impact concludes to extreme trolling. So I was tired of it. Only one week passed, but I feel much better.
I restarted to my research about object oriented programming. I read about physical, philosophical, language meanings of what was an “object”. Last year I was working in a video on demand project with no documentation and code commenting (because my supervisor believed that readable code does not have comments –I don’t agree at all–) with an excellent object oriented language called Haxe that compiles to Javascript to generate React components. I even write objective-C and Android code in Java for our Cordova plugins but no PHP code at all other than my toy projects. We had classes with 5–6 ancestors that required an annoying struggle to find a method’s implementation. (Alt-Enter for JIdea) I was questioning practices and methodologies, but no one other than me seem bothered. Remember the Berlin company douche-bags who were stalking the women candidates Facebook profile pictures? They were using Angular 1.something with SWIG (Some Javascript counterpart of TWIG) code that generates a god controller in Javascript wrapped with HTML, SQL and CSS code. That was the most intense spaghetti practice I’ve ever seen in my life. What were all of those developers in a million dollar multinational companies thinking? How they were this successful and I was feeling non-perfect all the time and have to memorise this book, to be hired? Most of the technology companies and their codebase concede humanity, compassion to their developers and future users to achieve the fastest delivery of the product.
In some job interviews, they ask me what I expect from their company. I tell them that I expect politeness, people talking and helping each other and say hello and good-bye during the day. Why do you rent this office and hiring all of the employees to work together in same space instead of outsourcing all of your IT department with different freelancers based in various countries all around the world? I don’t think most of the HR or IT professionals thought on it.
While I was researching about object oriented programming, I wanted to dig deeper from programming to maths and from there philosophy. What was an object? What was a thing? Dictionaries tell that everything other than our thinking mind is an object, an input for our thought mechanism. What is thinking? How do we think? My head was full of those kind questions. How we distinguish objects? What separates objects or things (“the thing” is an unnamed object) from the world that surrounds them? In that rabbit hole, I found out that the reality is not perfect. In a perfect world 1/n never becomes 0, when n goes to infinity. In real life it does. The perfection deludes us that we are never going to achieve our goals or things we want to obtain and there always be a gap. Remember the Xenon’s paradox? He proved that the arrow never reaches the target, dividing the distance in two to infinity. However, in real life, we arrive a point that can’t be divisible anymore. So there is no dividing to infinity. The infinity tells us that there is an infinitesimal thing somewhere, but we don’t know where. Infinite is nothing, other than our uncertainty and inner insecurities. So the objects and the space around them is finite and exist. No matter how much we try to measure, we cannot know exactly its position with a zero error rate, but we can know the range of its boundaries. If this sounds incomprehensible, think that you can never touch something with your finger. The atoms around the ends of your finger and the object you touch will repel each other. There always be some space between your fingers and the object.
I know I rambled a lot in this article, but I think becoming aware of the imperfect word was the biggest gift of December 2016. There are bad things and there always going to be, instead of being depressed and losing my faith to everything, I embrace them, try to understand the imperfection, giving up trying to be perfect and do whatever I can, without expecting anything more than enjoying the exact moment.
Web sayfalarında veya basılı mecralarda, hiçbir zaman veriyi, yazıyı resmi olduğu gibi okuyucuya sunmuyoruz. İnsanların yazıları rahatça okuyabilmeleri için, ya da web sayfamızda kaybolmadan dolaşabilmeleri için sayfamızdaki verileri, belirli bir düzen kullanarak sayfaya yerleştirmemiz ve biçimlendirmemiz gerekiyor. Burada web tasarımı yapmanın ilkelerine girmeyeceğim, ancak merak ediyorsanız bu konuyu araştırabilirsiniz.
Örneğin dümdüz veriyi ekrana bastığımızda, bunlardan bir anlam çıkarmak neredeyse imkansızdır.
Arjantin Enflasyon Verisi
Bu yüzden bu verileri mesela grafikler kullanarak anlamlı hale getirmeye çalışırız.
Arjantin Enflasyon Grafiği
Yani amacımız sadece verilerimizin güzel gözükmesi değil, anlaşılabilir olması, uygulamamızın kolay kullanılabilmesi, okunaklı olması, belli amaçlar için aynı veriyi farklı şekilde görüntüleyebilmemiz (yeniden kullanılabilirlik) de aynı zamanda. Sayfa düzeni, veya herhangi bir şeyi tasarlarken, hatta kod yazarken kullanıcıların kolay kullanımını aklımızda tutmamız çok önemli. Ancak kolaylık, basitlik konusunda aklımızda tutmamız gereken diğer bir ilke ise şu:
Her şey olabildiğince basit olmalıdır, ama olabildiğinden daha basit olmamalıdır. Albert Einstein
Bu alıntıyı yapmamdaki sebep şu, siz işleri gelecekteki kullanıcılar kullanacak diye basitleştirmek ve mükemmelleştirmek için uğraşabilirsiniz. Uygulamanın, tasarımın ya da kodun, şurasını veya burasını iyileştirmek için günlerce aylarca uğraşabilirsiniz, ancak mükemmelleştirmeye çalışırken asıl amacı gözden kaçırabilirsiniz. Albert Einstein burada buna parmak basıyor. Bu hatayı 10 yıllık profesyonel yazılım hayatımda çok yaptım. Bu yüzden bu konuyu sürekli aklınızda tutmanızı, mükemmel değil, ama temiz, düzgün, çalışan ve daha sonra düzeltebileceğiniz incelikli prototipler üretmenizi öneririm.
Şimdi, dönelim tekniğe. Daha önceden Dekoratör tasarım deseninden bahsettik, yani bir veriyi alıp HTML etiketlerine sarmalayıp onu temiz bir web sayfası olarak kullanıcıya sunuyorduk. Bir önceki yazıda bunu yaptık. Şimdi veriyi değil de, bu ürettiğimiz bileşenleri de sarmalayabilecek bir yazılım yazalım. Yani Sayfa bileşeni, yan bilgi, ana bilgi, alt bilgi, form gibi bileşenleri barındırsın ve bunları gerektiğinde LEGO gibi birbirine ekleyip çıkarabilelim. Bu tasarım mantığına bileşen tabanlı geliştirme deniyor. Web Components Standardı, React.js, Vue.js bu ilkeyi uygulamalarımızda kullanmamızı sağlayan teknolojiler. Kitabın sonuna doğru bu teknolojilerden de detaylı olarak bahsedeceğim.
Önce daha önce şurada bahsettiğim bileşenleri tek tek listeleyelim.
Head — Her sayfada olacak ama görünmeyecek, css gibi ihtiyaç duyduğumuz dosyaların listesini tutacağımız bileşen. <head> etiketi içinde yeralacak. Sayfanın başlığını belirlediğimiz <title> etiketi burada olacak.
Üst Bilgi — Bütün sayfalarda değişmeyen, logo, üst navigasyon yani yön bulma linklerini, eğer varsa fb veya Twitter’daki gibi üst arama çubuğunu barındıracak, gerekiyorsa kullanıcının çıkış yapıp kendi profilini göreceği açılır menüleri tutacak bileşen. <header> etiketi ile tanımlanıyor.
Yan bilgi — Opsiyonel bir bileşen, genellikle içinde olduğumuz program bileşeninin varsa iç bölümlerinde gezinmemize yarar, yani bu bölümleri link olarak listeler. Bazı programlarda, o an programı kullanan kullanıcı ile bilgileri de listeleyebiliyor.
Ana bilgi — Buna main de deniyor ve <main> etiketi ile tanımlanıyor. Her sayfada bir adet bulunmak zorunda. Eğer bir sayfada önem olarak birbirine eşit farklı bölümler kullanacağım diyorsanız, <section> yani bölüm etiketini kullanacaksınız.
Alt Bilgi — Yine bütün sayfalarda değişmeyecek olan alt bilgi kısmı. Genellikle, sayfaların copyright, telif, gizlilik ve kullanım sözleşmesi gibi sabit kısımları, varsa şirket adresi iletişim gibi bilgiler burada yer almalıdır. <footer> etiketi ile tanımlanır.
Yeniden kullanılabilen bileşenler. Bunlara eleman / element ya da bileşen / component de denebiliyor. Lego gibi programlama amacında olduğumuzdan bu konunun mantığını anlamanız çok önemli. Bunu en kolay şu şekilde anlatabilirim. Örneğin, facebook’ta kendi duvarımızda yeni birşey paylaştığımızda ya da herhangi bir arkadaşımızın duvarında veya bir grupta birşey paylaşmak istediğimizde aynı form bileşeni ortaya çıkıyor ve biz bişiler yazıp gönder tuşuna basıyoruz.
Duvara bişiler yazarken
Gruba bişiler yazarken
İşte bu elemanı (ya da bileşeni) tekrar tekrar yazmamıza gerek yok, belli özelliklerini değiştirip, başka bir sayfada çağırıp kullanabilmemize yeniden kullanılabilirlik deniyor. İşte nesne yönelimli programlamanın bir diğer faydası da bu. Bu konuya da detaylıca sayfa kodunu yazmaya başladığımızda değineceğim.
Yeniden kullanılabilirlik, kod tekrarını önler
DictionaryView ve EntryView isimleriyle yarattığımız dosyaların birbirine benzediğine dikkat edin demiştim. Bir önceki yazıda kod tekrarını önlemekten bahsetmiştim. Şimdi gelin bu iki dosyadan kurtulalım. Program dizinimizin içinde View.php adlı bir dosya oluşturalım ve içeriğini şöyle değiştirelim:
<?php namespace MidoriKocak;
class View { private $data;
public function __construct() { $this->data = []; }
public function set() { $args = func_num_args(); if ($args == 1 && is_array(func_get_arg(0))) { $this->data = func_get_arg(0); } elseif ($args == 2 && is_string(func_get_arg(0))) { $this->data[func_get_arg(0)] = func_get_arg(1); } else { throw new InvalidArgumentException('Cannot set variable for View.'); } }
public function render(string $filename) { extract($this->data);
Her zaman yaptığım gibi kodu tek tek açıklamaya başlayalım. Daha önce her görünüm sınıfı için $this->dictionary veya $this->entry gibi değişkenler kullanarak, görünüm sınıfımıza veri enjekte ediyorduk. İki görünüm sınıfında da render, template ve __construct metodları değişken isimleri hariç aynıydı. Bunun yerine tek bir sınıf kullanıp işimizi halletmek için data adlı bir dizi değişkeni belirledik. __construct metodunda yani new diyerek sınıftan bir nesne yarattığımız anda, php yorumlayıcısı bizim için bu boş diziyi anında oluşturacak.
Asıl hinlik cinlik yaptığımız kısım set yani değişkenleri belirlediğimiz metod. Bu metod Görünüm yani View sınıfına göndermek istediğimiz, template dosyalarımızın kullanacağı değişkenleri enjekte etmemize yarıyor. Yani örneğin bir template yani şema dosyasında $renk adlı değişken tanımlamak ve bu değişkene “pembe” değeri vermek istersem bunu $view->set(‘renk’,’pembe’) diyerek yapabilirim. Peki birden fazla değişkeni aynı anda bir dizi kullanarak göndermek istersem? Bunun için de metoda sadece $view->set($array) ifadesini kullanarak $array ismindeki değişkeni göndereceğim. Peki metodu bu iki farklı parametreyle çağırmamızı sağlayacak olan yöntem ne? Birincisi opsiyonel parametreler kullanmak yani metodu şu şekilde tanımlamak:
public function set($value, $name = null)
Ancak bu şekilde metod ismi ve parametlerin isimlendirmesi anlamsız ve okunaksız oldu. Çünkü ikinci name yani isim parametresini opsiyonel tanımladığımda, $value adlı değişken dizi olmak zorunda. Ancak ben $value değişkenini sadece tek bir değişkenin değerini belirlemek için ifade ediyordum. Bunun yerine PHP de uzun süredir bulunan variadic yani parametlerinin sayısı değişebilen metod kullanmayı tercih ettim.
Bunu da metod çağırıldığında parametrelerin sayısını veren func_num_args() metodu ve sırasına göre metoda gönderilmiş parametrenin değerine değişken ismi kullanmadan erişmemizi sağlayan func_get_arg() yöntemlerini kullanarak sağladım. Bu tarz metodların, C ve C++ dilinde komut satırı uygulamaları yazılırken kullanıldığını görmüşsünüzdür. Çünkü tek bir program komut satırından çağırılırken, kaç parametrenin kaç değer alacağı önceden bilinmiyor. Örneğin diyelim ki composer adlı programı şu şekilde çağırabiliriz.
$> composer help --format=xml list
Burada help, programa verdiğimiz birinci parametre, format=xml ikinci parametre ve list de üçüncü parametre oluyor. Linux ortamına alışkın değilseniz, ya da Windows üzerinde cmd.exe kullanmadıysanız buraları anlamamanız gayet doğal. Sorun değil, öğrenmesi de zor değil. Tavsiye ederim.
Render metodu daha önceki görünüm sınıflarıyla aynı. Tek fark extract metodunu kullanmamız. buradaki extract($this->data) ifadesiyle, örneğin, data dizisi içinde ‘renk’=>’pembe’ değerinde bir eleman tanımlandıysa, buradaki değişkeni doğrudan echo $renk; diyerek kullanabiliyoruz mesela. Dikkat etmemiz gereken en önemli nokta, kullanıcıdan gelen hiçbir veriyi buraya temizlemeden sokuşturmamamız. Yani bu metoda parametre olarak girecek her tür kullanıcının form üzerinden girdiği veriye htmlspecialchars() metodunu kullanarak müdahele etmemiz gerekiyor yoksa 14–15 yaşındaki veletler sistemimizi hem çok pis hacklerler, hem de bizimle dalga geçerler. Bu konudan da güvenlik bölümüne vardığımızda detaylıca bahsedeceğim.
Şimdi app.php dosyasını açalım ve şu şekilde değiştirelim:
} catch (Exception | Error $e) { echo 'Error on line ' . $e->getLine() . ' in ' . $e->getFile() . ': <b>' . $e->getMessage(); }
Dikkat ettiyseniz artık DictionaryView ve EntryView Dosyalarına ihtiyacımız kalmadı. Bunun yerine sadece tek bir sınıf kullandık.
App.php’yi web sunucumuzu kullanıp açtığımızda, tek bir görünüm sınıfının işleri başarıyla yerine getirdiğiniz göreceğiz.
Bu yazıda son olarak dikkat etmenizi istediğim bir satır da şu:
} catch (Exception | Error $e) {
Burada Error ifadesini de catch bloğuna ekledik ki, sadece exception tipinde değil, error tipinde de hataları yakalayalım. Eğer bunu yapmasaydık ve diyelim ki app.php içinde $dictionaryView->set(‘dictionary’, $dictionary); yazmayı unutsaydık, yani template dosyasında olmayan değişkenlere erişmeye çalışsaydık PHP yorumlayıcısı şu şekilde isyan bayrağını çekecekti.
Bunun yerine Error $e diyerek istisnaların yanında hataları da aynı şekilde yakalamak istediğimizi belirttik ve kullanıcının doğru düzgün bir hata mesajı görmesini sağladık:
Bu sayede hatanın nerde olduğunu kolayca bulup onu böcek gibi ezebiliriz. Şimdilik bu kadar. Bir sonraki yazıda bileşenleri birleştirip bir sayfa oluşturacağız (nihayet).
Bir önceki yazıda View yani görünüm sınıflarından bahsettik ve EntryView adında bir sınıf oluşturduk. Veriyi HTML kodlarıyla render metodunun içinde sardık ve sunumunu yaptık. Şimdi aynı render metodunu bir template yani şablon metodu kullanacak şekilde değiştirelim.
<?php
namespace MidoriKocak;
class EntryView { private $entry;
public function __construct(EntryInterface $entry = null) { if ($entry !== null) { $this->entry = $entry; } }
public function setEntry(EntryInterface $entry) { $this->entry = $entry; }
public function render() { if (!isset($this->entry)) { throw new Exception('Cannot render without entry'); }
return $this->template($this->entry); } }
Bu kodda, önce private yani özel bir template metodu oluşturduk. Dikkat ettiyseniz Template metodu içindeki $this kullanmadık değişkenleri, $this ifadesinden kurtardık. Kodumuz bu haliyle de aynı şekilde çalışacak. Ancak bizim amacımız mümkün olduğunca PHP kodu ile HTML kodlarını birbirinden ayırmak. Bunun için içinde bulunduğumuz dizinde Template adında bir klasör oluşturalım ve bu dizinin içinde entry.php adında bir dosya oluşturalım. entry.php dosyasının içeriği şöyle olsun:
Not: Buraya böyle salak salak boşluklar koymamın sebebi, kısa foreach yani süslü parantez yerine iki nokta ile biten döngü kullandığımda, html dosyası olarak çıktısı alınacak php dosyasında, php etiketlerinin, indentasyonu, yani html etiketlerinin hizalanmasını bozması. Normalde, performans için, etiket dışındaki bütün bu boşluk ve yeni satır karakterlerini siliyoruz, ancak ben kitapta kod örneklerinin düzgün olması için bu boşlukları koydum.
Şimdi EntryView dosyasına dönüp template metodunun içeriğini şöyle değiştirelim:
Yepyeni iki metod ile karşı karşıyayız. Output buffer yani ob_start() metodu, php’de ekrana basılacak olan herşeyi kaydetmeye yarar. Yani ob_start dediğimiz anda echo diyerek veya örneğin burada require diyerek çağırdığımız dosyanın metin içeriğini kamera gibi kaydetmeye başlıyoruz. ob_end_clean() metodu ise bu kaydettiğimiz şeyleri döndürür ve kaydettiğimiz output buffer denen zımbırtıyı siler. Bu sayede, tema dosyalarını kullanabilir, onları istediğimiz zaman ekrana istediğimiz değişkenlerle basabiliriz.
Şimdi, Template dizinimiz içinde dictionary.php isminde ikinci bir dosya oluşturalım. Bu yazacağımız DictionaryView yani Sözlük Görünüm sınıfımızın tema/şablon dosyası olacak. İçeriğini şu şekilde oluşturup kaydedelim:
public function render() { if (!isset($this->dictionary)) { throw new Exception('Cannot render without dictionary'); }
return $this->template($this->dictionary); } }
Bişi dikkatinizi çekti mi? EntryView ve DictionaryView dosyaları birbirine benzemeye başladı. Kod tekrarını önlemek için ne yapıyorduk? Kodları birleştirip başka bir yere referans veriyorduk. Kalıtım konusuna geldiğimizde bu konuya detaylı olarak değineceğim.
Şimdi app.php dosyamızı, DictionaryView adlı yeni dosyamızı kullanacak şekilde değiştirelim.
$entries = $dictionary->getEntries(); $dictionaryView = new MidoriKocakDictionaryView($dictionary, new MidoriKocakEntryView());
echo $dictionaryView->render();
} catch (Exception $e) { echo 'Error on line ' . $e->getLine() . ' in ' . $e->getFile() . ': <b>' . $e->getMessage(); }
Herşeyi tamamladığımıza göre tarayıcımızı açalım:
Sözlük Görünümü
Gördüğümüz gibi, sözlüğümüzün başlığını ekrana bastık, daha sonra sözlüğün sahip olduğu girdilere göre verileri listelemeye başladık, her girdinin başlığını ve içlerindeki her açıklamayı ekrana bastık. Bunları yapmak için de tema dosyaları kullandık. Şimdi sayfamızın kaynağına bakalım.
Sayfa Kaynağı
İşte şimdi sayfamız birbirine girmiş etiketler yumağı yerine doğru dürüst bir HTML sayfasına benzedi. Peki bu düzgün formatlı bir html sayfası mı? Hayır, sadece dümdüz verileri listeliyoruz. Hala bir uygulamaya benzemiyor. Bir sonraki yazıda, tasarımını yaptığımız uygulamamızın sayfa düzenlerini nasıl oluşturup kullanacağımızı öğreneceğiz.
Bir önceki yazıda, API kavramını, kullanıcının uygulamaya hangi adresleri kullanarak erişebileceğini, bu adreslerin nasıl çalışacağını, yönlendirme kavramını ve arayüz mantığını anlatmıştım. Bu yazıda kullanıcının ilk olarak karşılacağı arayüzleri nasıl oluşturacağımızı, nasıl tasarlayacağımızı ve kullanacağımızı anlatacağım. Daha önce Projelerle PHP 7 adlı kitabımda HTML konusunu anlattığım için, html sayfaları yazmak konusunda detaya inmeyeceğim. Eğer konu ile ilgili eksiğiniz varsa benim kitabımdan veya internetteki kaynaklardan eksiğinizi giderebilirsiniz.
Web uygulamamızda, kullanıcıya bilgileri bir HTML sayfası aracılığı ile gösterecek, ondan bilgileri bir HTML formu aracılığı ile alacak ve işleyeceğiz. Ayrıca kullanıcıdan gelecek bilgilere neden güvenmememiz gerektiğini, kötü niyetli bir kullanıcının sistemimize ve programdaki diğer kullanıcılara nasıl zarar verebileceğini göreceğiz. Ayrıca bu yazıda ilk defa uygulamamızda veriyi nasıl kaydedeceğimize kısaca giriş yapacağız.
Kısaca programımızda 3 ekran olacak. Birinde hali hazırda yarattığımız sözlükleri ve başlık sayılarını görüntüleyeceğiz. İkincisinde, sözlüğü ve içindeki başlıkların listesini görecek, üçüncüsünde de bir başlığa tıkladığımızda açıklamaları göreceğiz.
Kısaca ekranlarımız
Ekranların kötü göründüğüne bakmayın. Sonuçta bize yol gösterecek olan prototipler yani tasarımlar. Siz de benzer tasarımları Mockup Designer ile yapabilirsiniz.
Genel olarak mobil ve web uygulamalarında tasarımlar aynı oluyor. Biz yine kısaca tasarımları içerdikleri elemanlara göre inceleyelim.
Kaydettiğimiz verileri eklemize ve düzenlememize yarayan bir form bileşeni.
Kaydettiğimiz verileri listelememize yarayan bir liste bileşeni.
Kaydettiğimiz veriyi tek olarak görüntülememize yarayan bir diğer bileşen.
Biz bu bileşenleri şimdilik program kodu olmadan kullanıcının göreceği şekilde kodlayalım. Daha sonra bu düz HTML kodlarını bileşenlerine ayıracağız ve aynı kodları tekrar etmemize gerek kalmayacak.
Şimdi proje dizinimizde public adlı bir klasör oluşturalım. Genellikle bu public klasörlerinde js css ve img dizinleri bulunur. public klasörünün altında css adlı bir dizin oluşturalım ve içine şu adresteki kodu universal.css olarak kaydedelim.
Daha sonra public dizinine geri dönelim. index.html isminde bir dosya oluşturalım. İçine şu kodları ekleyelim:
Merak etmeyin bu HTML kodların tek tek uygulamamızla nasıl entegre olacağından bahsedeceğim.
web sunucumucdan index.html’yi çağırdığımızda şöyle bir görüntüyle karşılaşmamız gerekiyor:
Uygulama arayüzü
Genellikle bütün uygulama arayüzleri bu yapıyı kullanırlar. O yüzden başka bir uygulama yazarken siz de bu arayüz mantığını kullanabilirsiniz. Bunu tek tek örnekleriyle anlatacağım.Şimdi bu arayüzde hangi bileşen ne iş yapar onu görelim.
Header yani Üstbilgi
Üstteki siyah kısım. Kod bloğu içinde de header etiketi içinde yer alıyor. Uygulamalarda bu kısım, her sayfada aynı olan, sayfa değiştiğinde değişmeyen, logo, ana sayfa, hakkında, gizlilik vb. gibi linkleri, eğer kullanıcı yönetiyorsak, kullanıcı adını ve kendiyle ilgili işlemleri (login/logout gibi) yapmasını yarayan bir açılır menüyü, arama yapılabiliyorsa, arama formunu içerir.
Mesela, facebook, twitter ve linkedin uygulamalarında bu bölümler nasıl tasarlanmış bir göz atalım:
Twitter Üstbilgi:
Twitter Header
facebook Üstbilgi:
Facebook Header
Linkedin Üstbilgi:
Linkedin Header
Gördüğünüz gibi uygulamada header yapıları küçük değişikliklere rağmen aynı.
Aside yani Yanbilgi
Genel olarak, uygulamamızı kullanırken isteğe bağlı olarak ihtiyaç duyacağımız bilgileri içeren kısmı temsil eder. Bizim kodumuzda aside etiketiyle tanımlanmıştır.
Index.php dosyasını açtığımızda, sol tarafta bulunan üstüste linklerle oluşturduğumuz kısım. Burası genellikle uygulama içindeki farklı modüllere ilerlememizi sağlayan linkleri içerir. Biz uygulamamızda sadece sözlük tuttuğumuz için, Sözlüklerim adlı bir link var. Buradaki link sayesinde, programın herhangi bir yerinde istediğimiz zaman Modülün ana sayfasına dönüş yapabileceğiz. Sözlüklerim için, bütün sözlüklerimi listelediğim sayfa. Örneğin facebook’ta burada, Gruplar, Sayfalar, Etkinlikler gibi linkler bulunur. Görelim:
facebook Yanbilgi:
facebook aside
Twitter Yanbilgi:
Twitter Aside
Linkedin Yanbilgi:
Mesela linkedin ve twitter örneklerine baktığımızda, kendi profiliml ilgili bilgileri ve istatistikleri görmemi sağlayan bir profil bileşeni konmuş. Facebook örneğinde sadece kullanıcı adı ve kullanıcı resmi içeren minik bir link konmuş.
Main yani Asıl işi yaptığımız kısım
Bu kısım kaydettiğimiz varlığın, yani video, tweet, paylaşım gibi bilgilerin sırayla listelendiği kısım olacak. Bazen kullanıcı uygulamayı açtığında, dashboard yani gösterge paneli veya kontrol paneli dediğimiz, grafiklerle uygulama istatistiklerini gösteren bir kısımla da karşılaşabiliyor.
Gösterge Paneli
Ancak bizim şu an incelediğimiz, twitter, linkedin ve facebook örneklerinde sayfayı açtığımızda, uygulamamızda yönettiğimiz asıl varlığı eklememize yarayan minik bir form ve akış yani feed dediğimiz iki modül main yani ana ekranda bulunuyor. Örnekleri görelim:
Twitter:
Twitter Akış
Facebook:
Facebook Akış
Linkedin:
Linkedin Akış
Gördüğümüz gibi üç örnekte de, minik bir form ve varlıkları tek tek listeleyen akış dediğimiz bir kısım var.
Akış yani feedlerde lazy loading, yani tembel yükleme dediğimiz mantık işliyor. Yani binlerce, milyonlarca paylaşımı veya tweet’i bir anda göstermek yerine, örneğin ilk 10 tanesi yükleniyor, ve kullanıcı sayfayı aşağı kaydırdığında bir 10 tanesi daha yükleniyor. Başka bir yöntem de pagination ya da sayfalama.
Sayfalama
Sayfalama sayesinde takip ettiğimiz verilerin tamamını sayfaya indirip görüntülemek yerine, ilk 10 tanesini ilk sayfada görüntüleyecek ve kullanıcı eğer isterse sonraki sayfalara tıklayarak kalan varlıkları görüntüleyecek. Biz henüz javascript kısmına gelmediğimiz için uygulamamızda bu yöntemi kullanacağız.
PHP’de Görünümler yani View’lar
Yukarıda paylaştığım HTML örneği, son kullanıcının uygulamamızı açtığında, tarayıcısına gidecek olan HTML kodudur. Şimdi, kullanıcının bu kodu nasıl elde edeceğine, server yani sunucu tarafında bizim karar vermemiz gerekiyor. PHP’de bunu yapmanın birden fazla yolu var.
Düz PHP dosyasında, PHP ve HTML kodlarını karıştırmak.
Tema olarak kullanacağımız PHP dosyaları oluşturmak ve yine PHP ve HTML kodlarını karıştırmak.
Nesne yönelimli olarak bir View yani Görünüm sınıfı oluşturmak ve bu sınıfı kullanmak. Hatta kullanıcıya göstereceğimiz form, liste gibi modüller için de ayrı sınıflar oluşturmak.
Bu konu aslında “separation of concerns” yani tam çevirmek gerekirse “ilgilerin birbirinden ayrılması” konusuna giriyor ve MVC yani model-view-controller kısmını anlattığımda detaylıca bahsedeceğim kısımla alakalı ancak, şimdilik o bölüme girmeden nesne yönelimli olarak kullanıcının görüntüleyeceği HTML kodunu yönetmenin dışına çıkmayacağız. Biz bu yazı serisinde Nesne Yönelimli Programlama ile ilgili olduğumuzdan, 3. yöntemle yani View sınıfları kullanarak bi işi halledeceğiz.
Piyasada örneğin Plates, Aura veya Blade gibi anlattığımız işleri gerçekleştiren kütüphaneler mevcut, ancak biz bu seride konuları temelinden aldığımız kendi sınıflarımızı kendimiz yazacağız. Daha sonra, profesyonel işlerimizde tabii ki, vakit kazanmak adına piyasadaki hazır kütüphane ve frameworkleri (uygulama çerçevelerini) kullanabiliriz. (Framework konusunu da serinin sonunda detaylıca anlatacağım.) Ancak şimdilik herşeyi kendimiz yapacağız ki, konuyu detayıyla kavrayabilelim.
Daha önce sınıflarımızın, bağımsız, kendi başına çalışabilen bir uygulama gibi olmaları gerektiğini, belli bir amaca ve tek bir sorumluluğa sahip olması gerektiğinden bahsetmiştik. Şimdi bu düşünceyi aklımızda tutarak View yani Görünüm sınıfımızı tasarlamaya başlayalım. Önce görünüm sınıfımızın ne yapması gerektiğine karar verelim ve son kullanıcının onu nasıl kullacağını gösterecek arayüzü oluşturalım. Peki View yani Görünüm sınıfımızın yapması gerekenler neler?
Verdiğimiz veriyi, doğru dürüst bir şekilde, doğru biçim ile kullanıcıya sunmak.
Gerekirse, aynı veriyi, başka biçimlerle kullanıcıya sunmak. (Örneğin JSON ya da desktop uygulamasının GUI yani grafik kullanıcı arayüzüne uyumlu olacak biçimde) (JSON konusunu daha sonra detaylı olarak anlatacağım.)
Kullanıcının yaptığı işlemleri tespit edip ona göre kullanıcıyı yönlendirmek. Yani kullanıcı, programımızın diğer kısımları ile muhattap olmak yerine, sadece Görünüm yani View sınıfı ile muhattap olmalı.
İstediğimizde aynı veriyi web üzerinde ya da terminal ekranında görüntülemek için aynı arayüzü kullanan farklı View sınıfları kullanabiliriz.
Görünüm sınıfımızın veriyi sarmalamak için kullanacağı bir Template’si yani şablonu olmalı.
View yani Görünüm sınıfını daha da karmaşıklaştırabiliriz ancak şimdilik en basit özellik ve metodları düşünerek işe başlayalım. Entry yani Girdi sınıfımız için program dizinimiz içinde EntryView.php adında bir dosya oluşturalım ve içine şu kodları ekleyelim:
<?php
namespace MidoriKocak;
class EntryView { private $entry;
public function __construct(EntryInterface $entry = null) { if ($entry !== null) { $this->entry = $entry; } }
public function setEntry(EntryInterface $entry) { $this->entry = $entry; }
public function render() { if (!isset($this->entry)) { throw new Exception('Cannot render without entry'); }
Şu duruma dikkat edelim: EntryView sınıfımız, EntryInterface yani Girdi Arayüzü ile muhattap oluyor, ve Girdi sınıfını bu arayüze göre kullanıyor. Constructor yani sihirli yaratma metodumuzda paramtere EntryInterface $entry = null ifadesini kullandık. Buradaki = null ifadesi, $entry değişkeninin opsiyonel olduğunu belirtir. Yani istersek boş bir entry sınıfı oluşturabiliriz ve setEntry metodu ile istediğimiz girdiyi bu view yani görünüm sınıfı içerisine data yani veri olarak yerleştirebiliriz. Bu sayede her girdi için yeni bir görünüm sınıfı yaratmamıza gerek kalmaz.
Render yani sunum metodumuzda, constructor yani sihirli yaratma metodunda, $entry değişkenini opsiyonel olarak tanımladığımız için, hatalara karşı sınıf içindeki $this->entry değişkeninde tanımlı olan girdi verisini kontrol ettik ki, herhangi bir kişi, setEntry demeden veya constructor içinde girdiyi tanımlamadan render yani sunum metodunu çalıştırıp hataya yol açmasın. if (!isset($this->entry)) bloğu içinde bu kontrolü yapıyoruz.
Daha sonra, girdi sınıfından başlık ve değerler gibi verileri çekip, html kodu içinde sarmalıyoruz. Görünüm için başka bir sınıf oluşturup, asıl nesnenin verilerini bu şekilde sarıp sarmalamaya Decorator yani Dekoratör tasarım deseni deniyor. Tasarım desenleri kısmına geldiğimizde bu konuyu daha detaylıca anlatacağım.
Aslında PHP kodları içinde html etiketlerini karıştırıp kullanmayı sevmem. Bana spagetti kodu hatırlatıyor. Şimdilik kolay olması amacıyla bu şekilde anlatıyorum. Ancak daha sonra bir şablon dizini oluşturup, veriyi sarmalayacağımız HTML kodlarını ayrı ayrı php şablon dosyalarında tutacağız. View sınıfımız tema dosyasını açıp, sunumu o şekilde yapacak.
Şimdi Dictionary.php dosyası içinde yapmamız gereken küçük bir değişiklik var. Dictionary.php içindeki getEntries metodu bize metin dizisi döndürüyordu. Bunun yerinde EntryView sınıfını kullanacağımız için entry yani girdi dizisine ihtiyacımız var. Dosyadaki getEntries metodunun adını getEntriesAsArray olarak değiştirelim ve yeni yazacağımız getEntries metodunda doğrudan $this->entries içinde tanımlı olan girdi nesnelerinin dizisini döndürelim.
<?php
namespace MidoriKocak;
class Dictionary implements DictionaryInterface { private $title; private $entries;
public function __construct(string $title) { $this->setTitle($title); $this->entries = []; }
public function setTitle(string $title) { if (($title != "") && (strlen($title) <= 70)) { $this->title = $title; } else { throw new InvalidArgumentException('Wrong title value.'); } }
public function getTitle(): string { return $this->title; }
public function getEntriesAsArray(): array { $entries = []; /* @var $entry EntryInterface */ foreach ($this->entries as $entry) { $entries[$entry->getKey()] = $entry->getValues(); } return $entries; }
public function getEntries(): array { return $this->entries; }
Bu sayede app.php kodu içinde girdi nesnelerinin dizisine dışarıdan erişebileceğiz. app.php dosyamızı şu şekilde değiştirelim:
foreach ($entries as $entry) { $entryView = new MidoriKocakEntryView($entry); echo $entryView->render(); }
} catch (Exception $e) { echo 'Error on line ' . $e->getLine() . ' in ' . $e->getFile() . ': <b>' . $e->getMessage(); }
Yeni oluşturduğumuz EntryView dosyasını “require_once ‘EntryView.php’;” ifadesiyle dosyamıza ekledik.
Yaptığımız tek değişiklik, printr($entries) ifadesinin yerine şu kodları yazmak oldu:
foreach ($entries as $entry) { $entryView = new MidoriKocakEntryView($entry); echo $entryView->render(); }
foreach ifadesi ile $entries değişkeni içindeki her bir girdi nesnesine eriştik, ve her biri için yeni bir EntryView yani Girdi Görünümü sınıfı (ya da dekoratörü) yarattık. Şimdi web sunucumuzdan app.php’yi açtığımızda şöyle bir görüntünün karşımıza gelmesi gerekiyor:
HTML oluşturan sınıf
Şimdi biraz daha HTML sayfasına benzemeye başladı. Sayfa kaynağına baktığımızda şu kodları görmeliyiz:
Sayfa Kaynağı
Gördüğümüz gibi, sınıfımız ham veriyi aldı ve bizim istediğimiz şekilde HTML kodlarının içine sardı. Şimdilik uygulamamız düzgün bir web sayfası gibi görünmemekle beraber, View yani görünüm sınıflarının ne işe yaradığını, dekoratör tasarım deseninin mantığını ve Ham verinin nasıl süsleneceğini iyice anladık. Bir sonraki yazıda sözlük için görünüm sınıfını oluşturacağız ve ufaktan sayfa şablonlarımızı yazmaya başlayacağız.
Medium’da ki yazılarını severek okuyorum, yalnız gözden kaçırdığın bazı şeyler olduğunu düşünüyorum.
Endüstriye ucuz iş gücü yapmak amacıyla hazırlanan müfredatın bilginin oluşma diyalektiğine aykırı biçimde oluşması ve kakalanmasıyla, yazılım mimarilerinin mutlak doğruymuşçasına kabul edilmesi gerçeğinin benzerliğini fark edemediğini düşünüyorum.
Yıllar önce gazeteler çok farklı konuları barındırırdı ama teknolojinin gelişmesi ve mobil cihazların yaygınlaşması insanların ilgi alanlarını ve önceliklerini hedeflenmesinin yolunu açtı, bu durum aldıkları telefon arayüzlerini sadeleştirme eğilimlerini tetikledi, teknoloji insan ihtiyaçlarına paralel olarak gelişti, hep de öyle olacak gibi görünüyor.
Bilginin oluşma diyalektiğini görmezden gelen kapitalizm, bugün inovasyon adı altında workshoplar düzenliyor, dev şirketler kodlarını geliştiricilere açıyor.
Kalıplarla öylesine meşguller ki insanların ihtiyaç ve amaçlarını artık anlamakta zorlanıyorlar, bunun sebebini yazılım teknolojilerinin mutlak doğruymuşçasına kabul edilmesine ve geliştiricilerin bu teknolojileri öğrenmek için büyük zaman harcamasına bağlıyorum.
Ben interface ve diğer mimari öğelerin gerekli olmadığına onlar yerine javascript, html de ki gelişmelerin beraber kullanıldığı ekosistemlerin işe yarayabileceğine inanıyorum bilmiyorum belki de yanılıyorumdur ama dediğim gibi bunları kullanmanın mantıklı olmadığına inanıyorum.
Yürekten soruyorum beni ikna eder misin? Sevgilerimle.
Ben de şu şekilde cevap verdim:
Selam,
Ben yazılarımı ucuz iş gücü için müfredat oluşturmak amacıyla yazmıyorum. Ben zamanında bilişim çalışanları dayanışma ağında bilginin metalaştırılmasına karşı bilgiyi paylaşma amaçlı ücretsiz eğitimleri başlattığımda da amacım buydu. Yazılım mimarilerinin mutlak doğru olması ile ucuz işgücü kavramının veya zorlama bilgi olmasının bağlantısı olmadığını düşünüyorum. Bilgisayar Bilimleri ve mühendisliği şunun surasında 50–60 senelik uygulama alanı bulmuş, genç bir mühendislik dalıdır. Tıp gibi, Fizik veya Biyoloji gibi teorileri yoktur veya çok azdır. Standartlar oturmamıştır ve standartlaşma çabası vardır. Yazılım mimarileri olarak tasarım desenlerinden bahsediyorsunuz sanırım. Tasarım desenleri konusu Christopher Alexander tarafından bina mimarisi yani inşaat mühendisliği için 1977’de ortaya atılan bir kavram. Yazılım mühendisliğinde bugün olduğu gibi o gün de varolan anarşi ve kaos ortamından dolayı, ortak ve bilimsel bir metodoloji oluşturmak amaçlı, inşaat mühendisliği biliminden alınan bir kavramlar dizisi. Bilimsel olduğu için din gibi değiştirilmez değil tabii ki.
Gelelim ikna konusuna, biyoloji, tıp, ilaç sanayii gibi insan sağlığını ilgilendiren bilim ve mühendislik alanları gibi kurallar ve yönetim organları olmadığı için, doğal olarak kuralsız, kaotik ve cahillikten beslenen bir ortam mevcut. 1950’lerde de mevcuttu. Bu ortamın böyle olması büyük şirketlerin lehine bir durum. Büyük şirketler yapısal kodlara karşı, tabii ki prosedürel kodlamayı promote edecekler ve standartların olmamasından kendi standartlarını dayatarak faydalanacaklar. Tıpkı, MVC gibi OOP gibi, Modülerlik kavramlara tecavüz eden React teknolojisinin facebook tarafından yaygınlaştırılması veya Google’ın saçma sapan bir js çerçevesi olan Angular teknolojisini yaymaya çalışması gibi. Bu şirket bağımlı teknolojiler, internetin, web’in herkese açık, demokratik web standartlarına tam anlamıyla tecavüz ederler, ve bu standartların oluşmalarını geciktirirler. Tıpkı Adobe şirketinin ömrü dolduğu ve gericilik ürettiği halde Flash adlı berbat kapalı kaynaklı boktan teknolojiyi solunum cihazında yaşatmaya çalışması gibi. Bu konu ile ilgili şu yazıyı okumanızı öneririm. https://www.pandastrike.com/posts/20150311-react-bad-idea
Sonuç olarak, ortak, açık demokratik standartlar ile oluşturulan yapısal kodlar, allahın emri değildir tabii ki, ancak standartsızlık ortamından küçük yazılımcılar veya minik yazılım evleri değil, dev şirketler fayda sağlarlar ki, tüm yazılımcılar kendi yan-kar ortamlarını yarattıkları ekosistemlerine doluşsunlar.
Kapitalizmin ve kapitalist şirketlerin kendi geliştirdikleri sözde “açık kaynaklı” ama açık web standartlarına tecavüz eden teknolojileri yaymaya çalışmalarının nedenini sanırım yukarıdaki paragraftan anlamışsınızdır. IBM gibi iğrenç bir kapitalist şirketin Linux vakfına neden on milyarlarca dolar bağış yaptığını da başka bir yazıda açıklamam gerekiyor sanırım ama bunu yorumlamayı şimdilik size bırakıyorum. İpucu olarak burjuva bankalarından kredi çeken aristokratlara referans verebilirim.
Bu yazıya kadar, bir programı sınıflar kullanarak nasıl tasarlayacağımızı, nasıl temiz kod yazacağımızı, kodlarımızı nasıl hatalara karşı kontrol edeceğimizi, sınıflarımızın sorumluluklarının ne olacağını anlattık. Peki programımızı kim nasıl kullanacak? Sınıflarımızı yazdık ve app.php dosyası içinde de kullandık, ama son kullanıcı bu programı nasıl kullanacak? Bu yazıda bu konuyu detaylıca işleyeceğiz. Ayrıca programımıza arama, sıralama, üyelik gibi ek özellikler eklemek istediğimizde ne yapacağız? Bu meseleye de bu yazıda kısaca değineceğiz.
Kullanıcılar
Genel olarak programların amacı
Bu konuyu bu seride defalarca açıklamıştık ancak şimdi tıpkı başta sınıflarımızı tasarladığımızda yaptığımız gibi, programımızın nasıl çalışacağını ve amaçlarının ne olduğunu kısaca anlamamız gerekiyor ki, sınıflarımızı kullanacak program kodumuzu da nesne yönelimli bir şekilde tasarlayabilelim.
Genellikle programların amacı kullanıcıdan istekleri ve girdiyi almak, bunu hatalı girdilere karşı doğruladıktan ve güvenlik açıklarına karşı temizledikten sonra işlemektir. Bu web uygulamalarında html dosyaları içindeki web formları aracılığı ile yapılır.
Kullanıcı girdileri genellikle bir dosyaya veya veri tabanına kaydedilir. Verilere ne şekilde kaydedildiği genellikle programın mantığını tutan sınıfın sorumluluğunda değildir. (MVC konusunda bu kavramı detaylıca açıklayacağım.)
Kullanıcı daha önce eklediği verilere erişmek ve görüntülemek isteyecektir. Yani program veri tabanından veriyi alıp, kullanıcıya, onun anlayabileceği şekilde göstermek zorundadır.
Daha önce nesneleri tasarlamayı anlatırken, nesnelerin bağımsız programlar gibi canlı ve kendi kendine yeten varlıklar olmaları gerektiğinden bahsetmiştik. Yani hiçbir nesne, mantıken başka bir nesneye göbekten bağlı, olmayacak. Bir diğer kural da bir nesne içinde başka bir nesneyi new ifadesi ile yaratmamak. Böyle bir kod bloku yazıyorsak, sınıfımızın diğer nesneye göbekten bağımlıdır ve o nesnede yapılacak herhangi bir değişiklik nesnemizin davranışını ve ondan beklentilerimizi bozacaktır. Daha iyi anlamak için şöyle bir örnek vereyim: Mesela bu yazıya eklediğim linkler üzerinde hiçbir kontrolüm yok. Eğer linkte bulunan yazı silinirse veya değişip saçma sapan bir siteye giderse, yazımda saçma sapan bir bilgiye yer vermiş olacağım. Bu konuya bağımlılık enjeksiyonu ve bağımlılık çevrimi (dependency injection ve dependency inversion) deniyor. Kabul, Türkçe’ye çevirince çok abuk oldu ama konuyu detaylıca anlattığımda buna da doğru dürüst bi karşılık bulmaya çalışırız. Solid ilkeleri ile ilgili bir kaynak: http://tarikkaygusuz.com/post/solid-prensipleri
Programdan beklentilerimiz ve arayüzler
Bu seride, sınıf ve program kavramlarını birbirinden ayrı düşünmemeye özen gösterdik. Bir sınıfı yazarken, yapabileceği işleri interface yani arayüzde tanımlamıştık. Daha sonra o sınıfı kullanan herhangi bir kod, sınıfın o arayüzde tanımlandığını bildiğinden erişebileceği ve kullanacağı public metotların neler olduğunu biliyordu. Herhangi bir programı web arayüzünden veya komut satırından kullandığımızda da aynı beklenti kalıplarına sahibiz. Örneğin, bir web uygulamasını veya sitesini açtığımızda, sol tarafta üstüste bir sürü link ile yapabileceğimiz işleri görebiliriz.
Kullanıcıların aşina oldukları arayüzleri programlarımızda kullanırsak, hem kolay kullanım hem de anlaşılabilirlik sağlamış oluruz.
Eğer Unix/Linux/MacOs/BSD terminaline, kabuğuna, komut satırına, ya da adı her ne boksa veya windows’taki cmd yazınca çıkan dos ortamına, powershell’e fln alışkınsanız, komut satırında herhangi bir komut ardından -h veya — help yazdığınızda, o programına hangi parametreleri girerek işlem yapacağınızı gösteren bir yardım listesiyle karşılaşacağınızı bilirsiniz.
Windows ortamında, cmd.exe’yi çalıştırıp dir /? yazarsanız, içinde bulunduğunuz dizindeki dosyaları gösteren dir komutunun yardım dokümanını görürsünüz.
Dos ortamında Dır komutunun yardımı
Unix ortamında çalışıyorsanız, herhangi bir komutun sonuna -h veya — help yazdığınızda aynı help dokümanıyla karşılaşmayı beklersiniz. Komutu yazan programcı, gıcık veya şerefsiz değilse genelde kullanıcının bu beklentisini tatmin etmeye çalışır.
Unix ortamında composer komutunun yardımı
İlk adım olarak bu “-h” ya da “ — help” parametresini bildiğinizde, kullanacağınız program hakkında hiçbir bilginiz olmasa dahi, programı nasıl kullanacağınızı size anlatacak bilgilere bu parametreyi kullanarak erişebilirsiniz.
Yenilikçi arayüzler
Yani bilgisayar programlarını yazarken kullanıcıların belirli davranış kalıplarına ve beklentilerine uyarsanız, kötü tasarımınız için küfür yemezsiniz. Tıpkı daha önce verdiğim araba arayüzü örneğinde olduğu gibi, vitesi tavana, direksiyonu vitesin olduğu yere, gaz pedallarını da direksiyonun üzerine koyabilirsiniz ama tabii ki koymamalısınız. Not: Bazı zamanlarda, aşırı yenilikçi kullanıcı arayüzleri yazmanız gerekebilir. Örneğin bir siteye girip uçak bileti almak yerine chat yapan yapay zeka bot programları ile konuşarak uçak bileti alabilirsiniz.
Chatbot arayüzü
Bu tarz yenilikçi arayüzler, yeni yeni kullanılmaya başlansalar da, bunlar için de ortak bir standart zamanla oturacaktır. En basitinden, herhangi bir chatbot’a merhaba dediğinizde merhaba demesini beklersiniz ki bu da bir arayüzdür ve beklentilerimizi tanımlar.
Yönlendirme — Routing
Şimdi genel olarak bir web uygulamasından beklentilerimiz meselesine dönelim. Şimdilik kullanıcı oturum açması parola girmesi kısmını geçeceğim. O konuyu kullanıcı yönetimi konusuna geldiğimizde detaylı olarak açıklayacağım. Bir web uygulamasını açmak için ilk olarak web tarayıcısına adres gireriz, ve genellikle uygulama neyi kaydediyorsa, ilk ekranında onların sıralı listesi gelir. Örneğin YouTube sitesini açtığımızda karşımıza sıralı, ya da belli başlı kategorilere ayrılmış videoların listesini görmek isteriz.
Youtube Anasayfa
Daha sonra mesela eğer video yüklemek istersek, video upload linkine tıklarız. Youtube örneğinde abuk subuk bir ikon kullanmışlar. Çok önemli ve dikkat etmemiz gereken ikinci beklentimiz de şu: Upload yani Video yükleme linkine tıkladığımız zaman, adres çubuğundaki adresin değişmesi. Görelim:
/dictionaries/add: Yeni bir sözlük oluşturmamızı sağlayan formu karşımıza çıkarmalı.
/dictionaries/12: 12 nolu id’ye sahip olan sözlüğü getirecek. Not: id dediğimiz numara, her varlık için eşsiz olan ve varlıkları birbirinden ayırmamızı sağlayan değişkendir. Örneğin, biz sözlük girdilerinde, girdi başlıklarını benzersiz olarak tanımladık. Yani ‘nesne’ adında başka bir girdi olmayacak. Bu sayede bir girdiye erişmek için anahtar olarak bu kelimeyi URL yani web adresi içinde kullanabiliriz.
/dictionaries/12/edit: 12 numaralı sözlüğün, örneğin başlığını değiştirmemizi sağlayacak olan formu çıkaran adres.
/dictionaries/12/delete: 12 numaralı sözlüğü silmemizi sağlayacak adres. Dikkat: Bu adrese erişildiğinde kullanıcıya emin misiniz diye bir bildirim yapılmalı ki, yanlışlıkla silme durumu olmasın.
/dictionaries/12/entries: 12 nolu sözlüğe ait bütün başlıkların listesini getirmeli.
/dictionaries/12/entries/add: 12 nolu sözlüğe ait yeni başlık eklememizi sağlayan formu göstermeli.
/dictionaries/12/entries/nesne: İşte burada id, yani benzersiz anahtar olarak başlık kullandığımız için, linkimiz bu şekilde oluyor. Girdideki değerleri gösterecek. Bu sayfaya /dictionaries/12/entries/nesne/values adresiyle de erişebiliriz. Çünkü nesne girdisindeki açıklamaları zaten gösteren sayfa bu sayfa. Not: Peki ya add isminde çakışan bir başlık girilirse? Buna da dikkat etmemiz gerekiyor.
/dictionaries/12/entries/nesne/edit: Nesne başlığına sahip olan girdinin başlığını değiştirmemizi sağlayan formu gösterecek.
/dictionaries/12/entries/nesne/values/add: Nesne başlığına yeni açıklama eklememizi sağlayacak formu göstermeli.
/dictionaries/12/entries/nesne/values/1: Nesne başlığında 1. sıradaki açıklamayı vermeli. Biz burada id, yani anahtar olarak değerin dizideki sırasını kullandık. Fakat doğru bir pratik olarak int yani tamsayı tipinde bir anahtar kullanmalıyız.
/dictionaries/12/entries/nesne/values/1/edit: Nesne başlığında 1. sıradaki açıklamayı değiştiren formu göstermeli.
/dictionaries/12/entries/nesne/values/1/delete: Nesne başlığında 1. sıradaki açıklamayı silecek olan adres.
Dikkat ettiyseniz, adresler belli deseni takip ediyor. Yani kaydettiğimiz varlığın çoğul ismi — anahtar değeri — add,edit,delete komutlarından bir tanesi. Eğer komut yoksa, hazırda kaydedilmiş varlıkları listeliyor veya anahtar varsa o varlığı ve bağlantılı varlıkları gösteriyor. Yani 12 numaralı sözlüğü açtığımızda, aynı zamanda aksini belirtmediysek, sözlükteki girdilerin listesini de görebilmeliyiz. Aslında bu listeleme işlemine de index deniyor. Yani /dictionaries/12/entries/ linki ile /dictionaries/12/entries/index linki aynı yere gidiyor.
İşte programı kullanmak için girdiğimiz bu adreslere programımızın Uygulama Programlama Arayüzü veya API’si deniyor. Daha bilimsel bir tanımlama verecek olursak:
Uygulama Programlama Arayüzü (UPA) [İngilizce: Application Programming Interface — API], işletim sisteminin, bir kütüphanenin veya bir servisin diğer programlara sağladığı fonksiyon ve sınıf kümesidir. Kaynak: http://www.emo.org.tr/ekler/1db5dd8fa2d87e7_ek.pdf
API’lar sayesinde, örneğin, bir kullanıcıya ait twitter akışını, tıpkı o kullanıcının twitter profilini açmış gibi kendi sayfamızda gösterebiliyoruz. Bu konuyu farklı API’ları kullanmak bölümünde detaylıca anlatacağım.
Yani sınıflarımızı kullanacak program kodunu yazarken, ayrı ayrı index.php, add.php, edit.php gibi dosyalar yazıp tarayıcıyı bu sayfalara yönlendirmek yerine, kullanıcının isteklerini alıp yönlendiren bir program koduna ihtiyacımız olacak. Bunu da bir sonraki yazıda yapacağız.
Bir önceki yazıda girdi sınıfımızı kullanan sözlük sınıfımızı yazdık. Deneme/Yanılma yani Try/Catch bloklarını kullanarak hatalara dayanıklı kod yazmayı ve kod tekrarlarını önlemekten bahsettik. Bu yazıda Entry yani girdi sınıfımızı oluşturacağız.
Sözlük girdisi
Dictionary.php sınıfımızın bulunduğu dizinde Entry.php adlı bir dosya oluşturalım. Artık bir sınıfın aşağı yukarı nasıl olması gerektiğini biliyorsunuz. İçi boş sınıfımızı şu şekilde yazalım.
<?php
namespace MidoriKocak;
class Entry implements EntryInterface { public function __construct(){} public function setKey(string $key){} public function getKey(): string{} public function setValues(array $values){} public function getValues(): array{} public function getValue(int $order): string{} public function setValue(int $order, string $newValue){} public function addValue(string $value){} public function deleteValue(int $order){} }
Her sınıfın __construct metodu olması gerektiğine dikkat edelim. Diğer metodlar daha önce EntryInterface.php içinde isimlerini tanımladığımız metodlar olacak. Tek tek metotlarımızın içlerini doldurmaya başlamadan önce, sınıfa özel private, yani sadece sınıfın içinden erişebileceğimiz değişkenleri sınıfımızın koduna eklemeye başlayalım.
__construct yani sihirli yaratma metodundan önce değişkenleri sınıfa şu şekilde ekleyelim:
<?php
namespace MidoriKocak;
class Entry implements EntryInterface { private $key; private $values;
public function __construct(string $key, string $value) { $this->setKey($key); $this->values = []; array_push($this->values, $value); }
Yaptıklarımızı her zaman yaptığım gibi tek tek açıklayayım:
Sınıfımıza private yani özel tipinde, $key ve $values adlı iki değişken ekledik.
$key burada metin tipinde girdinin başlığını tutacak. Her başlıkta olduğu gibi, boş olmayacak ve 70 karakterden uzun olmayacak. Buna benzer kontrolü, Dictionary.php dosyasında setTitle metodunda yapmıştık. Aynı metodu alıp biraz değiştirerek burada kullanacağız.
$values değişkeni, girdinin sahip olduğu açıklamaları tutan diziyi içerecek.
__construct metodu içinde parametre olarak $key ve $value adlı iki değer tanımladık ki, girdi sınıfımızı, kolaylık açısından, örneğin $entry = new Entry(‘nesne’, ‘harika bişi’); diyerek yaratabilelim.
__construct sınıfımızın içine baktığımızda, tıpkı Dictionary.php dosyasındaki __construct metodundaki setTitle metodunda olduğu gibi, setKey adlı metod ile $key değişkenini belirliyoruz ve bu sayede $key değişkeninin boş olmadığını, yani “” değerinde bir metin olmadığını ve 70 karakterden kısa olduğundan emin olarak nesnemizi yaratıyoruz.
$this->values = [] diyerek boş bir dizi oluşturuyoruz.
array_push metodu ile aldığımız $value değerini doğrudan $values dizisinin içerisine, sıra numarası olmadan ekliyoruz. Daha sonra addValue metodunun içini doldurduğumuzda bu satırı değiştirmeliyiz. Önemli: Uymamız gereken önemli bir kural olarak değişkenlere her zaman, o değişkenleri değiştiren ilgili set/get veya add metodları ile erişmeli veya manipüle etmeliyiz.
Getter ve Setter metodlarını bankadaki veznedar gibi düşünebilirsiniz.
Erişici ve Değiştirici yani Setter ve Getter metotları
Sınıfımızın metodlarının içlerini doldurmaya devam edelim:
public function setKey(string $key) { try { if (($key != "") && (strlen($key) <= 70)) { $this->key = $key; } else { throw new InvalidArgumentException('Wrong key title.'); } } catch (Exception $e) { echo $e->getMessage(); } }
public function getKey(): string { return $this->key; }
setKey ve getKey metodları, daha önce Dictionary.php yani sözlük sınıfımızdaki setTitle ve getTitle metodlarında olduğu gibi, değişkeni kurallarımıza uyarak değiştirmemizi ve erişimi kontrol etmemizi sağlıyorlar. Burada getKey metodu doğrudan değişkeni döndürmekte. Fakat daha sonra, örneğin, erişimi bir şekilde kontrol etmek istersek (Örneğin sadece yazının yazarının erişebildiği bir yazı gibi), bu metodu değiştirebiliriz. Aklımızda bulunsun. Yine try/catch blokları ile hata denetimi yaptık.
public function setValues(array $values) { try { if (!empty($values)) { $this->values = $values; } else { throw new InvalidArgumentException('Array cannot be empty.'); } } catch (Exception $e) { echo $e->getMessage(); } }
public function getValues(): array { return $this->values; }
Tıpkı setKey ve getKey metotlarında olduğu gibi gelen değerleri kontrol ettik. Burada gelen dizinin boş olmamasını kontrol ediyoruz. Fakat, dizinin tek boyutlu ve her elemanının metin tipinde olduğunu tek tek kontrol etmemiz gerekirdi. Şimdilik kolaylık açısından bunu daha sonraya bırakalım.
private function isOrderInValues($order): bool { try { if (!array_key_exists($order, $this->values)) { throw new OutOfBoundsException('Cannot find entry in dictionary'); } else { return true; } } catch (Exception $e) { echo $e->getMessage(); } return false; }
Daha önce Dictionary.php sınıfında, bir anahtarın dizide olup olmadığını kontrol eden ve hata döndüren isKeyInEntries($key) metodu yazmıştık. Burada da isOrderInValues metodu ile aynı şeyi yapıyoruz ki olmayan bir değere erişim sağlamaya çalışan bir gerizekalı programımızı çökertmesin.
public function setValue(int $order, string $newValue) { if ($this->isOrderInValues($order)) { $this->values[$order] = $newValue; } }
public function getValue(int $order): string { if ($this->isOrderInValues($order)) { return $this->values[$order]; } return ""; }
public function addValue(string $value) { array_push($this->values, $value); }
public function deleteValue(int $order) { if ($this->isOrderInValues($order)) { unset($this->values[$order]); } }
Kalan metodlarımızı da tek tek açıklayalım:
setValue: Sıra numarası ile açıklamayı düzenleyen metot. Bu metot aslında istediğimiz değerleri sıra numarası ile erişip düzenlememize yarıyor. Yine isOrderInValues metodu ile o sıra numarasında değer olup olmadığını kontrol ettik.
getValue: Sıra numarası ile açıklamayı döndürüyor.
addValue: Sıra numarası kullanmadan doğrudan $values değişkeninin yani açıklamalar dizisinin sonuna istediğimiz değeri ekliyor.
deleteValue: Sıra numarası girerek açıklamayı silmemizi sağlıyor.
AddValue ve setValue metotlarında, açıklamanın zaten ekli olup olmadığını da kontrol edebilirdik, ancak bunu da şimdilik kolaylık açısından geçiyorum. Arama kısmına geldiğimizde, bu konuyu detaylı olarak anlatacağım.
Girdi yani Entry sınıfımızı tanımlamayı bitirdik. Gelelim kodumuzu kullanacak kodu yazmaya. Aynı dizinde daha önceden yazdığımız app.php isimli dosyayı şu şekilde değiştirelim:
Burada print_r metodu, dizileri insanların okuyacağı şekilde ekrana basan metoddur. Eğer herhangi bir sorun olmazsa, web sunucumuzdan app.php adlı dosyayı çağırdığımız zaman şöyle bir sonuçlar karşılaşacağız:
Ne kadar çok chrome extension yüklemişim yahu.
Sonuç olarak elimizde doğru dürüst çalışan tertemiz iki sınıfımız oldu.
Hata mesajlarımız anlaşılır ve açık olmalı. Bu nedir yani?
Peki hata durumunda ne olacak? app.php dosyasını şu şekilde değiştirelim:
$şey değişkeninde oluşturduğumuz entry nesnesinin başlık bilgisini girmeyi unuttuk. Sonuç ne olacak? Görelim:
Dikkat. Eğer uncaught error gibi bir hata gördüysek hata try/catch bloklarımızın dışında meydana gelmiş ve biz önlem alamamışız demektir. İnceleyelim. Burada sorun, key değeri boş olmasına rağmen, setKey metodu içinde istisnayı yakaladığımız için kodun çalışmaya devam etmiş olması. Bunu şimdilik setKey metodunu şu şekilde değiştirerek çözelim:
public function setKey(string $key) { try { if (($key != "") && (strlen($key) <= 70)) { $this->key = $key; } else { throw new InvalidArgumentException('Wrong key title.'); } } catch (Exception $e) { echo $e->getMessage(); throw new Exception("Cannot create entry"); } }
Sorunu şu anlık çözmek adına catch bloğu içinde kodu durduracak bir istisna fırlattık. Hata kodumuz şu şekilde değişti:
Gördüğümüz gibi kod çalışmaya devam etmedi ve hatanın olduğu yeri tamı tamına bize iletti.
Hata denetimi kimin sorumluluğunda?
Sorumluluk
Konuyu öğretmek açısından metotlarımızda try/catch bloklarını birlikte metotun içinde kullandık. Ancak, bu hatalara karşı önlem almak bizim sınıfımızın sorumluluğunda değil. Hatayı düzeltmek ve buna karşı önlem almak o kodu hatalı çalıştıran kişinin sorumluluğudur. Sınıf, sadece hatalı durumu belirtecek istisnayı fırlatır ve pisliğini temizle kardeşim der. Ayrıca, catch bloklarını tekrar tekrar metotlar içinde aynı şekilde kullandığımızı farkettiniz. Eğer bir yerde sık sık kod tekrarı oluyorsa, tekrarlanan kodun başka bir yere gitmesi ve oralardan erişilmesi gerekiyor. SOLID ilkelerinden henüz detaylıca bahsetmemiştim, ancak Single Responsibility Principle yani Tek Sorumluluk Prensibine göre her sınıfın iyi yapması gereken tek bir iş var. Bu tek sorumluluğa hata denetimi yapmak girebilir ama kodu kullanan kişinin hatasını temizlemek girmez. O zaman sınıfılarımızdaki try/catch bloklarını kaldıracak ve başka bir yere taşıyacağız. (Not: kitapta örnekleri baştan düzeltmek lazım.)
Yani tryb bloğu içindeki ifadeyi bırakalım, ve kalanını silelim. Örneğin setTitle metodu şöyleydi:
public function setKey(string $key) { try { if (($key != "") && (strlen($key) <= 70)) { $this->key = $key; } else { throw new InvalidArgumentException('Wrong key title.'); } } catch (Exception $e) { echo $e->getMessage(); throw new Exception("Cannot create entry"); } }
Bunu şu şekilde değiştirelim:
public function setKey(string $key) { if (($key != "") && (strlen($key) <= 70)) { $this->key = $key; } else { throw new InvalidArgumentException('Wrong key title.'); } }
Gördüğümüz gibi kod artık sadece hata durumunda istisna fırlatıyor, ama bu istisnanın pisliğinin temizlenmesi artık kullanıcının sorumluluğunda. Entry.php ve Dictionary.php içindeki tüm try/catch bloklarını bu şekilde kaldırlarım. Yani try bloğu içindeki kodu dışarı taşıyalım, ve try/catch bloğunu yukarıdaki örnekteki gibi silelim.
Dictionary.php dosyamızın son hali şu şekilde olsun:
<?php
namespace MidoriKocak;
class Dictionary implements DictionaryInterface { private $title; private $entries;
public function __construct(string $title) { $this->setTitle($title); $this->entries = []; }
public function setTitle(string $title) { if (($title != "") && (strlen($title) <= 70)) { $this->title = $title; } else { throw new InvalidArgumentException('Wrong title value.'); } }
public function getTitle(): string { return $this->title; }
public function getEntries(): array { $entries = []; /* @var $entry EntryInterface */ foreach ($this->entries as $entry) { $entries[$entry->getKey()] = $entry->getValues(); } return $entries; }
public function setEntries(array $entries) { if (!empty($entries)) { $this->entries = $entries; } else { throw new InvalidArgumentException('Array cannot be empty.'); } }
public function addEntry(EntryInterface $entry) { $key = $entry->getKey(); $this->entries[$key] = $entry; }
private function isKeyInEntries($key) :bool { if (!array_key_exists($key, $this->entries)) { throw new OutOfBoundsException('Cannot find entry in dictionary'); } else { return true; } }
public function getEntry(string $key): EntryInterface { if ($this->isKeyInEntries($key)) { return $this->entries[$key]; } }
public function deleteEntry(string $key) { if ($this->isKeyInEntries($key)) { unset($this->entries[$key]); } } }
Entry.php dosyamızın son hali de şu şekilde:
<?php
namespace MidoriKocak;
class Entry implements EntryInterface { private $key; private $values;
public function __construct(string $key, string $value) { $this->setKey($key); $this->values = []; array_push($this->values, $value); }
public function setKey(string $key) { if (($key != "") && (strlen($key) <= 70)) { $this->key = $key; } else { throw new InvalidArgumentException('Wrong key title.'); } }
public function getKey(): string { return $this->key; }
public function setValues(array $values) { if (!empty($values)) { $this->values = $values; } else { throw new InvalidArgumentException('Array cannot be empty.'); } }
public function getValues(): array { return $this->values; }
private function isOrderInValues($order) { if (!array_key_exists($order, $this->values)) { throw new OutOfBoundsException('Cannot find entry in dictionary'); } else { return true; } }
public function setValue(int $order, string $newValue) { if ($this->isOrderInValues($order)) { $this->values[$order] = $newValue; } }
public function getValue(int $order): string { if ($this->isOrderInValues($order)) { return $this->values[$order]; } return ""; }
public function addValue(string $value) { array_push($this->values, $value); }
public function deleteValue(int $order) { if ($this->isOrderInValues($order)) { unset($this->values[$order]); } } }
Şimdi app.php dosyamızı da şu şekilde değiştirelim:
print_r($entries); } catch (Exception $e) { echo 'Error on line '.$e->getLine().' in '.$e->getFile() .': <b>'.$e->getMessage(); }
Gördüğümüz gibi tüm kullanıcı kodumuzu try/catch bloğu içinde aldık. Artık elimizde tek bir try/catch bloğu var. Üstelik catch bloğu içinde hata mesajımızı da değiştirdik. Bu sayede hatanın olduğu dosyayı, satırı ve mesajı aynı anda görebileceğiz. Eğer bunu metotlar içinde sürekli tekrarlanan try/catch blokları içinde yapsaydık aynı kodu sürekli yapıştırmamız gerekecekti. Unutmayın, eğer bir yerde kod tekrarı varsa, kötü tasarım vardır.
Şimdi hata kodumuzun nasıl göründüğüne bakalım:
Güzel görünüyor değil mi? Bu sayede kodumuzun neresinde hata olduğunu rahatça anlayıp değiştirebiliyoruz. Tek catch bloğu kullandığımız için, kodumuz ilk hatada duracak, ve kodun kalan kısmı da hatalı bir şekilde çalışmaya devam etmeyecek.
Şimdilik bu kadar. Bu yazıda entry yani girdi sınıfımızı tanımladık ve try/catch bloklarımızın kullanımını düzelttik. Bir sonraki yazıda uygulamamızı tamamlamaya devam edeceğiz.
Önceki yazılarda nesne yönelimli programlama mantığını, sınıflarımızı nasıl oluşturacağımızı, tek sorumluluk prensibini, bilgi gizlemeyi, sihirli yaratıcı metotları kavramaya çalıştık. Bir önceki yazıda sadece metot isimlerini içeren arayüzler yani interface’ler yazdık. Önümüzde iki yol var:
Ya da bu sınıfları kullanacak uygulamayı yazmak. Yani bu sınıfları uygulamayı yazarken test etmek ve çıkabilecek hatalara önceden önlem almak. Biz arayüz yazdığımız için kullanıcının kullanacağı kodları zaten belirtmiş olduk.
PHP’de test konusuna bu yazıda basitçe değineceğim. Çünkü kodumuzu insanlar kullanacak. Daha sonra phpUnit konusuna geldiğimizde detaylıca anlatacağım da. Bu insanların neye ihtiyaçları olduğunu, neler yapabileceklerini az çok tahmin edebiliyorsak, sınıflarımızın içlerini bu kullanıcıların yapacakları hatalara karşı dayanıklı bir şekilde doldurabiliriz. Tabii ki hiçbir kod parçası doğduğu anda hatalara karşı bağışıklık kazanmış değildir. Ama biz önceden tahmin edemeyeceğimiz durumlara olabileceğini aklımızda tutarak kodumuzu yazarsak, daha sonra kafamızı beton duvarlara vurmayız.
Sıradan vatandaş sınıfımızı nasıl kullanır?
Sıradan vatandaş sınıfımızı arayüzlerine bakarak kullanır. Interface yani arayüz, yazdığımız sınıf için aynı zamanda bir kullanım kılavuzu işlevi de görür aslında. Bu nedenle sınıfımızı arayüze uygun hale getirmekle işe başlayabiliriz.
Şimdi Dictionary.php dosyamızın içindeki her şeyi silelim ve şu kodu ekleyelim:
<?php
namespace MidoriKocak;
class Dictionary implements DictionaryInterface { private $title; private $entries;
public function __construct(string $title) { $this->setTitle($title); $this->entries = []; }
public function setTitle(string $title) { if (($title != "") && (strlen($title) <= 70)) { $this->title = $title; } } }
Burada yaptığımız bir kaç değişiklik var. implements kelimesini kullanarak, sınıfımızın DictionaryInterface.php içinde tanımladığımız sözlük arayüzünü kullandığını belirttik. İkinci yaptığımız değişiklik ise __construct metodunun içinde setTitle metodunu çağırdık ki, boş başlık gibi girdilerle, VALIDATION yani doğrulama kurallarımız ezilmesin. Her durumda geçerli olsun.
Doğrulama
Set metotlarının hiçbir şey döndürmediğine dikkat ettiniz mi? Zorunlu olmasa da bu bir kuraldır. Peki bir kullanıcı yanlış başlık girdiğinde bundan nasıl haberimiz olacak? Ya da yanlış başlık kullanılarak sözlük yaratılmasının önüne nasıl geçeceğiz? İşte burada PHP’deki harika programlama yapısı olan try/catch yani deneme/yanılma yapısı devreye giriyor.
Dene ve Yanıl yani Try/Catch
Kodu sürekli hatalara karşı kontrol edeceğiz.
Örneğin set metotlarımızda try/Catch yani deneme yanılma blokları kullanarak, abuk subuk değerler geldiğinde kullanıcıyı bu durumdan haberdar edeceğiz ve bu duruma göre önlem alacağız ki kullanıcı programı çökertmesin. Şimdi setTitle metodumuzu şu şekilde değiştirelim:
public function setTitle(string $title) { try { if (($title != "") && (strlen($title) <= 70)) { $this->title = $title; } else { throw new InvalidArgumentException("Wrong title value."); } } catch (Exception $e) { echo $e->getMessage(); } }
Metodumuzu satır satır okuyalım.
Try/Catch bloklarının dışında kod olmadığına dikkat edin.
Try bloğunu açtığımızda, php yorumlayıcısından, burada olacak hataları, veya diğer bir değişle exception yani istisnai durumları gözetlemesini rica ediyoruz.
if bloğundan sonra, else bloğu ekledik ki, hatalı bir girdi olduğunda throw diyerek, önceden tanımlı istisnalardan birini fırlatalım. Burada invalidArgument yani Hatalı parametre istisnası fırlattık. Bu sayede kodu kullanan kişi, coder, yani oraya hatalı bilgi giren gerizekalı, nerede hata yaptığını anlayacak ve önlemini alacak bi zahmet. İstisna içinde göstermek istediğimiz mesajı “Wrong title value.” diyerek belirttik.
catch bloğu içerisinde Exception $e diyerek ne tipte hataya dikkat etmemiz gerektiğini tanımladık. echo $e->message ifadesiyle meydana gelen istisnanın mesajını ekrana bastık.
İstisna yani Excetion
Genellikle try/catch blokları bu şekilde yazılır. Ancak örneğin biz ekrana cart diye böyle birşey basarsak, bütün sitemizin ya da kullanıcıya döndürdüğümüz örneğin JSON formatındaki değerin yapısı bozulacak. (Not: JSON formatını daha sonra API bölümüne geldiğimizde detaylıca anlatacağım.) Daha sonra bu echo ifadesini değiştireceğiz, ama şimdilik böyle kalsın.
Tüm setter metotlarımızda try/catch mantığını kullanmalıyız. Bu sayede hatalı bir bilgi girişi olduğunda, nasıl bir hatayla karşılaştığımızı anlarız.
Aynı dizinde oluşturduğumuz app.php dosyasını şu şekilde değiştirelim.
$dictionary = new MidoriKocakDictionary("Nesne Yönelimli Programlama Sözlüğü");
Daha sonra bu dosyada sınıfımızı kullanacak diğer kodları yazacağız. Dosyayı web sunucumuzdan çağıralım. Şöyle bir görüntü karşımıza gelecek:
Yani diyor ki, hani verdiğin sözler, vaadettiğin public metodlar nerde? O yüzden devam edelim ve DictionaryInterface.php dosyasında tanımladığımız arayüzdeki metotları Dictionary.php dosyası içinde tanımlamaya devam edelim. setTitle metodundan sonra şu metodları sınıfımıza ekleyelim. Korkmaya gerek yok, hepsini tek tek anlatacağım.
<?php
namespace MidoriKocak;
class Dictionary implements DictionaryInterface { private $title; private $entries;
public function __construct(string $title) { $this->setTitle($title); $this->entries = []; }
public function setTitle(string $title) { try { if (($title != "") && (strlen($title) <= 70)) { $this->title = $title; } else { throw new InvalidArgumentException('Wrong title value.'); } } catch (Exception $e) { echo $e->getMessage(); } }
public function getTitle(): string { return $this->title; }
public function getEntries(): array { $entries = []; foreach ($this->entries as $entry) { $entries[$entry->getKey()] = $entry->getValues(); } return $entries; }
public function setEntries(array $entries) { try { if (!empty($entries)) { $this->entries = $entries; } else { throw new InvalidArgumentException('Array cannot be empty.'); } } catch (Exception $e) { echo $e->getMessage(); } }
public function addEntry(EntryInterface $entry) { $key = $entry->getKey(); $this->entries[$key] = $entry; }
public function getEntry(string $key): EntryInterface { try { if (array_key_exists($key, $this->entries)) { return $this->entries[$key]; } else { throw new OutOfBoundsException('Cannot find entry in dictionary'); } } catch (Exception $e) { echo $e->getMessage(); } }
public function deleteEntry(string $key) { try { if (array_key_exists($key, $this->entries)) { unset($this->entries[$key]); } else { throw new OutOfBoundsException('Cannot find entry in dictionary'); } } catch (Exception $e) { echo $e->getMessage(); } }
}
Şimdi içlerini doldurduğumuz metotlara tek tek gözatalım.
getTitle: Pek açıklayacak birşey yok. Doğrudan sözlüğün başlığını metin tipinde döndürecek.
addEntry: Sözlük sınıfındaki girdiler dizisine istediğimiz başlığı ekleyecek. Burada girdi başlığının boş olup olmamasıyla, ya da girdinin açıklaması olup olmamasıyla artık ilgilenmiyoruz. Bu konular Dictionary yani sözlük sınıfımızın sorumluluğunda değil. Çünkü bunları tek bir sınıfta halletmeye kalksaydık, sözlük sınıfı devasa bir Tanrı nesnesi (God Object) olurdu, ve tek başına her boku yapmaya çalışırdı. Bu metodun içinde $entry değişkeninde girdi Arayüzü sınıfını tanımlayan (implement eden) bir girdi nesnesi geleceğinden emin olduğumuz için, gelen nesnenin doğru dürüst verilere sahip olup olmadığını doğrulamamıza gerek yok. Zaten PHP yorumlayıcısı type hinting yani tip ipucu, yani metodun parametresinin tipini EntryInterface olarak tanımladığımız için, saçma sapan bir değere izin vermeyecek. Bu yüzden tekrar try/catch bloğunda bu değeri oluşabilecek hatalara karşı kontrol etmek için doğrulama yapmamıza gerek yok. Burada daha önce arayüzü açıklarken dediğimiz gibi, set yerine add yani ekle ifadesini kullandık. Çünkü set dediğimiz zaman, sınıfa ait tek bir değişkeni değiştirmeye çalıştığımız anlaşılıyor.
setEntries: Yine burada da try/catch bloğu ile gönderdiğimiz dizinin boş olup olmadığını kontrol ettik. Aslında tek tek dizinin her elemanının EntryInterface tipinde olup olmadığını da kontrol etmemiz gerekiyordu. Ancak bu metodu daha sonra, hızlıca dosyadan sınıfı oluşturmak için kullanacağımız için bu kontrolü yapmayı veriyi kaydetme kısmına bırakıyorum.
getEntries: EntryInterface tipinde objelere sahip olan sınıfın $entries dizisini döndürecek olan metot. Burada kolaylık açısından, diziyi metin içeren elemanlar olarak döndürelim.
getEntry: Burada try/catch bloğu ile doğrulama yaptık çünkü sorulan key yani girdi başlığı değerinin dizi içerisinde olup olmadığını kontrol etmek istiyoruz. İstisna olarak OutOfBoundsException fırlattık. Bu istisna bir dizide olmayan değere işaret ettiğimizi gösteriyor.
deleteEntry: Tıpkı getEntry metodunda olduğu gibi $key değerini hatalara karşı kontrol ettik. Kod tekrarı yaptığımıza dikkat edin.
Sözlük programımızı yazmaya devam ediyoruz.
Kod tekrarlarını önlemek
Şahsen yapılan kod tekrarlarına uyuz olan bir insan olduğum ve kopyalayıp yapıştırılan kod öbekleri, kodumuzu saçma bi şekilde şişmanlatacağından tekrarlanan kodu ayrı bir metot haline getireceğim.
Şimdi en sondaki getEntry ve deleteEntry metotlarını silelim ve şu kodu ekleyelim:
private function isKeyInEntries($key) :bool { try { if (!array_key_exists($key, $this->entries)) { throw new OutOfBoundsException('Cannot find entry in dictionary'); } else { return true; } } catch (Exception $e) { echo $e->getMessage(); } return false; }
public function getEntry(string $key): EntryInterface { if ($this->isKeyInEntries($key)) { return $this->entries[$key]; } }
public function deleteEntry(string $key) { if ($this->isKeyInEntries($key)) { unset($this->entries[$key]); } }
Gördüğünüz gibi isKeyInEntries adında bir metod yazdık. :bool diyerek kesinlikle bool yani true false, yani doğru yanlış tipinde bir değişken döndürmesi gerektiğini belirttik. Metodun sonuna return false koyduk ki, başta belirttiğimiz return sözüne uyalım. Eğer bunu buraya koymazsak, kullandığımız ide veya phpmd, phpcs gibi kod kontrol araçları, neden metod kodunda birşey döndürmüyorsun diyebilir. if bloğu içinde return ifadesi olması yeterli değil. Kod her durumda eğer birşey döndürmeyi taahüt ettiyse o değer tipinde bir değişkeni döndürmeli. (Not: Null Object Pattern kısmında bu konuyu detaylıca anlatacağım.)
isBilmemne şeklinde yazilan metodlar genellikle kontrol metodlarıdır ve true veya false döndürürler. Bu try/catch bloğunda yaptığımız kontrol işini bizim için yapacak olan metot. private diyerek bu metodun sadece sınıf içerisinden erişilebileceğini belirttik. Bu sayede programı kullanan herhangi bir kimsenin bu metodun varlığından haberi olmayacak. Bu şekilde metotlar içinde kullandığınız, zırt pırt tekrar eden kod bloklarını ayrı bir private metot haline getirirsek, sınıflarımızı önemli ölçüde fit bir hale getirmiş oluruz.
Şimdi örneğin hatalı bir durumda ne olacağını görmek için, app.php dosyamızı şu şekilde değiştirelim:
$dictionary = new MidoriKocakDictionary(""); $dictionary->setTitle("Doğru başlık"); $dictionary->getTitle();
app.php dosyasını web sunucumuzdan çağırdığımızda şöyle bir şey karşımıza çıkmalı:
Yakalanmış istisna durumu
Gördüğümüz gibi, kodumuz hatalı girdiyi yakaladı ve php yorumlayıcısı kodun kalan kısmını çalıştırmadı. Bu sayede ne çeşit bir hatayla karşılaştığımızı anladık. Daha sonra bu hata mesajını da özelleştirmeyi göstereceğim.
Şimdilik bu kadar. Bir sonraki yazıda Girdi yani Entry sınıfımızı tanımlayacağız. Sevgiyle kalın.