Uygulama

PhpWomen Pokemon Proje

Proje kurulumu

Terminalinizden aşağıdaki komutu çalıştırarak laravel projenizin bağımlılıkları ile birlikte kurabilirsiniz.

composer create-project --prefer-dist laravel/laravel pokemon

Bu komut sizin geçerli dizininiz içerisindeki yeni bir pokemon klasörüne Laravel'in yepyeni bir kopyasını indirecek ve yükleyecektir.

Eğer isterseniz, alternatif olarak GitHub'daki Laravel ambarının bir kopyasını elle indirebilirsiniz. Sonra da elle oluşturduğunuz proje dizininizin kökünde

composer install komutunu çalıştırın. Bu komut, frameworkün bağımlılıklarını indirecek ve yükleyecektir.

Tipik olarak, Laravel uygulamalarınızı sunmak için Apache veya Nginx gibi bir web sunucusu kullanabilirsiniz. Eğer sizde PHP 5.4+ var ve PHP'nin yerleşik geliştirme sunucusunu kullanmak isterseniz, serve Artisan komutunu kullanabilirsiniz:

php artisan serve

Dizin Yapısı

Frameworkün yüklenmesinden sonra, dizin yapısıyla aşina olmak için projenize bir göz atın. Projenizdeki app dizini views, controllers ve models gibi klasörler içerir. Uygulamanızın kodlarının çoğu bu dizin içindeki bir yerlerde ikamet eder. Ayrıca, app/config dizinini de inceleyip sizin için sunulmuş yapılandırma seçeneklerini keşfetmek isteyebilirsiniz.

Authentication

Laravel, tek bir basit komut kullanarak authentication için ihtiyacınız olan tüm route ve view dosyalarını ekleme işlemini gerçekleştiriyor.

php artisan make:auth

Bu işlemden sonra kullanıcı eklemek için database/migrations içerisindeki artisan komutu ile gelmiş olan migrationları çalıştırmak. Bu işlemden sonra veritabanınız içerisinde oluşan tabloları inceleyebilirsiniz.

php artisan migrate

Şimdi kayıt olma işlemini gerçekleştirebilirsiniz.

Yeni bir Artisan komutu oluşturalım

Varolan artisan komutları içerisine kendi işlemlerimizi yaptırabileceğimiz artisan komutu oluşturalım. Biz pokemon karakterlerini database’e ekleyeceğimiz için PokemonInitCommand isimli oluşturduk bu alana istediğiniz isimlendirmeyi yapabilirsiniz.

php artisan make:command PokemonInitCommand

Bu dosya app/Console/Comman/PokemonInitCommand.php dizininde oluşturulacaktır.

<?php namespace App\Console\Commands; use Illuminate\Console\Command; class PokemonInitCommand extends Command { /** * The name and signature of the console command. * * @var string */ protected $signature = 'pokemon:init'; /** * The console command description. * * @var string */ protected $description = 'Insert pokemon to database'; /** * Create a new command instance. * * @return void */ public function __construct() { parent::__construct(); } /** * Execute the console command. * * @return mixed */ public function handle() { $this->line('işlem başlatılıyor.'); } }

artık artisan listesinde sizin oluşturduğunuz komutu çalıştırabilirsiniz. Konsol çıktısı handle içerisindeki yazacağınız metin olacaktır. Komut sonucunda yapılacak işlemler handle içerisine yazılmalıdır.

php artisan pokemon:init

RESTful API

HTTP protokolünü kullanarak GET ve POSTgibi isteklerde bulunup, bu isteklere çeşitli formatlarda yanıt aldığı bir dağıtık sistemdir. REST (REpresentational State Transfer), Temsili Durum Aktarımı olarak Türkçede de kullanılır. REST’in tüm prensiplerini yerine getiren API’ler ise RESTful olarak nitelendirilir.

GuzzleHttp

Guzzle, web servisleri ile entegre etmek için HTTP istekleri ve önemsiz şeyleri göndermeyi kolaylaştıran bir PHP HTTP istemcisidir.

https://packagist.org/packages/guzzlehttp/guzzle

composer require guzzlehttp/guzzle

Artık https://pokeapi.co/api/v2/pokemon servisinden pokemon bilgilerini çekebiliriz. Aşağıdaki kodları app/Console/Comman/PokemonInitCommand.php handle içerisinde yazabiliriz.

$client = new Client(); $request = $client->get('https://pokeapi.co/api/v2/pokemon'); $characters = json_decode($request->getBody()->getContents(), true);dd($characters);

php artisan pokemon:init çalıştırırsak pokemonlara ait bilgileri görmüş olacaksınız.

Laravel Migration

