Laravel API Eğitimi: RESTful API Nasıl Oluşturulur ve Test Edilir

Yayınlanan: 2022-03-11

Mobil geliştirme ve JavaScript çerçevelerinin yükselişiyle birlikte, verileriniz ve istemciniz arasında tek bir arabirim oluşturmak için RESTful API kullanmak en iyi seçenektir.

Laravel, PHP geliştirici üretkenliği göz önünde bulundurularak geliştirilmiş bir PHP çerçevesidir. Taylor Otwell tarafından yazılan ve sürdürülen çerçeve, oldukça fikirlidir ve yapılandırma yerine konvansiyonu tercih ederek geliştiricinin zamanından tasarruf etmeye çalışır. Çerçeve ayrıca web ile birlikte gelişmeyi hedefliyor ve web geliştirme dünyasına iş kuyrukları, hazır API kimlik doğrulaması, gerçek zamanlı iletişim ve çok daha fazlası gibi birçok yeni özellik ve fikir ekledi.

Laravel API Eğitimi - RESTful Web hizmeti oluşturma

Bu eğitimde, kimlik doğrulamalı Laravel kullanarak sağlam bir API oluşturma ve test etme yollarını keşfedeceğiz. Laravel 5.4 kullanacağız ve kodun tamamı GitHub'da referans için mevcut.

RESTful API'ler

Öncelikle, tam olarak neyin RESTful API olarak kabul edildiğini anlamamız gerekiyor. REST, Temsili Durum Aktarımı anlamına gelir ve etkileşim için durumsuz bir protokole (genellikle HTTP) dayanan uygulamalar arasında ağ iletişimi için bir mimari stildir.

HTTP Fiilleri Eylemleri Temsil Eder

RESTful API'lerde, HTTP fiillerini eylemler olarak kullanırız ve uç noktalar, üzerinde işlem yapılan kaynaklardır. Anlamsal anlamları için HTTP fiillerini kullanacağız:

  • GET : kaynakları al
  • POST : kaynaklar oluşturun
  • PUT : kaynakları güncelle
  • DELETE : kaynakları sil

HTTP fiilleri: GET, POST, PUT ve DELETE, RESTful API'lerdeki eylemlerdir

Güncelleme Eylemi: PUT ve POST

RESTful API'ler çok tartışılan bir konudur ve POST , PATCH veya PUT ile güncellemenin en iyisi olup olmadığı veya oluşturma eyleminin PUT fiiline bırakılması konusunda birçok fikir vardır. Bu makalede, HTTP RFC'ye göre güncelleme eylemi için PUT kullanacağız, PUT belirli bir konumda bir kaynak oluşturmak/güncellemek anlamına gelir. PUT fiili için başka bir gereklilik de idempotence'dir, bu durumda temelde bu isteği 1, 2 veya 1000 kez gönderebileceğiniz anlamına gelir ve sonuç aynı olacaktır: veritabanında güncellenmiş bir kaynak.

Kaynaklar

Kaynaklar, bizim durumumuzda Makaleler ve Kullanıcılar gibi eylemlerin hedefleri olacaktır ve kendi uç noktalarına sahiptir:

  • /articles
  • /users

Bu laravel api öğreticisinde, kaynakların veri modellerimizde 1:1 gösterimi olacaktır, ancak bu bir gereklilik değildir. Birden fazla veri modelinde temsil edilen (veya veritabanında hiç temsil edilmeyen) kaynaklara ve kullanıcı için tamamen sınırsız modellere sahip olabilirsiniz. Sonunda, uygulamanıza uygun bir şekilde kaynakları ve modelleri nasıl oluşturacağınıza karar verirsiniz.

Tutarlılık Üzerine Bir Not

REST gibi bir dizi kuralı kullanmanın en büyük avantajı, API'nizi tüketmenin ve geliştirmenin çok daha kolay olacağıdır. Bazı uç noktalar oldukça basittir ve sonuç olarak, GET /get_article?id_article=12 ve POST /delete_article?number=40 gibi uç noktalara sahip olmanın aksine API'nizin kullanımı ve bakımı çok daha kolay olacaktır. Geçmişte bunun gibi korkunç API'ler geliştirdim ve bunun için hala kendimden nefret ediyorum.

Ancak, Oluştur/Al/Güncelle/Sil şemasına eşlemenin zor olacağı durumlar olacaktır. URL'lerin fiil içermemesi gerektiğini ve kaynakların bir tablodaki satırlar olması gerekmediğini unutmayın. Akılda tutulması gereken başka bir şey de, her kaynak için her eylemi uygulamak zorunda olmamanızdır.

Laravel Web Servis Projesi Kurulumu

