Ufak Bir Uygulama : PHP Test

PHP ile uğraşırken arada sırada ufak kod parçalarını deneme ihtiyacı hissediyorum. Maalesef PHPStorm proje haricinde dosya oluşturamıyor. Diğer türlüde etrafta test isimli bir çok dosya kalıyor.

Sadece bu ihtiyacımı giderecek bir kaç uygulama buldum App Store’da. Ancak hepsi ücretliydi. Sırf bu şey için 0.99$ ödemeyi geliştirici gururuma yediremedim :) Basit bir şey olduğu için fazlada vaktimi almadı. Bu vesileyle ilk Mac Os uygulamamı da yazmış oldum.

Berbat bir tasarıma, saçma bir isme sahip olan bu uygulamayı kullanmak isterseniz, şu adresten indirebilirsiniz. Kullanmadan önce /usr/bin/php yolunun dolu olduğundan emin olmalısınız. Eğer farklı bir yolda ise php dosyanız, sembolik link oluşturabilirsiniz.

Ipad ve VPN DNS Sorunu

Ipad üzerinde maalesef VPN için özel bir DNS ayarı mevcut değil. Bu yüzden VPN sunucusuna bağlandıktan sonra internete çıkamama gibi bir problemle karşılaşabilirsiniz. Bende maalesef bu sorunla uğraştım biraz.

Yapmanız gereken tek şey sunucunuzdaki /etc/ppp/pptpd-options dosyasında bulunan aşağıdaki ayarları güncellemek:

ms-dns 8.8.8.8
ms-dns 8.8.4.4

ms-wins 8.8.8.8
ms-wins 8.8.4.4

Böylelikle Ipad gibi bu tür ayara sahip olmayan cihazlardaki DNS sorununu aşmış oluyorsunuz. Bu ayarlarla birlikte cihaz DNS bilgilerini otomatik olarak alıyor.

Node.js events Modülü

node.js ile geliştirme yaparken (her yeni başlayan gibi) benim de en zorlandığım konu asenkron kod yazmaya alışmak oldu. Arkaplanda işlemler olaylarla (events) yürüdüğü için, eğer senkron işlem yapacaksanız ya callback kullanmak ya da events modülünü kullanmak gerekiyor. callback yöntemini kullanırsanız, yazdığınız koddan sonra ekrana bir süre anlamsız bir şekilde bakabilirsiniz. Hatta bu anlamsız bakış kodu her okumaya çalışmanızda da oluşur.

events modülü node.js ile birlikte gelmekte. Bu modül içindeki EventEmitter sınıfını extend ederek sınıflarınızı yazabilirsiniz ve böylelikle yazdığınız modüllerin daha işlevsel olmasını sağlarsınız.

Bir uygulama ile konuyu örneklendirelim. Bu masum uygulamamızda kullanıcı adı ve parolayı kontrol ettirip, durum olumlu ise kullanıcıya ait bir model oluşturacağız. İlk örnek kodlar callback yöntemi için.

ApiClientCallback.js:

var ApiClientCallback = function(){};

ApiClientCallback.prototype = {
    getUserModel : function(object, callback){
        var model = new Object();
        model.username = object.username;
        model.password = object.password;

        callback(false, model);
    },
    isUser : function(object, callback){
        if (object.username == "admin" && object.password == "admin")
        {
            this.getUserModel(object, callback);
        }else{
            callback("User not found", null)
        }
    }
};

exports.ApiClientCallback = ApiClientCallback;

callbackTest.js

var ApiClientCallback = require("./ApiClientCallback.js").ApiClientCallback;

var client = new ApiClientCallback();
client.isUser({
    username : "admin",
    password : "admin"
}, function(err, model){
    if (err)
    {
        console.log(err);
    }else{
        console.log("username : " + model.username + " password : " + model.password);
    }
});

Açıkçası bu yöntemin yönetimi bana zor geliyor. Önyargı mı bilemiyorum ancak 6. his debug işlemleri, kod güncellemeleri vb. konularda bu yöntemin epey soruna neden olacağını söylüyor. Çünkü koda her baktığımda kimin kimi çağırdığını kontrol etmem gerekiyor. Bunları takip etmek bu tür ufak kodlar için zor değil belki ama kod satır sayısı arttıkça iç içe dallanma ile bu işlem işkenceye dönüşebilir.

Şimdi aynı örneği events modülü ile yapalım:

ApiClientEvent.js