Laravel’de migration veritabanınızda version control yapmanızı sağlar. Migration sınıfları içerisinde oluşturduğunuz veritabanı şemaları, geliştirme takımı içerisinde iletişimi kolaylaştırmaktadır. Versiyon kontrol sistemlerinde veritabanı şeması üzerinde yapılan değişiklikleri takip edebilme, eski şemayı kolay güncelleyebilme imkanı sunar.

Bizim pokemonlarımızı characters tablomuzda tutacağız bu tabloyu oluşturmak için aşağıdaki komutu çalıştırmamız gerekiyor.

NOT: Tablo isimleri genel olarak çoğul model isimleri ise tekil kullanılması önerilir.

php artisan make:migration create_characters_table

Bu komut database/migrations altında migration dosyasını oluşturacaktır. Aşağıda characters tablosu bilgilerini bulabilirsiniz.

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

Tablo alanlarınızı oluşturduktan sonra php artisan migrate ile veritabanında characters tablonuzu göreceksiniz.

Laravel Model

Modeller ilgili veritabanı tablosunu sorgulamak için kullanılabilir, aynı zamanda bu tablo içindeki belirli bir satırı temsil eder. Model oluşturmak için;

php artisan make:model Character

Model içinde tablo alanları aşağıdaki gibi tanımlanıyor.

<?php namespace App; use Illuminate\Database\Eloquent\Model; class Character extends Model { protected $fillable = ['pokeapi_id', 'name', 'experience']; }

Tabloya Veri Ekleme

Modelimizi de oluşturduktan sonra pokemonlarımızı veritabanına kayıt edebiliriz. Tekrar app/Console/Comman/PokemonInitCommand.php dönecek olursak son hali aşağıdaki gibi olacaktır.

<?php namespace App\Console\Commands; use App\Character; use GuzzleHttp\Client; use Illuminate\Console\Command; class PokemonInitCommand extends Command { /** * The name and signature of the console command. * * @var string */ protected $signature = 'pokemon:init'; /** * The console command description. * * @var string */ protected $description = 'Initialize pokemon characters'; /** * Create a new command instance. * * @return void */ public function __construct() { parent::__construct(); } /** * Execute the console command. * * @return mixed */ public function handle() { $client = new Client(); $request = $client->get('https://pokeapi.co/api/v2/pokemon'); $characters = json_decode($request->getBody()->getContents(), true); foreach ($characters['results'] as $character) { $pokeApiId = explode('/', $character['url']); Character::create([ 'name' => $character['name'], 'pokeapi_id' => $pokeApiId[6], 'experience' => random_int(0, 100) ]); $this->line("{$character['name']} added"); } $this->line("Done!"); } }

Son olarak php artisan pokemon:init dedikten sonra tablonuzu kontrol edebilirsiniz.

Karakter Listeleme

Öncelikle route oluşturmamız gerekiyor. routes/web.php içerisinde daha öncesinden authentication işlemi ile gelen Auth::routes(); ve laravel içerisinde bulunan home ve welcome sayfaları bulunuyor. Bizde aşağıdaki gibi characters pathi ile gideceğimiz sayfanın route’nu oluşturalum.

Route::get('/characters', '[email protected]');

Yukarıdaki işlemi welcome sayfası gibi direkt view’e göndere biliriz ama bu sayfaya karakterlerin listelendiği değerleri göndereceğimiz için bunu bir Controller ile bağlayacağız.

Controller

Controllerlar app/Http/Controllers altında bulunur. Yeni bir kontrol oluşturmak için ister elle CharactersController.php oluşturabilirsiniz ya da artisan komutu ile aşağıdaki gibi controllerı oluşturabilirsiniz.

php artisan make:controller CharactersController

Route’ta belirtiğimiz gibi Controller içerisinde bir index medotu oluşturuyoruz. Bu method içerisinde karakterlerimizi çağırıp view’e göndereceğiz. Karakterleri listelemek için oluşturduğumuz Character modelinin all() metodunu kullanıyoruz.

public function index() { $characters = Character::all(); return view('characters.index')->with('characters',$characters); }

View

Laravel view modetodu ile url’in hangi sayfaya yönleneceğini belirler. View dosyası resources/views içerisinde bulunur. Yukarıdaki örnekte biz characters klasörü içerisindeki index.blade.php içerisine yönlendirmemizi yapmışız.

View içerisine değer göndermek için ister yukarıdaki gibi with metodu kullanılabilir isterseniz aşağıdaki gibi compact ile gönderebilirsiniz.

return view('characters.show',compact('character'));

Blade Templates