Tüm modern PHP çerçevelerinde olduğu gibi, bağımlılıklarımızı kurmak ve işlemek için Composer'a ihtiyacımız olacak. İndirme talimatlarını izledikten (ve yol ortam değişkeninize ekledikten sonra), şu komutu kullanarak Laravel'i kurun:

 $ composer global require laravel/installer

Kurulum tamamlandıktan sonra, aşağıdaki gibi yeni bir uygulama oluşturabilirsiniz:

 $ laravel new myapp

Yukarıdaki komut için, $PATH dosyanızda ~/composer/vendor/bin olması gerekir. Bununla uğraşmak istemiyorsanız, Composer'ı kullanarak yeni bir proje de oluşturabilirsiniz:

 $ composer create-project --prefer-dist laravel/laravel myapp

Laravel kuruluyken, sunucuyu başlatabilmeli ve her şeyin çalışıp çalışmadığını test edebilmelisiniz:

 $ php artisan serve Laravel development server started: <http://127.0.0.1:8000> 

Tarayıcınızda localhost:8000'i açtığınızda, Laravel örnek sayfasını görmelisiniz.

Tarayıcınızda localhost:8000 açtığınızda bu örnek sayfayı görmelisiniz.

Taşımalar ve Modeller

İlk geçişinizi gerçekten yazmadan önce, bu uygulama için oluşturulmuş bir veritabanınız olduğundan emin olun ve kimlik bilgilerini projenin kökünde bulunan .env dosyasına ekleyin.

 DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=homestead DB_USERNAME=homestead DB_PASSWORD=secret

Ayrıca, Laravel için özel olarak hazırlanmış bir Vagrant kutusu olan Homestead'i de kullanabilirsiniz, ancak bu, bu makalenin kapsamı dışındadır. Daha fazla bilgi edinmek isterseniz, Homestead belgelerine bakın.

İlk modelimiz ve geçişimiz olan Makale ile başlayalım. Makalenin bir başlığı ve vücut alanı ile oluşturulma tarihi olmalıdır. Laravel, Laravel'in komut satırı aracı olan Artisan aracılığıyla, dosyalar oluşturup bunları doğru klasörlere koyarak bize yardımcı olan birkaç komut sağlar. Makale modelini oluşturmak için şunları çalıştırabiliriz:

 $ php artisan make:model Article -m

-m seçeneği --migration ve Artisan'a modelimiz için bir tane oluşturmasını söyler. İşte oluşturulan göç:

 <?php use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class CreateArticlesTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::create('articles', function (Blueprint $table) { $table->increments('id'); $table->timestamps(); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::dropIfExists('articles'); } }

Bunu bir saniyeliğine inceleyelim:

  • up() ve down() yöntemleri, sırasıyla geçiş yaptığımızda ve geri aldığımızda çalıştırılacaktır;
  • $table->increments('id') , id adıyla otomatik artan bir tamsayı ayarlar;
  • $table->timestamps() bizim için zaman damgalarını ayarlayacaktır— created_at ve updated_at , ancak bir varsayılan ayarlama konusunda endişelenmeyin, Laravel gerektiğinde bu alanları güncellemekle ilgilenir.
  • Ve son olarak, Schema::dropIfExists() tabii ki varsa tabloyu bırakacaktır.

Bu arada, up() yöntemimize iki satır ekleyelim:

 public function up() { Schema::create('articles', function (Blueprint $table) { $table->increments('id'); $table->string('title'); $table->text('body'); $table->timestamps(); }); }

string() yöntemi bir VARCHAR eşdeğeri sütunu oluştururken text() bir TEXT eşdeğeri oluşturur. Bunu yaptıktan sonra, devam edelim ve geçiş yapalım:

 $ php artisan migrate

Burada --step seçeneğini de kullanabilirsiniz ve gerektiğinde bunları ayrı ayrı geri alabilmeniz için her taşımayı kendi toplu işlemine ayırır.

Laravel kutudan çıktığı gibi iki geçişle birlikte gelir: create_users_table ve create_password_resets_table . password_resets tablosunu kullanmayacağız, ancak users tablosunun bizim için hazır olması yardımcı olacaktır.

Şimdi modelimize geri dönelim ve bu nitelikleri $fillable alanına ekleyelim, böylece onları Article::create ve Article::update modellerimizde kullanabiliriz:

 class Article extends Model { protected $fillable = ['title', 'body']; }

$fillable özelliğinin içindeki alanlar, Eloquent'in create() ve update() yöntemleri kullanılarak toplu olarak atanabilir. Birkaç özellik dışında tümüne izin vermek için $guarded özelliğini de kullanabilirsiniz.

Veritabanı Tohumlama