// events modülünü yükledik.
var events = require("events");

// Balatıcımızı oluşturduk.
var ApiClientEvents = function(){
    events.EventEmitter.call(this);

    // Olaylara sınıf metodlarımızı bağlıyoruz.
    this.on(this.ON_IS_USER_SUCCESS, this.onIsUserSuccess);
    this.on(this.ON_IS_USER_FAIL, this.onIsUserError);
    this.on(this.ON_USER_MODEL_READY, this.onUserModelReady);
};

// Sınıfımızın EventEmitter sınıfını extend etmesini sağladık.
ApiClientEvents.super_ = events.EventEmitter;
ApiClientEvents.prototype = Object.create(events.EventEmitter.prototype, {
    constructor : {
        value : ApiClientEvents,
        enumerable : false
    }
});

// Artık sınıfımızla ilgilenmeye başlayabiliriz.

// Öncelikle olay isimlerimizi tanımlıyoruz. Sınıfla birlikte kullanırken kolaylık oluyor.
ApiClientEvents.prototype.ON_PREFIX = "ApiClientEventsOn";
ApiClientEvents.prototype.ON_IS_USER_SUCCESS = ApiClientEvents.prototype.ON_PREFIX + "IsUserSuccess";
ApiClientEvents.prototype.ON_IS_USER_FAIL = ApiClientEvents.prototype.ON_PREFIX + "IsUserFail";
ApiClientEvents.prototype.ON_USER_MODEL_READY = ApiClientEvents.prototype.ON_PREFIX + "UserModelReady";

// Kullanıcıyı bu metodla kontrol edeceğiz.
ApiClientEvents.prototype.isUser = function(object){
    var self = this;
    if (object.username == "admin" && object.password == "admin")
    {
        // İşlem başarılıysa ON_IS_USER_SUCCESS olayını tetikle. Parametre olarak object i ver.
        self.emit(self.ON_IS_USER_SUCCESS, object);
    }else{
        // İşlem başarısız ise ON_IS_USER_FAIL olayını tetikle. Parametre olarak object i ver.
        self.emit(self.ON_IS_USER_FAIL, object);
    }

    return self;
};

// Kullanıcı için bu metodla model oluşturacağız.
ApiClientEvents.prototype.createUserModel = function(object){
    var self = this;
    var model = new Object();
    model.isLogged = true;
    model.username = object.username;
    model.password = object.password;

    // Model oluşturma işleminden sonra modelin hazır olduğunu ON_USER_MODEL_READY ile duyur.
    self.emit(self.ON_USER_MODEL_READY, model);
    return self;
};

// Olay metodlarımız
ApiClientEvents.prototype.onIsUserSuccess = function(object){
    console.log("onIsUserSuccess");
    var self = this;
    self.createUserModel(object);
    return self;
};

ApiClientEvents.prototype.onIsUserError = function(object){
    console.log("onIsUserError");
};

ApiClientEvents.prototype.onUserModelReady = function(model){
    console.log("onUserModelReady");
    console.log(model);
};

exports.ApiClientEvents = ApiClientEvents;

events_test.js:

var ApiClientEvents = require("./ApiClientEvents.js").ApiClientEvents;

var client = new ApiClientEvents;

client.on(client.ON_USER_MODEL_READY, function(model){
    console.log("Buradayım 1");
});

client.on(client.ON_USER_MODEL_READY, function(model){
    console.log("Buradayım 2");
});

var check = client.isUser({
    username : "admin",
    password : "admin"
});

events modülü ile daha çok kod yazıldığının farkındayım. Ancak daha temiz kodlara sahip olduğumuz kesin. Peki ne yaptık biz bu kodlarda?

İlk başta EventEmitter sınıfını extend ettik. Bu bizim sınıfımıza olayları yönetme özelliği kazandırdı. EventEmitter sınıfının genelde ihtiyaç duyulan on ve emit adlı iki metodu var. on metodu ile ilgili olay için fonksiyonları tanımladık. Aynı olaya birden çok fonksiyon tanımlanabilir. Bu tanımlanan fonksiyonları emit metodu ile çağırdık. emit ile çağrılan bir olay, on ile tanımlı tüm fonksiyonları sırasıyla tetikler.

Olayları sınıfınız içinde tanımlayabildiğiniz gibi, sınıfı kullanacak olanların tanımlaması için olayları hiç tanımlamadan da geliştirmenizi yapabilirsiniz.