Blade, Laravel ile sağlanan basit ama güçlü şablon motorudur. Diğer popüler PHP şablonlarından farklı olarak Blade, görünümünüzde düz PHP kodunu kullanmanıza sağlar. Tüm bladeler blade.php uzantısını alırlar.

Layout

Proje kurulumu ile birlikte gelen view dosyaları içerisinde gördüğünüz gibi resources/views/layouts/app.blade.php bulunmaktadır. Bu blade diğer bladelerin ana katmanıdır master page layout olarak geçer. Yeni bir blade oluştururken @extends('layouts.app') eklediğimiz tüm bladeler app.blade.php içerisinden klonlanır diyebiliriz. Böylelikle her sayfada benzer olan head, footer, body gibi html taglarını tekrarlamamış olursunuz yani layout viewlerinize bir düzen getirir.

Şimdi blade içerisinde karakterlerimizi listeleyelim.

@extends('layouts.app') @section('content') <div class="container"> <div class="row justify-content-center"> <div class="col-md-8"> <div class="card"> <div class="card-header">Characters</div> <div class="card-body"> <ul> @foreach($characters as $character) <li> <a href="/characters/{{ $character->name }}">{{ $character->name }}</a> </li> @endforeach </ul> </div> </div> </div> </div> </div> @endsection

Karakterleri listelediğimiz gibi karakter detay sayfamızıda oluşturabiliriz. Burada da aynı yolu izleyebiliriz.

Route oluşturma;

Route::get('/characters/{slug}', '[email protected]');

Mevcut Controllera şimdi show metodu oluşturalım. Tek fark bu kez slug değerine denk gelen karakteri getireceğiz.

public function show($slug) { $character = Character::with('users') ->where('name', $slug) ->firstOrFail(); return view('characters.show')->with('character', $character); }

Aynı characters klasörü içerisine show.blade.php dosyası oluşturalım.

@extends('layouts.app') @section('content') <div class="container"> <div class="row justify-content-center"> <div class="col-md-8"> <div class="card"> @if(session()->get('message')) {{ session()->get('message') }} @endif @if(auth()->check()) <div class="card-header"> <form action="/catch" method="POST"> @csrf <input name="character_id" type="hidden" value="{{ $character->id }}"> <button type="submit" class="btn btn-danger">Yakala!</button> </form> </div> @endif <div class="card-body"> <p>{{ $character->name }}</p> <p><strong>Deneyim Puanı</strong> {{ $character->experience }}</p> </div> <ul> @foreach($character->users as $user) <li>{{ $user->name }}</li> @endforeach </ul> <div> </div> </div> </div> </div> </div> @endsection

Post İşlemi

Karakter detayında küçük bir form işlemi bulunuyor. Form işleminde dikkat edilecek iki durum bulunuyor. Birincisi form parametreleri action="/catch" method="POST" action formun post edileceği route aşağıda bulunuyor.

Diğer önemli konu CSRF (Cross Site Request Forgery) genel yapı olarak sitenin açığından faydalanarak siteye sanki o kullanıcıymış gibi erişerek işlem yapmasını sağlar. Genellikle GET requesleri ve SESSION işlemlerinin doğru kontrol edilememesi durumlarındaki açıklardan saldırganların faydalanmasını sağlamaktadır.

Route::post('/catch', '[email protected]');

Bu route’un diğerlerinden tek farkı post olması aynı şekilde bir Controller ve işlemlerinin gerçekleşeceği bir metodu bulunuyor.

Bu işlemi gerçekleştirmek için bir tabloya daha ihtiyacımız var. Bu tablo hangi user hangi pokemona sahip olduğunu tutacak. Önceden yaptığımız gibi bir migration oluşturuyoruz.

php artisan make:migration create_character_user_table

Aşağıdaki gibi alanlarımızı oluşturuyoruz.

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

Migration çalıştırmak için php artisan migrate şimdi pokemon yakalayabiliriz. Bir pokemon ustasının (user) birden fazla pokemonu olabileceği gibi charmander karakteride bir çok pokemon ustasında olabilir. Bu durum Many to Many ilişki olarak bilinir. Bizde modelimize bu durumu tanımlayacağız. User modeline aşağıdaki metodu tanımlıyoruz.

public function characters() { return $this->belongsToMany(Character::class); }

Böylelikle catchAction metodumuz aşağıdaki gibi olacaktır. Kendi kurallarınız oluşturabilirsiniz. Burada bir kullanıcı max 5 pokemona sahip olabilir dedik. Post işlemlerinde metoda Request $request parametre ekleyerek inputları çekiyoruz.

$request->all(); ile tüm inputları görebilirsiniz. Debug için oluşturduğunuz değişkenleri dd($değişken); ile ekrana basabilirsiniz.