Veritabanı tohumlama, veritabanımızı test etmek için kullanabileceğimiz yapay verilerle doldurma işlemidir. Laravel, bizim için yalnızca doğru sahte veri biçimini oluşturmak için harika bir kitaplık olan Faker ile birlikte gelir. İlk ekme makinemizi oluşturalım:

 $ php artisan make:seeder ArticlesTableSeeder

Ekiciler /database/seeds dizininde bulunacaktır. Birkaç makale oluşturmak için ayarladıktan sonra nasıl göründüğü:

 class ArticlesTableSeeder extends Seeder { public function run() { // Let's truncate our existing records to start from scratch. Article::truncate(); $faker = \Faker\Factory::create(); // And now, let's create a few articles in our database: for ($i = 0; $i < 50; $i++) { Article::create([ 'title' => $faker->sentence, 'body' => $faker->paragraph, ]); } } }

Şimdi tohum komutunu çalıştıralım:

 $ php artisan db:seed --class=ArticlesTableSeeder

Bir Kullanıcılar ekme makinesi oluşturmak için işlemi tekrarlayalım:

 class UsersTableSeeder extends Seeder { public function run() { // Let's clear the users table first User::truncate(); $faker = \Faker\Factory::create(); // Let's make sure everyone has the same password and // let's hash it before the loop, or else our seeder // will be too slow. $password = Hash::make('toptal'); User::create([ 'name' => 'Administrator', 'email' => '[email protected]', 'password' => $password, ]); // And now let's generate a few dozen users for our app: for ($i = 0; $i < 10; $i++) { User::create([ 'name' => $faker->name, 'email' => $faker->email, 'password' => $password, ]); } } }

Veritabanı database/seeds klasörünün içindeki ana DatabaseSeeder sınıfına ekme makinelerimizi ekleyerek daha kolay hale getirebiliriz:

 class DatabaseSeeder extends Seeder { public function run() { $this->call(ArticlesTableSeeder::class); $this->call(UsersTableSeeder::class); } }

Bu şekilde, basitçe $ php artisan db:seed çalıştırabiliriz ve o, run() yönteminde çağrılan tüm sınıfları çalıştıracaktır.

Rotalar ve Kontrolörler

Uygulamamız için temel uç noktaları oluşturalım: oluştur, listeyi al, tek bir tane al, güncelle ve sil. routes/api.php dosyasında basitçe şunu yapabiliriz:

 Use App\Article; Route::get('articles', function() { // If the Content-Type and Accept headers are set to 'application/json', // this will return a JSON structure. This will be cleaned up later. return Article::all(); }); Route::get('articles/{id}', function($id) { return Article::find($id); }); Route::post('articles', function(Request $request) { return Article::create($request->all); }); Route::put('articles/{id}', function(Request $request, $id) { $article = Article::findOrFail($id); $article->update($request->all()); return $article; }); Route::delete('articles/{id}', function($id) { Article::find($id)->delete(); return 204; })

api.php içindeki yolların ön eki /api/ olacak ve API daraltma ara yazılımı bu yollara otomatik olarak uygulanacaktır (ön eki kaldırmak istiyorsanız /app/Providers/RouteServiceProvider.php üzerinde RouteServiceProvider sınıfını /app/Providers/RouteServiceProvider.php ).

Şimdi bu kodu kendi Controller'ına taşıyalım:

 $ php artisan make:controller ArticleController

MakaleController.php:

 use App\Article; class ArticleController extends Controller { public function index() { return Article::all(); } public function show($id) { return Article::find($id); } public function store(Request $request) { return Article::create($request->all()); } public function update(Request $request, $id) { $article = Article::findOrFail($id); $article->update($request->all()); return $article; } public function delete(Request $request, $id) { $article = Article::findOrFail($id); $article->delete(); return 204; } }

routes/api.php dosyası:

 Route::get('articles', 'ArticleController@index'); Route::get('articles/{id}', 'ArticleController@show'); Route::post('articles', 'ArticleController@store'); Route::put('articles/{id}', 'ArticleController@update'); Route::delete('articles/{id}', 'ArticleController@delete');

Örtük rota modeli bağlamayı kullanarak uç noktaları iyileştirebiliriz. Bu şekilde, Laravel, Article örneğini yöntemlerimize enjekte edecek ve bulunamazsa otomatik olarak bir 404 döndürecektir. Rota dosyasında ve denetleyicide değişiklik yapmamız gerekecek:

 Route::get('articles', 'ArticleController@index'); Route::get('articles/{article}', 'ArticleController@show'); Route::post('articles', 'ArticleController@store'); Route::put('articles/{article}', 'ArticleController@update'); Route::delete('articles/{article}', 'ArticleController@delete');
 class ArticleController extends Controller { public function index() { return Article::all(); } public function show(Article $article) { return $article; } public function store(Request $request) { $article = Article::create($request->all()); return response()->json($article, 201); } public function update(Request $request, Article $article) { $article->update($request->all()); return response()->json($article, 200); } public function delete(Article $article) { $article->delete(); return response()->json(null, 204); } }

HTTP Durum Kodları ve Yanıt Formatı Üzerine Bir Not

Ayrıca, uç noktalarımıza response()->json() çağrısını da ekledik. Bu, JSON verilerini açıkça döndürmemize ve istemci tarafından ayrıştırılabilen bir HTTP kodu göndermemize olanak tanır. Döndüreceğiniz en yaygın kodlar şunlar olacaktır:

  • 200 : Tamam. Standart başarı kodu ve varsayılan seçenek.
  • 201 : Nesne oluşturuldu. store işlemleri için kullanışlıdır.
  • 204 : İçerik yok. Bir eylem başarıyla yürütüldüğünde, ancak döndürülecek içerik yok.
  • 206 : Kısmi içerik. Sayfalandırılmış bir kaynak listesi döndürmeniz gerektiğinde kullanışlıdır.
  • 400 : Kötü istek. Doğrulamayı geçemeyen istekler için standart seçenek.
  • 401 : Yetkisiz. Kullanıcının kimliğinin doğrulanması gerekiyor.
  • 403 : Yasak. Kullanıcının kimliği doğrulandı, ancak bir eylemi gerçekleştirme izinlerine sahip değil.
  • 404 : Bulunamadı. Bu, kaynak bulunamadığında Laravel tarafından otomatik olarak döndürülecektir.
  • 500 : Dahili sunucu hatası. İdeal olarak, bunu açıkça iade etmeyeceksiniz, ancak beklenmedik bir şey bozulursa, kullanıcınızın alacağı şey budur.
  • 503 : Hizmet kullanılamıyor. Oldukça açıklayıcı, ancak aynı zamanda uygulama tarafından açıkça döndürülmeyecek başka bir kod.

Doğru Bir 404 Yanıtı Gönderme

Var olmayan bir kaynağı getirmeye çalıştıysanız, bir istisna atılırsınız ve aşağıdaki gibi tüm yığın izini alırsınız:

NotFoundHttpException Stacktrace

Bir JSON yanıtı döndürmek için app/Exceptions/Handler.php içinde bulunan istisna işleyici sınıfımızı düzenleyerek bunu düzeltebiliriz:

 public function render($request, Exception $exception) { // This will replace our 404 response with // a JSON response. if ($exception instanceof ModelNotFoundException) { return response()->json([ 'error' => 'Resource not found' ], 404); } return parent::render($request, $exception); }

İşte bir geri dönüş örneği:

 { data: "Resource not found" }

Diğer sayfaları sunmak için Laravel kullanıyorsanız, Accept başlığıyla çalışmak için kodu düzenlemeniz gerekir, aksi takdirde normal isteklerden gelen 404 hataları da bir JSON döndürür.

 public function render($request, Exception $exception) { // This will replace our 404 response with // a JSON response. if ($exception instanceof ModelNotFoundException && $request->wantsJson()) { return response()->json([ 'data' => 'Resource not found' ], 404); } return parent::render($request, $exception); }

Bu durumda, API isteklerinin Accept: application/json başlığına ihtiyacı olacaktır.

kimlik doğrulama

API Kimlik Doğrulamasını Laravel'de uygulamanın birçok yolu vardır (bunlardan biri Passport, OAuth2'yi uygulamanın harika bir yoludur), ancak bu makalede çok basitleştirilmiş bir yaklaşım izleyeceğiz.

Başlamak için, users tablosuna bir api_token alanı eklememiz gerekecek:

 $ php artisan make:migration --table=users adds_api_token_to_users_table

Ve sonra geçişi uygulayın:

 public function up() { Schema::table('users', function (Blueprint $table) { $table->string('api_token', 60)->unique()->nullable(); }); } public function down() { Schema::table('users', function (Blueprint $table) { $table->dropColumn(['api_token']); }); }

Bundan sonra, aşağıdakileri kullanarak geçişi çalıştırın:

 $ php artisan migrate

Kayıt Uç Noktasını Oluşturma

Kayıt sırasında doğru yanıtı döndürmek için RegisterController ( Auth klasöründe) kullanacağız. Laravel, kutudan çıktığı gibi kimlik doğrulama ile birlikte gelir, ancak istediğimiz yanıtı döndürmek için hala biraz ince ayar yapmamız gerekiyor.

API'ler İngilizce olsaydı, bir api kimlik doğrulama konuşması böyle olurdu

Denetleyici, kaydı uygulamak için RegistersUsers özelliğini kullanır. İşte nasıl çalıştığı:

 public function register(Request $request) { // Here the request is validated. The validator method is located // inside the RegisterController, and makes sure the name, email // password and password_confirmation fields are required. $this->validator($request->all())->validate(); // A Registered event is created and will trigger any relevant // observers, such as sending a confirmation email or any // code that needs to be run as soon as the user is created. event(new Registered($user = $this->create($request->all()))); // After the user is created, he's logged in. $this->guard()->login($user); // And finally this is the hook that we want. If there is no // registered() method or it returns null, redirect him to // some other URL. In our case, we just need to implement // that method to return the correct response. return $this->registered($request, $user) ?: redirect($this->redirectPath()); }

RegisterController yalnızca registered() yöntemini uygulamamız gerekiyor. Yöntem $request ve $user öğelerini alır, yani gerçekten tek istediğimiz bu. Yöntemin denetleyicinin içinde nasıl görünmesi gerektiği aşağıda açıklanmıştır:

 protected function registered(Request $request, $user) { $user->generateToken(); return response()->json(['data' => $user->toArray()], 201); }

Ve onu route dosyasına bağlayabiliriz:

 Route::post('register', 'Auth\RegisterController@register');

Yukarıdaki bölümde, belirteci oluşturmak için Kullanıcı modelinde bir yöntem kullandık. Bu, belirteçleri oluşturmanın yalnızca tek bir yoluna sahip olmamız için yararlıdır. Kullanıcı modelinize aşağıdaki yöntemi ekleyin:

 class User extends Authenticatable { ... public function generateToken() { $this->api_token = str_random(60); $this->save(); return $this->api_token; } }

Ve bu kadar. Kullanıcı artık kayıtlıdır ve Laravel'in doğrulaması ve kullanıma hazır kimlik doğrulaması sayesinde name , email , password ve password_confirmation alanları zorunludur ve geri bildirim otomatik olarak işlenir. Kuralların nasıl uygulandığını görmek için RegisterController içindeki validator() yöntemini kontrol edin.

İşte bu uç noktaya ulaştığımızda elde ettiğimiz şey:

 $ curl -X POST http://localhost:8000/api/register \ -H "Accept: application/json" \ -H "Content-Type: application/json" \ -d '{"name": "John", "email": "[email protected]", "password": "toptal123", "password_confirmation": "toptal123"}'
 { "data": { "api_token":"0syHnl0Y9jOIfszq11EC2CBQwCfObmvscrZYo5o2ilZPnohvndH797nDNyAT", "created_at": "2017-06-20 21:17:15", "email": "[email protected]", "id": 51, "name": "John", "updated_at": "2017-06-20 21:17:15" } }

Oturum Açma Uç Noktası Oluşturma

Tıpkı kayıt uç noktasında olduğu gibi, API kimlik doğrulamamızı desteklemek için LoginController ( Auth klasöründe) düzenleyebiliriz. AuthenticatesUsers özelliğinin login yöntemi, API'mizi desteklemek için geçersiz kılınabilir:

 public function login(Request $request) { $this->validateLogin($request); if ($this->attemptLogin($request)) { $user = $this->guard()->user(); $user->generateToken(); return response()->json([ 'data' => $user->toArray(), ]); } return $this->sendFailedLoginResponse($request); }

Ve onu route dosyasına bağlayabiliriz:

 Route::post('login', 'Auth\LoginController@login');

Şimdi, ekicilerin çalıştırıldığını varsayarsak, bu rotaya bir POST isteği gönderdiğimizde elde ettiğimiz şey:

 $ curl -X POST localhost:8000/api/login \ -H "Accept: application/json" \ -H "Content-type: application/json" \ -d "{\"email\": \"[email protected]\", \"password\": \"toptal\" }"
 { "data": { "id":1, "name":"Administrator", "email":"[email protected]", "created_at":"2017-04-25 01:05:34", "updated_at":"2017-04-25 02:50:40", "api_token":"Jll7q0BSijLOrzaOSm5Dr5hW9cJRZAJKOzvDlxjKCXepwAeZ7JR6YP5zQqnw" } }

Belirteci bir istekte göndermek için, yükte bir api_token niteliği göndererek veya istek başlıklarında Authorization: Bearer Jll7q0BSijLOrzaOSm5Dr5hW9cJRZAJKOzvDlxjKCXepwAeZ7JR6YP5zQqnw .

Oturumu Kapatmak

Mevcut stratejimizle, belirteç yanlış veya eksikse, kullanıcının kimliği doğrulanmamış bir yanıt alması gerekir (bunu bir sonraki bölümde uygulayacağız). Bu nedenle, basit bir çıkış bitiş noktası için belirteci göndereceğiz ve veri tabanından kaldırılacaktır.

routes/api.php :

 Route::post('logout', 'Auth\LoginController@logout');

Auth\LoginController.php :

 public function logout(Request $request) { $user = Auth::guard('api')->user(); if ($user) { $user->api_token = null; $user->save(); } return response()->json(['data' => 'User logged out.'], 200); }

Bu stratejiyi kullanarak, kullanıcının sahip olduğu jeton geçersiz olacak ve API erişimi reddedecektir (bir sonraki bölümde açıklandığı gibi ara yazılımları kullanarak). Kullanıcının herhangi bir içeriğe erişimi olmadan oturum açmasını önlemek için bunun ön uç ile koordine edilmesi gerekir.

Erişimi Kısıtlamak için Ara Yazılımları Kullanma

Oluşturulan api_token ile, route dosyasındaki kimlik doğrulama ara yazılımını değiştirebiliriz:

 Route::middleware('auth:api') ->get('/user', function (Request $request) { return $request->user(); });

Geçerli kullanıcıya $request->user() yöntemini kullanarak veya Auth cephesi aracılığıyla erişebiliriz.

 Auth::guard('api')->user(); // instance of the logged user Auth::guard('api')->check(); // if a user is authenticated Auth::guard('api')->id(); // the id of the authenticated user

Ve şöyle bir sonuç alıyoruz:

Bir InvalidArgumentException Stacktrace

Bunun nedeni, Handler sınıfımızdaki mevcut unauthenticated yöntemi düzenlememiz gerektiğidir. Geçerli sürüm, yalnızca isteğin Accept: application/json başlığına sahip olması durumunda bir JSON döndürür, bu yüzden değiştirelim:

 protected function unauthenticated($request, AuthenticationException $exception) { return response()->json(['error' => 'Unauthenticated'], 401); }

Bunu düzelttikten sonra, onları auth:api ara yazılımına sarmak için makale uç noktalarına geri dönebiliriz. Bunu rota gruplarını kullanarak yapabiliriz:

 Route::group(['middleware' => 'auth:api'], function() { Route::get('articles', 'ArticleController@index'); Route::get('articles/{article}', 'ArticleController@show'); Route::post('articles', 'ArticleController@store'); Route::put('articles/{article}', 'ArticleController@update'); Route::delete('articles/{article}', 'ArticleController@delete'); });

Bu şekilde, rotaların her biri için ara katman yazılımı ayarlamamız gerekmez. Şu anda çok zaman kazandırmıyor, ancak proje büyüdükçe rotaları KURU tutmaya yardımcı oluyor.

Uç Noktalarımızı Test Etme

Laravel, önceden kurulmuş bir phpunit.xml ile kutudan çıktığı gibi PHPUnit ile entegrasyonu içerir. Çerçeve ayrıca bize, özellikle API'leri test etmek için hayatımızı çok daha kolaylaştıran birkaç yardımcı ve ekstra iddia sağlar.

API'nizi test etmek için kullanabileceğiniz bir dizi harici araç vardır; ancak, Laravel içinde test yapmak çok daha iyi bir alternatiftir—veritabanının tam kontrolünü korurken bir API yapısını ve sonuçlarını test etmenin tüm avantajlarına sahip olabiliriz. Örneğin, liste uç noktası için birkaç fabrika çalıştırabilir ve yanıtın bu kaynakları içerdiğini iddia edebiliriz.

Başlamak için, bir bellek içi SQLite veritabanını kullanmak için birkaç ayarı değiştirmemiz gerekecek. Bunu kullanmak, testlerimizin yıldırım hızında çalışmasını sağlayacaktır, ancak takas, bazı geçiş komutlarının (örneğin kısıtlamalar) söz konusu kurulumda düzgün çalışmamasıdır. Geçiş hataları almaya başladığınızda veya performanslı çalıştırmalar yerine daha güçlü bir test setini tercih ettiğinizde testlerde SQLite'tan uzaklaşmanızı öneririm.

Ayrıca her testten önce geçişleri çalıştıracağız. Bu kurulum, her test için veritabanı oluşturmamıza ve ardından testler arasında herhangi bir tür bağımlılıktan kaçınarak onu yok etmemize izin verecektir.

config/database.php dosyamızda, sqlite konfigürasyonundaki database alanını :memory: olarak ayarlamamız gerekecek:

 ... 'connections' => [ 'sqlite' => [ 'driver' => 'sqlite', 'database' => ':memory:', 'prefix' => '', ], ... ]

Ardından, DB_CONNECTION ortam değişkenini ekleyerek phpunit.xml SQLite'ı etkinleştirin:

 <php> <env name="APP_ENV" value="testing"/> <env name="CACHE_DRIVER" value="array"/> <env name="SESSION_DRIVER" value="array"/> <env name="QUEUE_DRIVER" value="sync"/> <env name="DB_CONNECTION" value="sqlite"/> </php>

Bu arada, geriye kalan tek şey, temel TestCase sınıfını geçişleri kullanacak ve her testten önce veritabanını tohumlayacak şekilde yapılandırmak. Bunu yapmak için DatabaseMigrations özelliğini eklememiz ve ardından setUp() yöntemimize bir Artisan çağrısı eklememiz gerekiyor. İşte değişikliklerden sonraki sınıf:

 use Illuminate\Foundation\Testing\DatabaseMigrations; use Illuminate\Foundation\Testing\TestCase as BaseTestCase; use Illuminate\Support\Facades\Artisan; abstract class TestCase extends BaseTestCase { use CreatesApplication, DatabaseMigrations; public function setUp() { parent::setUp(); Artisan::call('db:seed'); } }

Yapmayı sevdiğim son şey, test komutunu composer.json eklemek:

 "scripts": { "test" : [ "vendor/bin/phpunit" ], ... },

Test komutu şu şekilde kullanılabilir olacaktır:

 $ composer test

Testlerimiz için Fabrikalar Kurmak

Fabrikalar, test için doğru verilerle hızlı bir şekilde nesneler oluşturmamıza izin verecek. database/factories klasöründe bulunurlar. Laravel kutudan User sınıfı için bir fabrika ile çıkıyor, o halde Article sınıfı için bir tane ekleyelim:

 $factory->define(App\Article::class, function (Faker\Generator $faker) { return [ 'title' => $faker->sentence, 'body' => $faker->paragraph, ]; });

Faker kitaplığı, modellerimiz için doğru rastgele veri biçimini oluşturmamıza yardımcı olmak için zaten enjekte edilmiştir.

İlk Testlerimiz

Bir uç noktaya kolayca ulaşmak ve yanıtını değerlendirmek için Laravel'in assert yöntemlerini kullanabiliriz. Aşağıdaki komutu kullanarak ilk testimiz olan login testini oluşturalım:

 $ php artisan make:test Feature/LoginTest

Ve işte testimiz:

 class LoginTest extends TestCase { public function testRequiresEmailAndLogin() { $this->json('POST', 'api/login') ->assertStatus(422) ->assertJson([ 'email' => ['The email field is required.'], 'password' => ['The password field is required.'], ]); } public function testUserLoginsSuccessfully() { $user = factory(User::class)->create([ 'email' => '[email protected]', 'password' => bcrypt('toptal123'), ]); $payload = ['email' => '[email protected]', 'password' => 'toptal123']; $this->json('POST', 'api/login', $payload) ->assertStatus(200) ->assertJsonStructure([ 'data' => [ 'id', 'name', 'email', 'created_at', 'updated_at', 'api_token', ], ]); } }

Bu yöntemler birkaç basit durumu test eder. json() yöntemi son noktaya ulaşır ve diğer iddialar oldukça açıklayıcıdır. assertJson() ilgili bir ayrıntı: bu yöntem, yanıtı argüman için bir dizi aramasına dönüştürür, bu nedenle sıra önemlidir. Bu durumda birden çok assertJson() çağrısını zincirleyebilirsiniz.

Şimdi register endpoint testini oluşturalım ve o endpoint için bir çift yazalım:

 $ php artisan make:test RegisterTest
 class RegisterTest extends TestCase { public function testsRegistersSuccessfully() { $payload = [ 'name' => 'John', 'email' => '[email protected]', 'password' => 'toptal123', 'password_confirmation' => 'toptal123', ]; $this->json('post', '/api/register', $payload) ->assertStatus(201) ->assertJsonStructure([ 'data' => [ 'id', 'name', 'email', 'created_at', 'updated_at', 'api_token', ], ]);; } public function testsRequiresPasswordEmailAndName() { $this->json('post', '/api/register') ->assertStatus(422) ->assertJson([ 'name' => ['The name field is required.'], 'email' => ['The email field is required.'], 'password' => ['The password field is required.'], ]); } public function testsRequirePasswordConfirmation() { $payload = [ 'name' => 'John', 'email' => '[email protected]', 'password' => 'toptal123', ]; $this->json('post', '/api/register', $payload) ->assertStatus(422) ->assertJson([ 'password' => ['The password confirmation does not match.'], ]); } }

Ve son olarak, çıkış bitiş noktası:

 $ php artisan make:test LogoutTest
 class LogoutTest extends TestCase { public function testUserIsLoggedOutProperly() { $user = factory(User::class)->create(['email' => '[email protected]']); $token = $user->generateToken(); $headers = ['Authorization' => "Bearer $token"]; $this->json('get', '/api/articles', [], $headers)->assertStatus(200); $this->json('post', '/api/logout', [], $headers)->assertStatus(200); $user = User::find($user->id); $this->assertEquals(null, $user->api_token); } public function testUserWithNullToken() { // Simulating login $user = factory(User::class)->create(['email' => '[email protected]']); $token = $user->generateToken(); $headers = ['Authorization' => "Bearer $token"]; // Simulating logout $user->api_token = null; $user->save(); $this->json('get', '/api/articles', [], $headers)->assertStatus(401); } }

Test sırasında Laravel uygulamasının yeni bir istek üzerine yeniden başlatılmadığına dikkat etmek önemlidir. Bu, kimlik doğrulama ara yazılımına çarptığımızda, veritabanına tekrar çarpmamak için mevcut kullanıcıyı TokenGuard örneğinin içine kaydettiği anlamına gelir. Ancak akıllıca bir seçim - bu durumda, önceden önbelleğe alınmış kullanıcıyla ilgili herhangi bir sorundan kaçınmak için çıkış testini ikiye bölmemiz gerektiği anlamına gelir.

Makale uç noktalarını test etmek de basittir:

 class ArticleTest extends TestCase { public function testsArticlesAreCreatedCorrectly() { $user = factory(User::class)->create(); $token = $user->generateToken(); $headers = ['Authorization' => "Bearer $token"]; $payload = [ 'title' => 'Lorem', 'body' => 'Ipsum', ]; $this->json('POST', '/api/articles', $payload, $headers) ->assertStatus(200) ->assertJson(['id' => 1, 'title' => 'Lorem', 'body' => 'Ipsum']); } public function testsArticlesAreUpdatedCorrectly() { $user = factory(User::class)->create(); $token = $user->generateToken(); $headers = ['Authorization' => "Bearer $token"]; $article = factory(Article::class)->create([ 'title' => 'First Article', 'body' => 'First Body', ]); $payload = [ 'title' => 'Lorem', 'body' => 'Ipsum', ]; $response = $this->json('PUT', '/api/articles/' . $article->id, $payload, $headers) ->assertStatus(200) ->assertJson([ 'id' => 1, 'title' => 'Lorem', 'body' => 'Ipsum' ]); } public function testsArtilcesAreDeletedCorrectly() { $user = factory(User::class)->create(); $token = $user->generateToken(); $headers = ['Authorization' => "Bearer $token"]; $article = factory(Article::class)->create([ 'title' => 'First Article', 'body' => 'First Body', ]); $this->json('DELETE', '/api/articles/' . $article->id, [], $headers) ->assertStatus(204); } public function testArticlesAreListedCorrectly() { factory(Article::class)->create([ 'title' => 'First Article', 'body' => 'First Body' ]); factory(Article::class)->create([ 'title' => 'Second Article', 'body' => 'Second Body' ]); $user = factory(User::class)->create(); $token = $user->generateToken(); $headers = ['Authorization' => "Bearer $token"]; $response = $this->json('GET', '/api/articles', [], $headers) ->assertStatus(200) ->assertJson([ [ 'title' => 'First Article', 'body' => 'First Body' ], [ 'title' => 'Second Article', 'body' => 'Second Body' ] ]) ->assertJsonStructure([ '*' => ['id', 'body', 'title', 'created_at', 'updated_at'], ]); } }

Sonraki adımlar

Hepsi bu kadar. İyileştirme için kesinlikle yer var—OAuth2'yi Passport paketiyle uygulayabilir, bir sayfalandırma ve dönüştürme katmanını entegre edebilirsiniz (Fractal'ı öneririm), liste uzayıp gider—ancak ben Laravel'de API oluşturma ve test etme temellerini gözden geçirmek istedim. harici paketler.

Laravel geliştirme, PHP ile olan deneyimimi kesinlikle geliştirdi ve onunla test etme kolaylığı, çerçeveye olan ilgimi sağlamlaştırdı. Mükemmel değil, ancak sorunları üzerinde çalışmanıza izin verecek kadar esnek.

Genel bir API tasarlıyorsanız, Harika Web API Tasarımı için 5 Altın Kurala göz atın.

İlgili: Tam Kullanıcı Kimlik Doğrulaması ve Erişim Kontrolü – Bir Laravel Pasaport Eğitimi, Pt. 1