Sınıf içinde isimleri ayrı bir değişkene aldım. Bu kullanım açısından epey kolaylık sağlıyor. Aynı zamanda bu yöntemle daha tekil isimler kullanabilirsiniz. Olaylar için bir ön ek belirlemeniz de bu tekilliği sağlamanıza yardımcı olur.

Node.js ve Güncellik Sorunu

node.js kullanırken uygulamadaki herhangi bir güncelleme için sunucunun yeniden başlatılması pek hoş olmuyor. Henüz node.js ile bir proje geliştirip yayınlamadım ancak node.js ile test amaçlı kodlar yazarken bu yeniden başlatma olaylarını gerçekleştirmek zorunda kalıyorum. Konuyu biraz araştırdıktan sonra güzel bir çözüm buldum.

require fonksiyonu ile kişisel modüllerimizi uygulamaya dahil edebiliyoruz. node.js bu modülleri önbellekliyor ve tekrar tekrar kullanmayı engelliyor. Sorunda burada çıkıyor. require.cache listesi ile dosya yoluna göre önbellekleme işlemi yapıyor. Arkaplanda bu dosyaların güncellenmesini takip etmiyor, bu kontrol geliştiriciye kalıyor.

require.cache listesinden önbelleğin silinmesi:

Buradaki örnekte sunucuya bağlanan her client yeni app.js kodlarını çalıştırır.

var net = require("net");

var server = net.createServer(function (stream) {
    // Önbelleği sil.
    delete require.cache[process.cwd() + "/app.js"];
    // Dosyayı yeniden önbellekle.
    var App = require("./app.js");

    var MyApp = new App();
    MyApp.init(stream);
});

server.listen(7000);

Böylelikle sunucuyu durdurmadan sürekli güncel kodların çalışmasını sağlayabiliyorsunuz. Ancak “Performansa etkisi nedir?” sorusu cevapsız kalıyor şu an. Belki fs.watchFile ile farklı bir çözüm üretilebilir.

Büyük Firmalar Neden Akılsız?

Haftasonu http://dictionary.cambridge.org sitesine API desteklerinin olup olmadığını öğrenmek için bir mail attım. Cevabı bugün döndüler: 2011 bitmeden böyle bir desteğe sahip olacaklarını umuyorlarmış (Geliştirici ekibe acıdım şimdi, kovulmazlar inş.). API yerine XML verelim diyorlar..

İlk olarak garibime giden şey böyle bir firmanın (ya da kuruluşun) neden API konsoluna sahip olmadığı. İkincisi ise bana gönderdikleri e-postada sordukları sorular (aşağıda).

Artık müşteri saçma şeylerle vakit kaybetmek istemiyor. Online satış yapan bir sitenin size, aldığınız ürünü ne amaçla kullanacağını sormasını istermiydiniz? Bu bilginin ne cevaplayan ne de soran açısından hiç bir anlamı yok. Olay para kazanmak ve harcamaksa geri kalan saçmalıkları ortadan kaldırmak lazım.

Bırakın parasını harcamak isteyen onu harcasın, siz gerekli alt yapıyı kurun. Böylelikle insanlar daha hızlı ürünlerinize erişebilir. Bu en temel kullanıcı seviyesinde de böyle olmalı, en üst seviyede de. Online satışlardaki kolaylık en temel kullanıcılar için güzel bir örnek. Amazon servislerini ise üst seviye kullanıcılara örnek gösterebiliriz.

Bir – iki haftadır boş vakitlerde Amazon EC2 hizmetini denedim. Kimse ile iletişim kurmadan devasa sunucularda kodlarımı çalıştırdım. Onlar beni bilmiyorlar bende onların kim olduğunu. Ama onlar para kazandı, bende işimi gördüm. Kimseyi telefonla aramak zorunda kalmadım. Her şeyi geliştirilen konsol ile hallettim.

Artık firmaların bu yeni düzene ayak uydurmaları gerekiyor. Üst seviye kullanıcılar çok fazla dataya ihtiyaç duyuyor. Temel kullanıcılara hizmet sunarken geniş boyutlu olaya bakmak lazım.

Use
What is the nature of the proposed use (e.g., a feed, an app, a lookup
feature underlying text, a stand-alone product)?
When are you planning to launch the product?
How would our data be built into the structure of your product?
How will you sell the commercial product, and at what price? What are your
anticipated annual sales?

Data
Do you require British or American English?
Do you require audio pronunciations?
Do you require additional content (e.g., word family information, usage
notes)?

