Eski PHP Projeleri için REST API Oluşturma
Yayınlanan: 2022-03-11Bir REST API'si oluşturmak veya mimarisini oluşturmak, özellikle eski PHP projeleri için yapmanız gerektiğinde kolay bir iş değildir. Günümüzde bir REST API'yi uygulamayı kolaylaştıran birçok 3. taraf kitaplığı var, ancak bunları mevcut eski kod tabanlarına entegre etmek oldukça göz korkutucu olabilir. Ve her zaman Laravel ve Symfony gibi modern çerçevelerle çalışma lüksüne sahip değilsiniz. Eski PHP projelerinde, kendinizi genellikle PHP'nin eski sürümlerinin üzerinde çalışan, kullanımdan kaldırılmış şirket içi çerçevelerin ortasında bir yerde bulabilirsiniz.
Bu makalede, REST API'lerini sıfırdan uygulamaya çalışmanın bazı genel zorluklarına, bu sorunları çözmenin birkaç yoluna ve eski projeler için özel PHP tabanlı API sunucuları oluşturmaya yönelik genel bir stratejiye göz atacağız.
Makale PHP 5.3 ve üzerini temel alsa da, temel kavramlar PHP'nin 5.0 sürümünden sonraki tüm sürümleri için geçerlidir ve hatta PHP dışı projelere bile uygulanabilir. Burada, genel olarak bir REST API'nin ne olduğunu ele almayacağız, bu nedenle, aşina değilseniz, önce onu okuduğunuzdan emin olun.
Takip etmenizi kolaylaştırmak için, bu makale boyunca kullanılan bazı terimlerin ve anlamlarının bir listesi:
- API sunucusu: API'ye hizmet veren ana REST uygulaması, bu durumda PHP ile yazılmıştır.
- API uç noktası: istemcinin bir eylemi gerçekleştirmek ve sonuç üretmek için iletişim kurduğu bir arka uç “yöntemi”.
- API uç noktası URL'si: Arka uç sisteminin dünya tarafından erişilebilir olduğu URL.
- API belirteci: Kullanıcının tanımlanabileceği HTTP üstbilgileri veya çerezler aracılığıyla geçirilen benzersiz bir tanımlayıcı.
- Uygulama: API uç noktaları aracılığıyla REST uygulamasıyla iletişim kuracak istemci uygulaması. Bu makalede, web tabanlı (masaüstü veya mobil) olduğunu ve dolayısıyla JavaScript ile yazıldığını varsayacağız.
İlk Adımlar
Yol Kalıpları
Karar vermemiz gereken ilk şeylerden biri, API uç noktalarının hangi URL yolunda kullanılabilir olacağıdır. 2 popüler yol vardır:
- api.example.com gibi yeni bir alt etki alanı oluşturun.
- example.com/api gibi bir yol oluşturun.
Bir bakışta, ilk varyant daha popüler ve çekici görünebilir. Ancak gerçekte, projeye özel bir API oluşturuyorsanız, ikinci varyantı seçmek daha uygun olabilir.
İkinci yaklaşımı benimsemenin en önemli nedenlerinden biri, bu, tanımlama bilgilerinin kimlik bilgilerini aktarmak için bir araç olarak kullanılmasına izin vermesidir. Tarayıcı tabanlı istemciler, XHR istekleri içinde uygun çerezleri otomatik olarak göndererek, ek bir yetkilendirme başlığı ihtiyacını ortadan kaldırır.
Diğer bir önemli neden de, özel başlıkların bazı proxy sunucuları tarafından kaldırılabileceği alt alan yapılandırması veya yönetim sorunları ile ilgili herhangi bir şey yapmanıza gerek olmamasıdır. Bu, eski projelerde sıkıcı bir çile olabilir.
Tanımlama bilgilerinin kullanılması, REST isteklerinin durum bilgisi içermemesi gerektiği için "RESTful" bir uygulama olarak kabul edilebilir. Bu durumda bir uzlaşma yapabilir ve belirteç değerini özel bir başlık aracılığıyla iletmek yerine bir çereze iletebiliriz. Etkili bir şekilde, çerezleri doğrudan session_id yerine belirteç değerini iletmenin bir yolu olarak kullanıyoruz. Bu yaklaşım vatansız olarak kabul edilebilir, ancak bunu tercihlerinize bırakabiliriz.
API uç noktası URL'leri de sürümlendirilebilir. Ayrıca, yol adında bir uzantı olarak beklenen yanıt biçimini içerebilirler. Bunlar kritik olmasa da, özellikle API geliştirmenin ilk aşamalarında, uzun vadede bu ayrıntılar kesinlikle işe yarayabilir. Özellikle yeni özellikler uygulamanız gerektiğinde. İstemcinin hangi sürümü beklediğini kontrol ederek ve geriye dönük uyumluluk için gerekli formatı sağlayarak en iyi çözüm olabilir.
API uç noktası URL yapısı aşağıdaki gibi görünebilir:
example.com/api/${version_code}/${actual_request_path}.${format}
Ve gerçek bir örnek:
example.com/api/v1.0/records.json
yönlendirme
API uç noktaları için bir temel URL seçtikten sonra yapmamız gereken sonraki şey, yönlendirme sistemimizi düşünmektir. Mevcut bir çerçeveye entegre edilebilir, ancak bu çok hantalsa, olası bir geçici çözüm, belge kökünde "api" adlı bir klasör oluşturmaktır. Bu şekilde API tamamen ayrı bir mantığa sahip olabilir. API mantığını aşağıdaki gibi kendi dosyalarına yerleştirerek bu yaklaşımı genişletebilirsiniz:
"www/api/Apis/Users.php" dosyasını belirli bir API uç noktası için ayrı bir "denetleyici" olarak düşünebilirsiniz. Mevcut kod tabanındaki uygulamaları yeniden kullanmak, örneğin veritabanıyla iletişim kurmak için projede zaten uygulanmış modelleri yeniden kullanmak harika olurdu.
Son olarak, “/api/*” adresinden gelen tüm istekleri “/api/index.php” adresine yönlendirdiğinizden emin olun. Bu, web sunucusu yapılandırmanızı değiştirerek yapılabilir.
API Sınıfı
Sürüm ve Biçim
API uç noktalarınızın hangi sürümleri ve biçimleri kabul ettiğini ve varsayılanların ne olduğunu her zaman açıkça tanımlamanız gerekir. Bu, eski işlevleri korurken gelecekte yeni özellikler oluşturmanıza olanak tanır. API sürümü temelde bir dize olabilir, ancak daha iyi anlamak ve karşılaştırılabilirlik için sayısal değerler kullanabilirsiniz. Küçük sürümler için yedek rakamlara sahip olmak iyidir, çünkü yalnızca birkaç şeyin farklı olduğunu açıkça gösterir:
- v1.0 ilk sürüm anlamına gelir.
- v1.1 bazı küçük değişikliklerle ilk sürüm.
- v2.0 tamamen yeni bir sürüm olacaktır.
Biçim, JSON, XML ve hatta CSV dahil ancak bunlarla sınırlı olmamak üzere müşterinizin ihtiyaç duyduğu herhangi bir şey olabilir. URL aracılığıyla bir dosya uzantısı olarak sağlayarak, API uç noktası url'si okunabilirliği sağlar ve API tüketicisinin hangi formatı bekleyebileceklerini bilmeleri hiç de kolay olmaz:
- “/api/v1.0/records.json”, JSON kayıt dizisini döndürür
- “/api/v1.0/records.xml” XML kayıt dosyasını döndürür
Bu biçimlerin her biri için yanıtta uygun bir İçerik Türü başlığı göndermeniz gerekeceğini de belirtmekte fayda var.
Gelen bir istek aldığınızda yapmanız gereken ilk şeylerden biri, API sunucusunun istenen sürümü ve formatı destekleyip desteklemediğini kontrol etmektir. Gelen isteği işleyen ana yönteminizde, istenen biçim ve sürümün desteklenip desteklenmediğini belirlemek için $_SERVER['PATH_INFO'] veya $_SERVER['REQUEST_URI'] ayrıştırın. Ardından, devam edin veya bir 4xx yanıtı döndürün (örn. 406 “Kabul Edilemez”). Buradaki en kritik kısım, her zaman müşterinin beklediği bir şeyi geri vermektir. Buna bir alternatif, URL yolu uzantısı yerine "Kabul Et" istek başlığını kontrol etmek olabilir.
İzin Verilen Rotalar
API denetleyicilerinize her şeyi şeffaf bir şekilde iletebilirsiniz, ancak beyaz listeye alınmış bir izin verilen yol kümesi kullanmak daha iyi olabilir. Bu, esnekliği biraz azaltacaktır, ancak bir sonraki koda döndüğünüzde API uç noktası URL'lerinin nasıl göründüğüne dair çok net bir fikir sağlayacaktır.
private $public_routes = array( 'system' => array( 'regex' => 'system', ), 'records' => array( 'regex' => 'records(?:/?([0-9]+)?)', ), );
Bunları daha temiz hale getirmek için ayrı dosyalara da taşıyabilirsiniz. Yukarıdaki yapılandırma, şu URL'lere yönelik istekleri etkinleştirmek için kullanılacaktır:
/api/v1.0/system.json /api/v1.0/records.json /api/v1.0/records/7.json
PUT Verilerini İşleme
PHP, gelen POST verilerini otomatik olarak işler ve onu $_POST superglobal altına yerleştirir. Ancak, PUT taleplerinde durum böyle değildir. Tüm veriler php://input içine "gömülü". Gerçek API yöntemini çağırmadan önce onu ayrı bir yapıya veya diziye ayırmayı unutmayın. Basit bir parse_str yeterli olabilir, ancak istemci çok parçalı istek gönderiyorsa, form sınırlarını işlemek için ek ayrıştırma gerekebilir. Çok parçalı isteklerin tipik kullanım durumu, dosya yüklemelerini içerir. Çok parçalı istekleri algılama ve işleme aşağıdaki gibi yapılabilir:
self::$input = file_get_contents('php://input'); // For PUT/DELETE there is input data instead of request variables if (!empty(self::$input)) { preg_match('/boundary=(.*)$/', $_SERVER['CONTENT_TYPE'], $matches); if (isset($matches[1]) && strpos(self::$input, $matches[1]) !== false) { $this->parse_raw_request(self::$input, self::$input_data); } else { parse_str(self::$input, self::$input_data); } }
Burada parse_raw_request şu şekilde uygulanabilir:
/** * Helper method to parse raw requests */ private function parse_raw_request($input, &$a_data) { // grab multipart boundary from content type header preg_match('/boundary=(.*)$/', $_SERVER['CONTENT_TYPE'], $matches); $boundary = $matches[1]; // split content by boundary and get rid of last -- element $a_blocks = preg_split("/-+$boundary/", $input); array_pop($a_blocks); // loop data blocks foreach ($a_blocks as $id => $block) { if (empty($block)) { continue; } // parse uploaded files if (strpos($block, 'application/octet-stream') !== false) { // match "name", then everything after "stream" (optional) except for prepending newlines preg_match("/name=\"([^\"]*)\".*stream[\n|\r]+([^\n\r].*)?$/s", $block, $matches); // parse all other fields } else { // match "name" and optional value in between newline sequences preg_match('/name=\"([^\"]*)\"[\n|\r]+([^\n\r].*)?\r$/s', $block, $matches); } $a_data[$matches[1]] = $matches[2]; } }
Bununla, ham girdi olarak Api::$input'ta ve bir ilişkisel dizi olarak Api::$input_data'da gerekli istek yükünü alabiliriz.

Sahte PUT/DELETE
Bazen kendinizi sunucunun standart GET/POST HTTP yöntemleri dışında hiçbir şeyi desteklemediği bir durumda görebilirsiniz. Bu sorunun yaygın bir çözümü, PUT/DELETE veya diğer herhangi bir özel istek yöntemini "sahte" etmektir. Bunun için “_method” gibi bir “magic” parametresi kullanabilirsiniz. $_REQUEST dizinizde görürseniz, isteğin belirtilen türden olduğunu varsayın. Laravel gibi modern çerçeveler, içlerinde yerleşik olarak bu tür işlevselliklere sahiptir. Sunucunuzun veya istemcinizin sınırlamaları olması durumunda (örneğin bir kişi işinin Wi-Fi ağını kurumsal proxy arkasında PUT isteklerine izin vermeyen kullanıyorsa) büyük uyumluluk sağlar.
Belirli API'ye Yönlendirme
Mevcut proje otomatik yükleyicilerini yeniden kullanma lüksünüz yoksa, spl_autoload_register işlevi yardımıyla kendinizinkini oluşturabilirsiniz. “api/index.php” sayfanızda tanımlayın ve “api/Api.php” içinde bulunan API sınıfınızı çağırın. API sınıfı bir ara katman yazılımı görevi görür ve gerçek yöntemi çağırır. Örneğin, “/api/v1.0/records/7.json”a yapılan bir istek, parametre 7 ile “Apis/Records.php” GET yöntemini çağırarak sona ermelidir. mantık temizleyici Tabii ki, bunu kullandığınız çerçeveye daha derinlemesine entegre etmek ve belirli denetleyicilerini veya yollarını yeniden kullanmak mümkünse, bu olasılığı da göz önünde bulundurmalısınız.
İlkel otomatik yükleyicili "api/index.php" örneği:
<?php // Let's define very primitive autoloader spl_autoload_register(function($classname){ $classname = str_replace('Api_', 'Apis/', $classname); if (file_exists(__DIR__.'/'.$classname.'.php')) { require __DIR__.'/'.$classname.'.php'; } }); // Our main method to handle request Api::serve();
Bu, Api sınıfımızı yükleyecek ve ana projeden bağımsız olarak onu sunmaya başlayacaktır.
SEÇENEKLER İstekler
Bir istemci, benzersiz belirtecini iletmek için özel başlık kullandığında, tarayıcının önce sunucunun bu başlığı ne zaman desteklediğini kontrol etmesi gerekir. İşte burada OPTIONS isteği devreye girer. Amacı, hem istemci hem de API sunucusu için her şeyin yolunda ve güvenli olmasını sağlamaktır. Bu nedenle, bir istemci herhangi bir şey yapmaya çalıştığında SEÇENEKLER isteği tetikleniyor olabilir. Ancak, bir istemci kimlik bilgileri için tanımlama bilgilerini kullandığında, tarayıcıyı bu ek SEÇENEKLER isteğini göndermek zorunda kalmaktan kurtarır.
Bir müşteri çerezlerle POST /users/8.json talep ediyorsa, talebi oldukça standart olacaktır:
- Uygulama, /users/8.json'a bir POST isteği gerçekleştirir.
- Tarayıcı isteği gerçekleştirir ve bir yanıt alır.
Ancak özel yetkilendirme veya belirteç başlığıyla:
- Uygulama, /users/8.json'a bir POST isteği gerçekleştirir.
- Tarayıcı, isteği işlemeyi durdurur ve bunun yerine bir SEÇENEKLER isteği başlatır.
- SEÇENEKLER isteği /users/8.json'a gönderilir.
- Tarayıcı, API tarafından tanımlandığı şekilde mevcut tüm yöntemlerin ve başlıkların bir listesiyle yanıt alır.
- Tarayıcı, yalnızca özel başlık mevcut başlıklar listesinde mevcutsa orijinal POST isteğiyle devam eder.
Ancak, çerezleri kullanırken bile PUT/DELETE ile bu ek SEÇENEKLER isteğini alabileceğinizi unutmayın. Bu yüzden ona cevap vermeye hazır olun.
Kayıt API'sı
Basit yapı
Örnek Kayıtlar API'miz oldukça basittir. Tüm istek yöntemlerini içerecek ve çıktıyı aynı ana API sınıfına geri döndürecektir. Örneğin:
<?php class Api_Records { public function __construct() { // In here you could initialize some shared logic between this API and rest of the project } /** * Get individual record or records list */ public function get($id = null) { if ($id) { return $this->getRecord(intval($id)); } else { return $this->getRecords(); } } /** * Update record */ public function put($record_id = null) { // In real world there would be call to model with validation and probably token checking // Use Api::$input_data to update return Api::responseOk('OK', array()); } // ...
Bu nedenle, her bir HTTP yöntemini tanımlamak, API'yi REST tarzında daha kolay oluşturmamızı sağlayacaktır.
Çıktıyı Biçimlendirme
Veritabanından müşteriye geri alınan her şeyle naif bir şekilde yanıt vermek, feci sonuçlara yol açabilir. Verilerin kazara açığa çıkmasını önlemek için, yalnızca beyaz listeye alınmış anahtarları döndürecek belirli bir biçim yöntemi oluşturun.
Beyaz listeye alınmış anahtarların bir başka yararı da, bunlara dayalı olarak belgeler yazabilmeniz ve tüm tür kontrollerini yapabilmenizdir, örneğin, user_id'nin her zaman bir tamsayı olmasını, is_banned bayrağının her zaman boolean true veya false olmasını ve tarih saatlerinin tek bir standarda sahip olmasını sağlar. yanıt biçimi.
Sonuçların Çıktısı
Başlıklar
Başlık çıktısı için ayrı yöntemler, tarayıcıya gönderilen her şeyin doğru olmasını sağlayacaktır. Bu yöntem, API'yi aynı etki alanı üzerinden erişilebilir hale getirmenin avantajlarını kullanırken, özel yetkilendirme başlığı alma olasılığını korur. Aynı veya 3. taraf etki alanı arasındaki seçim, HTTP_ORIGIN ve HTTP_REFERER sunucu başlıkları yardımıyla yapılabilir. Uygulama, istemcinin x-yetkilendirmesi (veya başka bir özel başlık) kullandığını algılıyorsa, tüm kaynaklardan erişime izin vermelidir, özel başlığa izin verin. Yani şöyle görünebilir:
header('Access-Control-Allow-Origin: *'); header('Access-Control-Expose-Headers: x-authorization'); header('Access-Control-Allow-Headers: origin, content-type, accept, x-authorization'); header('X-Authorization: '.YOUR_TOKEN_HERE);
Ancak, istemci tanımlama bilgisi tabanlı kimlik bilgileri kullanıyorsa, başlıklar biraz farklı olabilir ve kimlik bilgileri için yalnızca istenen ana bilgisayar ve tanımlama bilgisi ile ilgili başlıklara izin verir:
header('Access-Control-Allow-Origin: '.$origin); header('Access-Control-Expose-Headers: set-cookie, cookie'); header('Access-Control-Allow-Headers: origin, content-type, accept, set-cookie, cookie'); // Allow cookie credentials because we're on the same domain header('Access-Control-Allow-Credentials: true'); if (strtolower($_SERVER['REQUEST_METHOD']) != 'options') { setcookie(TOKEN_COOKIE_NAME, YOUR_TOKEN_HERE, time()+86400*30, '/', '.'.$_SERVER['HTTP_HOST']); }
OPTIONS isteğinin çerezleri desteklemediğini ve bu nedenle uygulamanın çerezleri göndermeyeceğini unutmayın. Ve son olarak bu, istediğimiz tüm HTTP yöntemlerinin erişim kontrolünün sona ermesine izin verir:
header('Access-Control-Allow-Methods: POST, GET, OPTIONS, PUT, DELETE'); header('Access-Control-Max-Age: 86400');
Gövde
Gövdenin kendisi, müşteriniz tarafından istenen bir biçimde yanıtı, başarı durumunda 2xx HTTP durumu, istemci nedeniyle başarısızlık durumunda 4xx durumu ve sunucu nedeniyle başarısızlık durumunda 5xx durumu ile içermelidir. Yanıtın yapısı değişebilir, ancak “durum” ve “yanıt” alanlarının belirtilmesi de faydalı olabilir. Örneğin, istemci yeni bir kullanıcı kaydetmeye çalışıyorsa ve kullanıcı adı zaten alınmışsa, HTTP durumu 200 olan ancak gövdede şuna benzeyen bir JSON olan bir yanıt gönderebilirsiniz:
{“status”: “ERROR”, “response”: ”username already taken”}
… doğrudan HTTP 4xx hatası yerine.
Çözüm
Hiçbir iki proje tamamen aynı değildir. Bu makalede özetlenen strateji sizin durumunuza uygun olabilir veya olmayabilir, ancak yine de temel kavramlar benzer olmalıdır. Her sayfanın arkasında en son trend veya güncel çerçeveye sahip olamayacağını ve bazen “REST Symfony paketim neden burada çalışmıyor” konusundaki öfkenin faydalı bir şey inşa etmek için bir motivasyona dönüşebileceğini belirtmekte fayda var. işe yarayan bir şey. Nihai sonuç, her zaman özel ve projeye özgü bir uygulama olacağı için parlak olmayabilir, ancak günün sonunda çözüm gerçekten işe yarayan bir şey olacaktır; ve bunun gibi bir senaryoda, her API geliştiricisinin hedefi bu olmalıdır.
Burada tartışılan kavramların örnek uygulamaları, kolaylık olması için bir GitHub deposuna yüklenmiştir. Bu örnek kodları oldukları gibi doğrudan üretimde kullanmak istemeyebilirsiniz, ancak bu, bir sonraki eski PHP API entegrasyon projeniz için kolayca bir başlangıç noktası olarak çalışabilir.
Son zamanlarda bazı eski projeler için bir REST API sunucusu uygulamak zorunda mıydınız? Aşağıdaki yorum bölümünde deneyiminizi bizimle paylaşın.