Laravel API Eğitimi: RESTful API Nasıl Oluşturulur ve Test Edilir
Yayınlanan: 2022-03-11Mobil 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.
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
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>
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()
vedown()
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
veupdated_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'increate()
veupdate()
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:
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.
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:
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.