User experience and characteristics
How would your target users experience our data if they use your product?
Can you characterize your target users – adults, younger people, students,
nationality – as well as the size of the potential user base?

Branding
Do you seek to use the Cambridge brand in your product?

Android ve Content-Disposition Sorunu

Üzerinde çalıştığım bir projede içeriklere indirme limiti koyabiliyoruz. Böylelikle kullanıcılar izin verildiği kadar indirme işlemini gerçekleştirebiliyorlar. Sistem tarayıcılarda ve mobil cihazlarda sorunsuz çalışıyor. Ancak bugün test ekibinin bildirdiği bir sorundan yola çıkarak sadece Android üzerinde görülen ilginç bir sorunla karşılaştım.

Bu sorun Android ile gelen öntanımlı tarayıcıda yaşanıyor. Kullanıcı için sistem tarafından üretilen bağlantıya kullanıcı bir kez tıklıyor ancak arkaplanda sunucuya iki istek geliyor. Bunun nedeni de öntanımlı tarayıcının Content-Disposition başlık bilgisinde attachment gördüğünde bağlantıyı İndirme Yöneticisine aktarması. İlk bağlantıya tıklamada tarayıcı istek yapıyor, ikinci istek ise İndirme Yöneticisi tarafından tarayıcının tetiklemesiyle yapılıyor.

Gerçekten sıkıntı verici bir durum. Çünkü bunu tespit edebilmenin bir yolu yok. Her iki istekte birbirinin aynısı. Tespit etseniz dahi dosyanın sorunsuz bir şekilde indirilebilmesi için ikinci isteğe izin vermeniz gerekiyor.

Bununla ilgili bir test dosyası hazırlamıştım. Android telefonlarda denemek isteyenler bu kodları kullanabilirler:

<?php

header("Cache-Control: no-cache, must-revalidate");
header("Expires: Sat, 26 Dec 2011 05:00:00 GMT");
header("Content-Type: application/octet-stream");
header("Content-Disposition: attachment; filename=\"deneme.JPG\"");

error_log(var_export($_SERVER, true), 3, "/epsilon/logs/deneme.log");
echo file_get_contents("deneme.jpg");

İşin diğer kötü tarafı ise ikinci isteğin hep GET ile yapılması. Yani şöyle düşünün; bir form üzerinden özel bir kod girilerek mobil cihaza dosya teslim ettireceksiniz. Müşteri kodu girip gönder butonuna basıyor. Form bilgileri POST ile alınıp dosya teslim ediliyor, sonra İndirme Yöneticisine durum aktarılıyor. İndirme Yöneticisi bu sefer GET ile sayfaya gidiyor ve aslında dosya yerine kod giriş sayfasını indiriyor. Onla alakalıda şu örneğe göz atabilirsiniz:

<?php
if ($_SERVER['REQUEST_METHOD'] == 'POST' 
	&& isset($_POST['kod'])
	&& $_POST['kod'] = 123)
{
	header("Cache-Control: no-cache, must-revalidate");
	header("Expires: Sat, 26 Dec 2011 05:00:00 GMT");
	header("Content-Type: application/octet-stream");
	header("Content-Disposition: attachment; filename=\"deneme.JPG\"");

	error_log(var_export($_SERVER, true), 3, "/epsilon/logs/deneme.log");
	echo file_get_contents("deneme.jpg");
	exit;
}
error_log(var_export($_SERVER, true), 3, "/epsilon/logs/deneme.log");
header('Content-Type: text/html; charset=utf-8');
?>

<form method="POST" action="">
<input type="text" name="kod" value="" />
<button type="submit">Gönder</button>
</form>

Bu sorun giderilene kadar dikkatli kod yazmak lazım.

Pes

3 aydan fazla bir süredir twitter, facebook, google+ gibi sosyal ağ hesaplarını kapatıp yaşamaya çalıştım. Bunu devam ettiremeyeceğim sanırım, pes ediyorum :) Maalesef çevrede olup bitenlerden bu şekilde çok fazla haber alamıyorum.

Yalnız beni kendine çeken aslında biraz da Twitter’ın yeni tasarımıydı. Yeni açtığım hesapla inceleme fırsatı buldum (eski hesapta aktifleşmemiş tasarım). Gerçekten çok kullanışlı olmuş. Benim üyeliğimi tekrar kazanmak için çok büyük bir hamle :P