public function catchAction(Request $request) { $user = auth()->user(); if ($user->characters->count() >= 5) { session()->flash('error', 'en fazla 5 pokemona sahip olabilirsiniz'); return redirect()->back(); } $user->characters()->attach( $request->get('character_id') ); return redirect('/users/' . $user->id); }

Yukarıda iki işlem mevcut biri authentication olmuş kullanıcının bilgilerini almak ve bu kullanıcının characterslerine attach ile request ile aldığımız character_id eklemek.

Pokemonları Savaştır!

Öncelikle savaşacağımız kullanıcıları listelemeliyiz aynı karakterleri listelediğimiz gibi. Bu işlemi karakter örneğini baz alarak gerçekleştirelim. Bizim için önemli olan listelemeden sonra savaşmak istediğimiz kullanıcı detayı.

Route::get('/users', '[email protected]'); Route::get('/users/{id}', '[email protected]');

Show methodu aşağıdaki gibi user ve ona bağlı karakterlerin toplandığı değişkenimizi view e gönderdiğimiz yer.

public function show($id) { $user = User::with('characters')->find($id); return view('users.show')->with('user', $user); }

resources/views/users/show.blade.php

@extends('layouts.app') @section('content') <div class="container"> <div class="row justify-content-center"> <div class="col-md-8"> <div class="card"> <div class="card-header">{{ $user->name }}</div> <div class="card-body"> <p> <strong>Deneyim Puanı:</strong> {{ $user->experience }} </p> <p> <strong>Email Adresi:</strong> {{ $user->email }} </p> <p>{{ session()->get('message') }}</p> @if(auth()->check()) <ul> @foreach($user->characters as $character) @if(auth()->user()->id != $user->id) <form action="/battle" method="POST"> @csrf <li>{{ $character->name }} ile</li> <input type="hidden" name="user_id" value="{{ $user->id }}"> <input type="hidden" name="character_id" value="{{ $character->id }}"> <select name="my_character_id" id=""> @foreach(auth()->user()->characters as $myCharacters) <option value="{{ $myCharacters->id }}">{{ $myCharacters->name }}</option> @endforeach </select> <button type="submit" class="btn btn-danger">Savaş</button> </form> @endif @endforeach </ul> @endif </div> </div> </div> </div> </div> @endsection

Blade içerisindeki işlem seçilen kullanıcı bilgilerini ve pokemonlarını listelemek ayrıca authenticated olmuş kullanıcının auth()->user()->characters ile hangi pokemonu ile savaşacağını belirleyeceği selectbox oluşturuldu. Form içerisinde üç input bulunuyor. user_id, character_id, my_character_id

Yine bir post işlemimiz var. Yine ilk işimiz route oluşturmak.

Route::post('/battle', '[email protected]');

public function battleAction(Request $request) { $characters = []; $characters[] = $request->get('my_character_id'); $characters[] = $request->get('character_id'); $user = User::find($request->get('user_id')); $pokemon = Character::whereIn('id', $characters)->get()->toArray(); if ($pokemon[0]['experience'] > $pokemon[1]['experience']) { session()->flash('message', 'savaşı siz kazandınız'); $user->experience -= 1; $user->save(); auth()->user()->experience += 1; auth()->user()->save(); return redirect()->back(); } $user->experience += 1; $user->save(); auth()->user()->experience -= 1; auth()->user()->save(); session()->flash('message', 'savaşı kaybettiniz'); return redirect()->back(); }

Buradaki işlem iki pokemonun bilgilerini alıp ilk pokemon $pokemon[0] authenticated olmuş kullanıcının pokemonu diğeri savaştığı kullanıcının. Pokemonların experience değerine göre kazanan kullanıcının experience değerini 1 arttırıp diğer kullanıcının experience değerini 1 eksilterek ekrana sonucu yazıyoruz. Bu işlemi gerçekleştirmek için users tablomuza experience alanı ekleyerek migrationda yeni alan eklemeyide göreceğiz.

php artisan make:migration add_experience_to_users_table

<?php use Illuminate\Support\Facades\Schema; use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Migrations\Migration; class AddExperienceToUsersTable extends Migration { /** * Run the migrations. * * @return void */ public function up() { Schema::table('users', function (Blueprint $table) { $table->integer('experience')->after('remember_token')->default(10); }); } /** * Reverse the migrations. * * @return void */ public function down() { Schema::table('users', function (Blueprint $table) { $table->dropColumn('experience'); }); } }

after işlemi experience alanının remember_token dan sonra gelmesi için, default işlemi ise her kullanıcı için bu alana 10 değerinin verilmesi işlemini gerçekleştiriyor. Yeni alanı User.php’e eklemek gerekiyor.