Laravel - MVC პრინციპზე დაფუძნებული პლატფორმა, ფრეიმვორკი, ინსტრუმენტთა ნაკრები იმ პროგრამისტებისათვის, რომლებიც PHP-ის მეშვეობით ამზადებენ ვებ-გვერდებს. ფრეიმვორკის დახმარებით გაცილებით სწრაფად და მარტივად ვქმნით აპლიკაციებს და აღარ გვიწევს კოდის წერის დაწყება ნოლიდან. ფაქტიურად ფრეიმვორკი წარმოადგენს მომავალი პროექტის კარკასს, ჩონჩხს, მასში ჩადებულია ისეთი კლასები რომელთა მეშვეობითაც პროგრამისტს აღარ უწევს მთელი რიგი სტანდარტული და რუტინული სამუშაოების ჩატარება, მაგალითად: საიტზე შესვლის წერტილის შექმნა, ადამიანისათვის გასაგები URL-ების შექმნა, შემავალი პარამეტრებისა და მონაცემების ვალიდაციის მექანიზმის შექმნა, მონაცემთა ბაზასთან მუშაობის ორგანიზება და ა.შ
Laravel-ის პირველი ვერსია შეიქმნა 2011 წელს (ავტ. Taylor Otwell).
დიდი ალბათობით, Laravel-ის შესწავლისას მომხმარებელს უკვე ექნება შეხება PHP-ს რომელიმე ინტერპრეტატორთან და მონაცემთა ბაზის სერვერთან, ეს შეიძლება იყოს როგორც ცალკე დაინსტალირებული პროგრამები, მაგ: ვებ-სერვერი Apache, PHP ინტერპრეტატორი, მბ სერვერი MYSQL, ასევე გამზადებული ნაკრებები მაგ: Denver, OpenServer, XAMPP, WAMP, LAMP და ა.შ. შესაძლებელია მათი გამოყენებაც.
ინტალაციის პროცესი მოგვთხოვს გზას PHP-მდე, რომელიც შეიძლება გამოიყურებოდეს ასე:
composer
თუ შედეგად ვიხილავ ამდაგვარ სურათს:
composer create-project <PACKAGE_NAME> <MY_PROJECT>
ჩნდება კითხვები : მაინც სად არის ინახება ეს პაკეტები ? საიდან მოაქვს Composer-ს ეს ყველაფერი ?
როდესაც Composer-ი ინსტალირდება, ნაგულისხმეობის პრინციპით ხდება ერთადერთი საცავის რეგისტრაცია, ეს საცავია ვებ-გვერდი Packagist.org.
composer create-project laravel/laravel laravel
ბრძანების გაშვების შემდეგ დაიწყება Laravel-ის ინსტალაცია და აგრეთვე შეიქმნება laravel საქაღალდე შესაბამისი ფაილებისა და საქაღალდეების სტრუქტურით :
Artisan-ი არის Laravel-ში ჩადგმული ბრძანებათა ინტერფეისის სახელწოდება, რომლის მეშვეობითაც შესაძლებელია სხვადასხვა საჭირო ბრძანებებისა და ინსტრუქციების საკმაოდ მარტივად გაშვება აპლიკაციაზე მუშობის პროცესში. Artisan ბრძანებათა სრული ჩამონათვალის სანახავად უნდა გავუშვათ შემდეგი ბრძანება :
php artisan list
php artisan serve
შედეგი იქნება :
ამ საქაღალდეში აგრეთვე მოთავსებულია კატალოგი cache, სადაც თავმოყრილი ფაილები გამოიყენება ფრეიმვორკის მუშაობის დროს მიმდინარე პროცესების ოპტიმიზაციისათვის (მაგ: მარშრუტიზაცია, ფაილთა ქეშირება)
php artisan app:name <name-of-your-application>
პროექტის კეთების ეტაპი შესაძლებელია დავყოთ სამ ნაწილად:
APP_NAME=Laravel
APP_ENV=local
APP_KEY=base64:G1LDmxoK8uRi9uWtKO0ae4TQgLmTN7VEJszlIncU1BA=
APP_DEBUG=true
APP_URL=http://localhost
LOG_CHANNEL=stack
LOG_LEVEL=debug
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=
BROADCAST_DRIVER=log
CACHE_DRIVER=file
QUEUE_CONNECTION=sync
SESSION_DRIVER=file
SESSION_LIFETIME=120
MEMCACHED_HOST=127.0.0.1
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=smtp
MAIL_HOST=mailhog
MAIL_PORT=1025
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS=null
MAIL_FROM_NAME="${APP_NAME}"
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
PUSHER_APP_CLUSTER=mt1
MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}"
MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
გარემოს ცვლადების მნიშვნელობები მოქცეულია სუპერგლობალურ ცვლადში - $_ENV. მნიშვნელობათა განსაზღვრა შესაძლებელია env() დამხმარე ფუნქციის მეშვეობითაც, რომელსაც პირველ არგუმენტად უნდა გადაეცეს პარამეტრის დასახელება, მეორე არგუმენტად კი შეგვიძლია გადავცეთ ნაგულისმები მნიშვნელობა იმ შემთხვევისათვის თუ ფაილში ვერ მოიძებნება მითითებული გასაღების შესაბამისი პარამეტრი.
შესაძლებელია, რომ .env ფაილში აღვწეროთ ჩვენი საკუთარი პარამეტრებიც. თუ პარამეტრის მნიშვნელობა შეიცავს გამოტოვებულ ადგილებს მაშინ მნიშვნელობა უნდა ჩაისვას ორმაგ ბრჭყალებში:
...
CUSTOM_PARAM="CUSTOM PARAM VALUE"
...
მთავარი გვერდის მარშრუტის დამმუშვებელი ფუნქცია გადავაკეთოთ შემდეგნაირად :
Route::get('/', function () {
echo '<pre>';
print_r($_ENV);
echo '</pre>';
echo '<pre>';
print_r($_ENV['APP_DEBUG']);
echo '</pre>';
echo '<pre>';
print_r(env('SESSION_LIFETIME'));
echo '</pre>';
echo '<pre>';
print_r(env('CUSTOM_PARAM'));
echo '</pre>';
echo '<pre>';
print_r(env('UNKNOWN_PARAM','UNKNOWN PARAM VALUE'));
echo '</pre>';
});
თუ ახლა შევალთ შემდეგ მისამართზე : http://127.0.0.1:8000/, ვიხილავთ შემდეგ სურათს :
'name' => env('APP_NAME', 'Laravel'),
რომლის მნიშვნელობაც ბრუნდება env ფუნქციის მეშვეობით, ეს ფუნქცია ინფორმაციას იღებს .env ფაილიდან, და ამ ინფორმაციას უტოლებს მასივის
გასაღებებს, მას აგრეთვე მითითებული აქვს მეორე პარამეტრიც, რომელიც გამოიყენება ნაგულისხმეობის პრინციპით იმ შემთხვევაში თუ .env ფაილში
ასეთი კონფიგურაციული პარამეტრი არ არის განსაზღვრული.
ეს ყველაფერი რომ უფრო კარგად გავიგოთ განვიხილოთ მასივის სხვა ელემენტიც:
'debug' => env('APP_DEBUG', false),
როგორც ვიცით, "debug" კონფიგურაციული პარამეტრი განსაზღვრავს გამოჩნდეს თუ არა დაშვებული შეცდომების შესახებ შეტყობინებები. თუ .env ფაილში
არ არის მითითებული შესაბამისი მნიშვნელობა, მაშინ სისტემა ამ პარამეტრს მიანიჭებს მნიშვნელობას - false.
მასივის ერთ-ერთი გასაღები არის შემდეგი :
'url' => env('APP_URL', 'http://localhost'),
ეს იქნება პირველი პარამეტრი, რომელსაც ჩვენ შევცვლით და მივუთითებთ იმ დომენს ან ip მისამართს სადაც გაშვებულია პროექტი, ჩემს შემთხვევაში ეს არის:
'url' => env('APP_URL', 'http://127.0.0.1:8000'),
შემდეგი გასაღები არის :
'timezone' => 'UTC',
სადაც ხდება დროის სარტყელის მითითება, ჩავანაცვლოთ ნაგულისმები მნიშვნელობა ჩვენთვის სასურველით
'timezone' => 'Asia/Tbilisi',
კიდევ ერთი გასაღები არის
'key' => env('APP_KEY'),
აქ ეთითება ფრეიმვორკის საიდუმლო გასაღები, შევნიშნოთ, რომ ამ შემთხვევაში "env" ფუნქციას არ გადაეცემა ნაგულისმები მნიშვნელობა არგუმენტად,
ეს იმიტომ რომ საიდუმლო გასაღების გენერირება ხდება ფრეიმვორკის ინსტალაციის დროს და მას აგენერირებს Composer-ი.
მასივის providers გასაღებში მოქცეულია ყველა ხელმისაწვდომი სერვის-პროვაიდერი, რომელთა ჩატვირთვაც ხდება ფრეიმვორკის ამუშავებისას. თუ რა არის სერვის-პროვაიდერ, ცოტა მოგვიანებით განვმარტავთ.
მასივის ბოლო გასაღები არის - aliases, მასში შეტანილია ფასადების ანუ სისტემური კლასების ფსევდონიმები. თუ რა არის ფასადი და როგორ ვიმუშაოთ მასთან, ცოტა მოგვიანებით განვმარტავთ.
'default' => env('DB_CONNECTION', 'mysql')
ანუ მონაცემთა ბაზის სამართავი ნაგულისმები სისტემა, შევამჩნიოთ რომ ამ გასაღების მნიშვნელობაც .env ფაილიდან მოდის env ფუნქციის
მეშვეობით, მაგრამ იქ მონაცემთა ბაზის სამართავი სისტემის დასახელება განსაზღვრული ჯერჯერობით არ გვაქვს, ამიტომ ან უნდა განვსაზღროთ იგი .env ფაილში
DB_CONNECTION=mysql
ანდა არ განვსაზღვროთ და სისტემა გამოიყენებს ნაგულისმებ მნიშვნელობას, რომელიც env ფუნქციას მეორე პარამეტრად აქვს გადაცემული.
$value = config('app.timezone');
// განვსაზღვროთ პარამეტრი თუ მისი მნიშვნელობა ვერ მოიძებნა
$value = config('app.timezone', 'Asia/Tbilisi');
კონფიგურაციული პარამეტრის მნიშვნელობის განსასაზღვრავად config დამხმარეს უნდა გადავცეთ პარამეტრისა და მისი მნიშვნელობის შემცველი
აცოციაციური მასივი :
config(['app.timezone' => 'America/Chicago']);
php artisan config:cache
ბრძანების გაშვების შემდეგ შეიქმნება ფაილი bootstrap/cache/config.php, რომელშიც აღწერილი იქნება ყველა კონფიგურაციული პარამეტრის
შემცველი ასოციაციური მასივი.
თუ config: cache ბრძანებას გავუშვებთ მუშაობის პროცესში ანუ მაშინ, როდესაც აპლიკაცია ჯერ დასრულებული არ იქნება, დარწმუნებულები უნდა ვიყოთ, რომ env() დამხმარე ფუნქციას ვიძახებთ, მხოლოდ და მხოლოდ კონფიგურაციულ ფაილებში და არსად სხვაგან, ვინაიდან ქეშირების შემდეგ env ფაილის ჩატვირთვა საერთოდ აღარ ხდება და შესაბამისად ვერც რაიმე პარამეტრის ამოღებას შევძლებთ მისგან (ქეშირებისას კონფიგურაციულ ფაილებში env() ფუნქციის დახმარებით განსაზღვრული პარამეტრები ავტომატურად შეინახებოდა bootstrap/cache/config.php ფაილში).
php artisan config:clear
ბრძანების გაშვების შემდეგ bootstrap/cache/config.php ფაილი წაიშლება.
'debug' => (bool) env('APP_DEBUG', false),
require __DIR__.'/../vendor/autoload.php';
ამის შემდეგ იქმნება აპლიკაციის გლობალური ობიექტი :
$app = require_once __DIR__.'/../bootstrap/app.php';
bootstrap/app.php :
...
$app = new Illuminate\Foundation\Application(
$_ENV['APP_BASE_PATH'] ?? dirname(__DIR__)
);
...
return $app;
ამის შემდეგ საქმეში ერთვება მოთხოვნათა დამმუშვებელი ძირითადი კლასი Kernel, რომელიც აღწერილია app/Http/Kernel.php ფაილში.
ეს კლასი არის იგივე სახელწოდების მქონე Illuminate\Foundation\Http\Kernel კლასის მემკვიდრე
(სრული მისამართი : vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php). ამ კლასში აღწერილია $bootstrappers მასივი:
...
protected $bootstrappers = [
\Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class, // გარემოს ცვლადთა ჩამტვირთველი
\Illuminate\Foundation\Bootstrap\LoadConfiguration::class, // კონფიგურაციული პარამეტრების ჩამტვირთველი
\Illuminate\Foundation\Bootstrap\HandleExceptions::class, // გამონაკლისთა დამმუშავებელი
\Illuminate\Foundation\Bootstrap\RegisterFacades::class, // ფასადების რეგისტრაცია (ვისაუბრებთ ოდნავ მოგვიანებით)
\Illuminate\Foundation\Bootstrap\RegisterProviders::class, // პროვაიდერთა რეგისტრაცია (ვისაუბრებთ ოდნავ მოგვიანებით)
\Illuminate\Foundation\Bootstrap\BootProviders::class, // პროვაიდერთა ჩატვირთვა
];
...
სწორედ ამ კლასების ჩატვირთვა ხდება მანამ სანამ დამუშვდება მომხმარებლის მიერ გაკეთებული მოთხოვნა.
ამავე კლასის handle() მეთოდში ხდება მოთხოვნის შესაბამისი პასუხის გენერირება ანუ დაბრუნება.
დავუბრუნდეთ ისევ მემკვიდრე Kernel კლასს :)) მასში აღწერილია $middleware მასივი:
...
protected $middleware = [
// \App\Http\Middleware\TrustHosts::class,
\App\Http\Middleware\TrustProxies::class,
\Fruitcake\Cors\HandleCors::class,
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
];
...
ამ მასივში აღწერილია ის შუამავლები, რომლებიც აუცილებლად უნდა გაიაროს მომხმარებლის მიერ გაკეთებულმა მოთხოვნამ (შუამავლების შესახებ ვისაუბრებთ ოდნავ მოგვიანებით).
ავტოჩატვირთვისა და ყველა საჭირო პროცესის ინიციალიზაციის შემდეგ მოთხოვნა გადაეცემა მარშრუტიზატორს და ფრეიმვორკი ღებულობს გადაწყვეტილებას თუ რომელმა მარშრუტმა უნდა დაამუშავოს ესა თუ ის მოთხოვნა. მარშრუტმა მოთხოვნა შეიძლება გადასცეს კონკრეტულ კონტროლერს ან უბრალოდ ფუნქციას, თუ მოთხოვნა კონტროლერს გადაეცემა, იგი ჩაატარებს შესაბამის სამუშაოებს (ინფორმაციის გადამოწმება, ვალიდაცია და ა.შ) და თავის მხრივ მიმართავს მოდელს, თუ აუცილებელია მოდელი მიმართავს მონაცემთა ბაზას , ბაზა დაუბრუნებს მას პასუხს, მოდელი ამ პასუხს დაუბრუნებს კონტროლერს, კონტროლერი კი მიღებულ პასუხს გადასცემს წარმოდგენის შაბლონს, წარმოდგენის შაბლონი უზრუნველჰყოფს შედეგის ბრაუზერში გამოტანას. ხოლო თუ მარშრუტიზატორი მოთხოვნას არ გადასცემს კონტროლერს, არამედ გადასცემს ფუნქციას, მაშინ ეს ფუნქცია დაამუშავებს მოთხოვნას და შედეგს გადასცემს პირდაპირ წარმოდგენას.ინგ: Injection - ინექცია; დანერგვა; შემოღება; ჩადება;
თუ კონკრეტული კლასის მუშაობისათვის საჭიროა, გამოყენებულ იქნას სხვა კლასის ფუნქციონალი, ეს იმას ნიშნავს, რომ პირველი კლასი დამოკიდებულია მეორე კლასზე. ბუნებრივია უნდა მოვახდინოთ დამხმარე კლასის საწყის კლასში ინტეგრირება (Injection) და მხოლოდ ამის შემდეგ შეგვეძლება დამხმარე კლასის ფუნქციონალის გამოყენება. სწორედ ამ დამხმარე კლასს ეწოდება დამოკიდებულება (Dependency).
...
use Illuminate\Http\Request;
...
class SomeController extends Controller
{
...
public function post(Request $request)
{
$request->validate([
// ...
]);
// ...
}
...
}
ამ მაგალითზე შეიძლება ითქვას, რომ SomeController კლასში მოვახდინეთ Request დამოკიდებულების
ინექცია.
Route::get('/', function(){
echo '<pre>';
print_r(app());
echo '</pre>';
die;
});
თუ ახლა აპლიკაციის მთავარ გვერდზე შევალთ, ვიხილავთ ამდაგვარ სურათს :
namespace App\Services;
class MathService
{
public function doAddition($numbers)
{
return array_sum($numbers);
}
}
ასევე შევქმნათ შესაბამისი მარშრუტი და კონტროლერი :
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\MathController;
Route::get('/math', [MathController::class, 'index']);
MathController :
namespace App\Http\Controllers;
use App\Services\Mathservice;
class MathController extends Controller
{
public function index()
{
$serv = new Mathservice();
dd($serv->doAddition([4,6])); // 10
}
}
გადავაკეთოთ კონტროლერი ამდაგვარად :
namespace App\Http\Controllers;
use App\Services\Mathservice;
class MathController extends Controller
{
public function index(Mathservice $serv)
{
dd($serv->doAddition([4,6])); // 10
}
}
შედეგი აქაც იგივე იქნება, ანუ Mathservice სერვისის ამ სახით ინექციაც გასაგებია სისტემისათვის.
ახლა ამოცანა და შესაბამისად სერვისიც, გადავაკეთოთ ასე : სერვისმა დაგვიბრუნოს არა მასივის ელემენტების ჯამი, არამედ ამ ჯამს დამატებული კიდევ ერთი რიცხვი, რომელიც აღწერილი იქნება სერვისის კერძო თვისებაში - $add_param :
namespace App\Services;
class MathService
{
private $add_param;
public function __construct($add_param)
{
$this->add_param = $add_param;
}
public function doAddition($numbers)
{
return array_sum($numbers) + $this->add_param;
}
}
ასეთ შემთხვევაში ვიხილავთ შემდეგ შეტყობინებას :
Unresolvable dependency resolving [Parameter #0 [ <required> $add_param ]] in class App\Services\MathService
ეს იმას ნიშნავს, რომ სისტემამ ვერ იპოვა ის დამოკიდებულება, რომელიც მოვთხოვეთ. სწორედ ამ პრობლემის მოგვარებაში დაგვეხმარება სერვისისების კონტეინერი
და სერვისის პროვაიდერი.
ნებისმიერი პროვაიდერი არის Illuminate\Support\ServiceProvider კლასის მემკვიდრე და შეიცავს ორ მეთოდს : register და boot. როდესაც აპლიკაცია იტვირთება, სისტემა აკითხავს ყველა არსებული პროვაიდერის register მეთოდს და ასრულებს თითოეულ მათგანში აღწერილ ინსტრუქციებს. პროვაიდერის შექმნა შესაძლებელია შემდეგი ბრძანების მეშვეობით :
php artisan make:provider MathServiceProvider
namespace App\Providers;
use App\Services\Mathservice;
use Illuminate\Support\ServiceProvider;
class MathServiceProvider extends ServiceProvider
{
/**
* Register services.
*
* @return void
*/
public function register()
{
$this->app->bind(Mathservice::class, function ($app) {
// add_param : პარამეტრი, რომელიც უნდა დაემატოს მასივის ელემენტთა ჯამს
return new Mathservice(add_param : 25);
});
}
/**
* Bootstrap services.
*
* @return void
*/
public function boot()
{
//
}
}
ახლა დავარეგისტრიროთ პროვაიდერი config/app.php ფაილში აღწერილ მასივში:
...
'providers' => [
...
App\Providers\MathServiceProvider::class,
...
],
...
ახლა უკვე ყველაფერი რიგზე იქნება. ანუ ჩვენ Mathservice სერვისი დავარეგისტრირეთ სერვისების კონტეინერში სერვისის პროვაიდერის
დახმარებით:
namespace App\Http\Controllers;
use App\Services\Mathservice;
class MathController extends Controller
{
public function index(Mathservice $serv)
{
dd($serv->doAddition([4,6])); // 4 + 6 = 10 + 25 = 35
}
}
...
public function register()
{
$this->app->singleton(Someclass::class, function ($app) {
//
});
}
...
namespace App\Providers;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;
class ComposerServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
View::composer('view', function () {
//
});
}
}
წარმოდგენის ფაილებზე უფრო დაწვრილებით ვისაუბრებთ მოგვიანებით.
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Route;
Route::get('/cache', function () {
return Cache::get('key');
});
namespace App\Services;
class MathService
{
public static function doAddition($numbers)
{
return array_sum($numbers);
}
}
შევქმნათ ფაილი app/Helpers/Facades/Mathfacade.php. პირველი რაც ფასადის შექმნისას უნდა გავაკეთოთ, არის ის, რომ ხელახლა უნდა აღვწეროთ
აბსტრაქტული მშობელი კლასის - Facade-ს getFacadeAccessor მეთოდი :
namespace App\Helpers\Facades;
use Illuminate\Support\Facades\Facade;
class Mathfacade extends Facade
{
protected static function getFacadeAccessor()
{
return 'mathfacade';
}
}
ამ მეთოდის დანიშნულება არის ის, რომ დააბრუნოს სერვისების კონტეინერში არსებული, კონკრეტული ფუნქციონალის შესაბამისი
სიტყვაგასაღები ანუ მეტსახელი. ჩვენს შემთხვევაში ეს მეტსახელი არის -
mathfacade. ახლა გადავინაცვლოთ სერვისპროვაიდერში :
namespace App\Providers;
use App\Services\Mathservice;
use Illuminate\Support\ServiceProvider;
class MathServiceProvider extends ServiceProvider
{
/**
* Register services.
*
* @return void
*/
public function register()
{
$this->app->bind('mathfacade', function ($app) {
return new Mathservice();
});
}
/**
* Bootstrap services.
*
* @return void
*/
public function boot()
{
//
}
}
public __call ( string $name , array $arguments ) : mixed
public static __callStatic ( string $name , array $arguments ) : mixed
მაგალითად :
class MethodTest
{
public function __call($name, $arguments)
{
echo "მეთოდი '$name' " . implode(', ', $arguments). "\n";
}
public static function __callStatic($name, $arguments)
{
echo "მეთოდი '$name' " . implode(', ', $arguments). "\n";
}
}
$obj = new MethodTest;
$obj->runTest('ობიექტის კონტექსტში');
MethodTest::runTest('სტატიკურ კონტექსტში');
ამ კოდის შედეგი იქნება :
მეთოდი 'runTest' ობიექტის კონტექსტში
მეთოდი 'runTest' სტატიკურ კონტექსტში
public static function __callStatic($method, $args)
{
$instance = static::getFacadeRoot();
if (! $instance)
{
throw new RuntimeException('A facade root has not been set.');
}
return $instance->$method(...$args);
}
ამ მეთოდის გამოძახება ხდება მაშინ, როდესაც მივმართავთ ფასადის არარსებულ მეთოდს. იგი ახდენს ფასადის მეთოდის გადამისამართებას
სერვისების კონტეინერში არსებული, სასურველი კლასის ობიექტზე და რეალურად ჩვენ უკვე ამ ობიექტს და მის მეთოდს მივმართავთ.
__callStatic მეთოდში თავიდანვე ხდება getFacadeRoot() სტატიკური მეთოდის გამოძახება, დავაკვირდეთ ამ მეთოდს :
public static function getFacadeRoot()
{
return static::resolveFacadeInstance(static::getFacadeAccessor());
}
ეს მეთოდი დავის მხრივ იძახებს resolveFacadeInstance მეთოდს, რომელსაც არგუმენტად გადაეცემა, ჩვენს მიერ დასაწყისშივე აღწერილი
მეთოდი getFacadeAccessor. resolveFacadeInstance მეთოდის კოდი ასეთია :
protected static function resolveFacadeInstance($name)
{
if (is_object($name))
{
return $name;
}
if (isset(static::$resolvedInstance[$name]))
{
return static::$resolvedInstance[$name];
}
if (static::$app)
{
return static::$resolvedInstance[$name] = static::$app[$name];
}
}
განსაკუთრებული ყურადღება მივაქციოთ ამ ჩანაწეერს : static::$app[$name], სწორედ $app
სტატიკური თვისების უკან მოიაზრება სერვისების კონტეინერი. თავად ჩანაწერი კი აბრუნებს სერვისების კონტეინერში, ჩვენს მიერ განსაზღვრული სიტყვაგასაღების
(mathfacade) შესაბამის კლასს, ეს კლასი კი სერვისის პროვაიდერში გვაქვს აღწერილი :
namespace App\Providers;
use App\Services\Mathservice;
use Illuminate\Support\ServiceProvider;
class MathServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->bind('mathfacade', function ($app) {
return new Mathservice();
});
}
...
}
შესაბამისად წორედ Mathservice() კლასის ობიექტთან მოხდება გადამისამართება, როდესაც ფასადის არარსებულ მეთოდს
გამოვიძახებთ.
იმისათვის რათა ჩვენმა ფასადმა იმუშავოს, უნდა დავარეგისტრიროთ აღნიშნული ფასადის შესაბამისი ფსევდონიმი, ეს უნდა გავაკეთოთ config/app.php ფაილში :
...
'providers' => [
...
App\Providers\MathServiceProvider::class,
],
'aliases' => [
...
'Mathfacade' => App\Helpers\Facades\Mathfacade::class,
],
...
ახლა კი შეგვიძლია მივმართოთ ჩვენს ფასადს, ეს გავაკეთოთ MathController-ში:
namespace App\Http\Controllers;
use Mathfacade;
class MathController extends Controller
{
public function index()
{
$res = Mathfacade::doAddition([2,5]);
dd($res); // 7
}
}
https://vnadiradze.ge/info/laravel/index.html
URI შედგება ორი ნაწილისაგან : URL და URN.
http://vnadiradze.ge
/info/laravel/index.html
'Route' => Illuminate\Support\Facades\Route::class,
ფასადის შემდეგ უნდა მივუთითოთ HTTP მოთხოვნის ტიპი, მოთხოვნის ტიპს კი პირველ პარამეტრად უნდა გადავცეთ შაბლონი, ანუ URI-ს ის ნაწილი, რომლისთვისაც ვქმნით
ამ მარშრუტს. ჯერჯერობით ვართ ამ ეტაპზე :
Route::get('/page')
ანუ მოცემული მარშრუტი ამუშავდება მაშინ თუ მომხმარებელი შევა შემდეგ მისამართზე :
example.com/page
ყველაზე მარტივ შემთხვევაში მოთხოვნის ტიპს მეორე პარამეტრად შეიძლება გადაეცეს ფუნქცია დამმუშავებელი :
Route::get('/page', function(){
});
ამ ფუნქციის ტანში შესაძლებელია ნებისმიერი კოდის ჩაწერა. მაგალითად ეკრანზე გამოვიტანოთ ფრეიმვორკის კონფიგურაციის რომელიმე პარამეტრის მნიშვნელობა.
ვთქვათ app კონფიგურაციული ჯგუფის locale პარამეტრის მნიშვნელობა:
Route::get('/page', function(){
echo config('app.locale');
});
იგივეს გაკეთება შეგვეძლო Config ფასადის get მეთოდით:
Route::get('/page', function(){
echo Config::get('app.locale');
});
<!DOCTYPE html>
<html>
<head>
<title></title>
</head>
<body>
<form action="/comments" method="post">
<input type="text" name="fname" placeholder="სახელი">
<input type="text" name="lname" placeholder="გვარი">
<input type="submit" name="send" value="გაგზავნა">
</form>
</body>
</html>
ახლა დავწეროთ შესაბამისი მარშრუტი:
Route::post('/comments' , function(){
print_r($_POST);
});
თუ ახლა შევალთ შემდეგ მისამართზე :
http://127.0.0.1:8000/comments
ბრაუზერში ვიხილავთ შეტყობინებას :
The GET method is not supported for this route. Supported methods: POST.
ეს იმიტომ, რომ ჩვემ მარშრუტი დავწერეთ post მეთოდისათვის და მივაკითხეთ get მეთოდით. თუ შევალთ ამ მისამართზე :
http://127.0.0.1:8000/form.html
მაშინ ვიხილავთ შესაბამის ფორმას, რომელიც ინფორმაციას post მეთოდით გააგზავნის http://127.0.0.1:8000/comments გვერდზე.
419 | PAGE EXPIRED
ეს მოხდა CSRF (Cross-Site Request Forgery) ვერიფიკაციის გამო, ანუ სისტემამ ჩათვალა, რომ გვერდის მოთხოვნა მოხდა არასწორი
სახით. CSRF ვერიფიკაციის შესახებ უახლოეს ხანებში ვისაუბრებთ, ამჟამად კი ეს პრობლემა ასე მოვაგვაროთ :
app/Http/Middleware/VerifyCsrfToken.php საქაღალდეში ჩავამატოთ შემდეგი გამონაკლისი :
protected $except = [
'/comments'
];
ახლა http://127.0.0.1:8000/form.html ფორმის გაგზავნის შემდეგ გადავალთ http://127.0.0.1:8000/comments გვერდზე სადაც
ვიხილავთ გლობალურ ცვლად $_POST-ში მოქცეულ იმ ინფორმაციას, რომელიც ფორმაში ავკრიფეთ.
Route::match(['get','post'] , '/comments' , function(){
print_r($_POST);
});
ამ შემთხვევაში უკვე შეგვეძლება ფორმის გაგზავნის გარეშე http://127.0.0.1:8000/comments გვერდზე შესვლა.
Route::any('/comments' , function(){
print_r($_POST);
});
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\UserController;
Route::get('/user', [UserController::class, 'index']);
Route::redirect('/here', '/there');
Route::view('/welcome', 'welcome');
ამის შემდეგ http://127.0.0.1:8000/welcome მისამართზე შესვლისას ჩაიტვირთება resources/views/welcome.blade.php ფაილი.
Route::get('/user/{id}', function ($id) {
return 'User '.$id;
});
ანუ პარამეტრები ექცევა ფიგურულ ფრჩხილებში. შესაძლებელია ერთდროულად რამდენიმე პარამეტრის გადაცემაც :
Route::get('/page/{category}/{id}' , function(){
});
თუ ახლა შევალთ შემდეგ მისამართზე :
http://127.0.0.1:8000/page
ვიხილავთ შეტყობინებას, რომ გვერდი ვერ მოიძებნა, მაგრამ თუ შევალთ ამ მისამართზე :
http://127.0.0.1:8000/page/sport/10
ყველაფერი რიგზე იქნება.
გადაცემულ პარამეტრთან წვდომისათვის ეს პარამეტრი არგუმენტად უნდა გადავცეთ მარშრუტში აღწერილი ფუნქცია დამმუშავებელს :
Route::get('/page/{id}' , function($id){
echo $id;
});
როგორც ზემოთ აღვნიშნეთ, შესაძლებელია მარშრუტს მიეთითოს რამდენიმე ცვლადი პარამეტრი, აქ უნდა გავითვალისწინოთ ერთი ფაქტი: პარამეტრები ფუნქცია დამმუშავებელსაც
იმავე თანმიმდევრობით უნდა გადავცეთ რა თანმიმდევრობითაც მარშრუტში აღვწერთ მათ. ფუნქცია ამ პარამეტრებს სწორედ თანმიმდევრობიდან გამომდინარე აღიქვამს და
არა ცვლადთა დასახელებებიდან.
Route::get('/page/{category}/{id}',function($c,$i){
echo "category - " . $c;
echo "id - " . $i;
});
თუ ახლა შევალთ შემდეგ მისამართზე :
http://127.0.0.1:8000/page/cars/10
ბრაუზერში ვიხილავთ შემდეგ ტექსტს :
category - cars
id - 12
Route::get('/user_null/{name?}', function ($name = null) {
return $name; // ცარიელი
});
Route::get('/user_name/{name?}', function ($name = 'ვასო') {
return $name; // ვასო
});
ამ შემთხვევაში უშეცდომოდ შევალთ
http://127.0.0.1:8000/user_null
გვერდზეც და
http://127.0.0.1:8000/user_name
გვერდზეც.
Route::get('/page/{id}',function($id){
echo $id;
})->where('id','[0-9]+');
ანუ სისტემას ვეუბნებით, რომ id პარამეტრი უნდა იყოს ციფრი და იგი შეიძლება მეორდებოდეს მრავალჯერ (ამას აღნიშნავს რეგ. გამოსახულებაში არსებული "+" ნიშანი),
რადგან id შეიძლება იყოს 5-იც და 345343-იც. ასეთ შემთხვევაში შემდეგ მისამართზე შესვლა :
http://127.0.0.1:8000/page/cars
გამოიტანს შეცდომას.
დავუშვათ რამდენიმე პარამეტრთან ერთად ვმუშაობთ და საჭიროა ყველას გაფილტვრა where მეთოდით: პირველი პარამეტრი უნდა შეიცავდეს მხოლოდ ლათინური ანბანის დიდ ან პატარა ასოებს, მეორე პარამეტრი კი მხოლოდ ციფრებს. ასეთ შემთხვევაში მეთოდის გამოყენების სინტაქსი შემდეგნაირია :
Route::get('/page/{cat}/{id}',function($cat,$id){
echo $id;
})->where(['cat'=>'[A-Za-z]+' , 'id'=>'[0-9]+']);
public function boot()
{
//
parent::boot();
}
ჩავამატოთ მასში სასურველი ფილტრი
public function boot()
{
Route::pattern('id', '[0-9]+');
parent::boot();
}
ამ ჩანაწერის ჩამატების შემდეგ აღარ დაგვჭირდება ყოველი მარშრუტის აღწერისას პარამეტრის თავიდან გაფილტვრა. მარშრუტს წავუშალოთ id პარამეტრის გაფილტვრის ნაწილი
Route::get('/page/{id}',function($cat,$id){
echo $id;
});
ყველაფერი იმუშავებს ისევ კორექტულად.
რამდენიმე პარამეტრის ერთდროულად, გლობალურად გაფილტვრისთვის კი boot მეთოდში უნდა ჩავამატოთ შემდეგი ჩანაწერი
Route::patterns(['id'=>'[0-9]+' , 'cat'=>'[A-Za-z]+']);
მეთოდი მიიღებს ასეთ სახეს :
public function boot()
{
Route::patterns(['id'=>'[0-9]+' , 'cat'=>'[A-Za-z]+']);
parent::boot();
}
მარშრუტიდან კი საერთოდ წავშალოთ გაფილტვრის სინტაქსი :
Route::get('/page/{cat}/{id}',function($cat,$id){
echo $id;
});
http://127.0.0.1:8000/application/administrator/index.php
http://127.0.0.1:8000/application/administrator/home.php
http://127.0.0.1:8000/application/administrator/create.php
http://127.0.0.1:8000/application/administrator/edit.php
http://127.0.0.1:8000/application/administrator/delete.php
...
ბუნებრივია თითოეული მათგანის მარშრუტის განსაზღვრისას, მარშრუტის შაბლონში უნდა გავიმეოროთ ეს საერთო ფრაზა, პრეფიქსი
application/administrator, ამის თავიდან ასაცილებლად უნდა გამოვიყენოთ მარშრუტთა ჯგუფი,
მასთან მუშაობა შესაძლებელია Route ფასადის group მეთოდის დახმარებით :
Route::group(['prefix'=>'application/administrator'],function(){
Route::get('/index',function(){
echo '/index';
});
Route::get('/home',function(){
echo '/home';
});
Route::get('/create',function(){
echo '/create';
});
Route::get('/edit',function(){
echo '/edit';
});
Route::get('/delete',function(){
echo '/delete';
});
});
Route::get('/user/profile', function () {
//
})->name('profile');
სახელის დარქმევა შესაძლებელია ასეც :
Route::get('/user/profile', [UserProfileController::class, 'show'])->name('profile');
Route::get('/test',function(){
echo $url = route('profile'); // http://127.0.0.1:8000/user/profile
return redirect()->route('profile'); // გადამისამართება
});
თუ ასხელდებულ მარშრუტს გადაეცემა პარამეტრები, შეგვიძლია ეს პარამეტრები განვსაზღვროთ route დამხმარე ფუნქციის მეორე პარამეტრში :
Route::get('/user/{id}/profile', function ($id) {
//
})->name('profile');
$url = route('profile', ['id' => 1]);
თუ ამ მასივს გადავცემთ დამატებით პარამეტრებსაც, მაშინ გასაღები/მნიშვნელობა წყვილები ავტომატურად ჩაჯდება URL-ში get ტიპის პარამეტრებად :
Route::get('/user/{id}/profile', function ($id) {
//
})->name('profile');
$url = route('profile', ['id' => 1, 'photos' => 'yes']);
// /user/1/profile?photos=yes
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
if ($request->route()->named('profile'))
{
//
}
return $next($request);
}
Route::middleware(['first', 'second'])->group(function () {
Route::get('/', function () {
// გაივლის first & second შუამავლებს...
});
Route::get('/user/profile', function () {
// გაივლის first & second შუამავლებს...
});
});
Route::fallback(function () {
echo "გვერდი ვერ მოიძებნა";
});
use Illuminate\Support\Facades\Route;
$route = Route::current(); // Illuminate\Routing\Route
$name = Route::currentRouteName(); // მარშრუტის დასახელება
$action = Route::currentRouteAction(); // შესაბამისი კონტროლერის ის მეთოდი, რომელიც ამუშავებს მოთხოვნას
php artisan route:list
როდესაც პროექტი დასრულდება და რეალურ გარემოში გაეშვება, სასურველია, რომ მოვახდინოთ მარშრუტთა ქეშირება.
ეს საგრძნობლად შეამცირებს აპლიკაციის შატვირთვისას ყველა საჭირო მარშრუტის რეგისტრაციის დროს. ქეშირება ხდება
Artisan-ის route:cache ბრძანებით :
php artisan route:cache
ქეშირების შემდეგ ყოველი მოთხოვნის გაკეთებისას ჩაიტვირთება ქეშირებულ მარშრუტთა ფაილი, რომელიც შეიქმნებოდა - bootstrap/cache საქაღალდეში.
php artisan route:clear
php artisan make:middleware MiddlewareName
შევქმნათ შუამავალი კლასი სახელად CheckIP
php artisan make:middleware CheckIP
მისი ნახვა შესაძლებელია app/Http/Middleware საქაღალდეში.
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class CheckIP
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
return $next($request);
}
}
კლასის handle მეთოდს (ინგ: Handle - გარჩევა; განხილვა; რეგულირება; კონტროლის განხორციელება; განკარგვა) გადაეცემა ორი პარამეტრი, პირველი ეს არის $request
მოთხოვნა - შუამავალი კლასი მუშაობს მხოლოდ და მხოლოდ მოთხოვნასთან ერთად. შემდეგი პარამეტრი კი არის ფუნქცია $next, რომელიც მართვას გადასცემს შუამავალ
კლასთა ჯაჭვში არსებულ შემდეგ შუამავალ კლასს (თუ ზედა სურათს დავაკვირდებით, შევამჩნევთ, რომ შეიძლება არსებობდეს რამდენიმე შუამავალი ერთდროულად, პასუხი
არ დაბრუნდება მანამ სანამ ყველა მათგანი არ გააკეთებს თავის საქმეს). ყველა შუამავლის გავლის შემდეგ მოთხოვნა უკვე მიემართება აპლიკაციის ბირთვისაკენ შემდგომი დამუშავების
მიზნით.
აღვწეროთ რაიმე მარტივი ფუნქცია შუამავალ კლასში, მაგალითად გადავამოწმოთ ემთხვევა თუ არა მომხმარებლის IP მისამართი კონკრეტულ მნიშვნელობას, თუ ემთხვევა მაშინ გადავამისამართოთ მთავარ გვერდზე. ჩავამატოთ შესაბამისი ლოგიკა შუამავალ კლასში :
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class CheckIP
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
if($request->ip() == 'XXX.XXX.XXX.XXX')
{
return redirect()->route('index');
}
return $next($request);
}
}
ამ შუამავლის მიმაგრება შესაძლებელია როგორც ყველა მარშრუტზე ასევე რომელიმე კონკრეტულ მათგანზე. მივამაგროთ იგი კონკრეტულ მარშრუტს. პირველ რიგში
უნდა გავხსნათ HTTP მოთხოვნების დამუშავების ბირთვი ფაილი - app/Http/Kernel.php, რომელშიც აღწერილია კლასი Kernel, ამ კლასში არის
დახურული თვისება $routeMiddleware, ამ თვისებაში ასოციაციური მასივის სახით აღწერილია ის შუამავლები, რომელთა გამოყენებაც შეგვიძლია მარშრუტებთან მუშაობისას,
მასივის გასაღებები წარმოადგენენ შუამავალი კლასების ფსევდონიმებს რათა მარტივად შეგვეძლოს მათთან მიმართვა. ჩავამატოთ ჩვენი შექმნილი შუამავალი კლასი :
protected $routeMiddleware = [
'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'checkIP' => \App\Http\Middleware\CheckIP::class
];
Route::get('/test', function () {
//
})->middleware('checkIP');
მარშრუტზე რამდენიმე შუალედური კლასის მიმაგრება კი ხდება ასე :
Route::get('/', function () {
//
})->middleware(['first', 'second']);
შუამავალი კლასის მიმაგრება შესაძლებელია მარშრუტის დამმუშავებელ კონტროლერშიც, მეთოდი კონსტრუქტორის მეშვეობით :
public function __construct()
{
$this->middleware('checkIP');
}
როდესაც შუამავლებს ვამაგრებთ მარშრუტთა ჯგუფს, შესაძლებელია დაგვჭირდეს ისე, რომ ეს შუამავალი არ შეეხოს რომელიმე მათგანს ამ ჯგუფიდან.
ამაში დაგვეხმარება withoutMiddleware მეთოდი :
use App\Http\Middleware\CheckIP;
Route::middleware([CheckIP::class])->group(function () {
Route::get('/', function () {
//
});
Route::get('/profile', function () {
//
})->withoutMiddleware([CheckIP::class]);
});
როგორც ვხედავთ routes/web.php ფაილში კლასის სრული დასახელების გამოყენებითაცაა შესაძლებელი შუამავლების მიმაგრება მარშრუტებზე.
Laravel-ის ინსტალაციის შემდეგ ავტომატურად გენერირდება მარშრუტთა web და api ჯგუფები, რომლებიც მოიცავენ web და api მარშრუტებთან ყველაზე ხშირად გამოყენებად შუამავლებს.
/**
* The application's route middleware groups.
*
* @var array
*/
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session\Middleware\StartSession::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
'throttle:api',
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
];
აღსანიშნავია, რომ ეს ჯგუფები ავტომატურად ემაგრება ჩვენს აპლიკაციას App\Providers\RouteServiceProvider სერვის-პროვაიდერის მიერ :
public function boot()
{
$this->configureRateLimiting();
$this->routes(function () {
Route::prefix('api')
->middleware('api')
->namespace($this->namespace)
->group(base_path('routes/api.php'));
Route::middleware('web')
->namespace($this->namespace)
->group(base_path('routes/web.php'));
});
}
კონტროლერები ინახება app/Http/Controllers საქაღალდეში. ნაგულისხმეობის პრინციპით ამ საქაღალდეში უკვე შექმნილია ერთი საბაზისო კონტროლერი Controller.php სწორედ მისი მემკვიდრეები უნდა იყვნენ ის კონტროლერები, რომლებსაც მომავალში შევქმნით.
class UserController extends Controller
{
}
ახლა განსაზღვროთ კონტროლერის namespace ანუ სახელსივრცე :
namespace App\Http\Controllers
class UserController extends Controller
{
}
თუ ვქმნით კლასს, რომელიც არის სხვა კლასის მემკვიდრე, მაშინ მშობელ კლასთანაც უნდა გვქონდეს წვდომა :
namespace App\Http\Controllers
use App\Http\Controllers\Controller;
class UserController extends Controller
{
}
ახლა შევქმნათ მარშრუტი, რომელსაც დაამუშავებს შექმნილი კონტროლერი, routes/web.php :
use App\Http\Controllers\UserController;
Route::get('/user/{id}', [UserController::class, 'show']);
შევქმნათ კონტროლერის შესაბამისი მეთოდი show :
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
class UserController extends Controller
{
public function show($id)
{
echo $id;
}
}
შევიდეთ http://127.0.0.1:8000/user/1 მისამართზე.
php artisan make:controller ControllerName
შევქმნათ კონტროლერი TestController.php
php artisan make:controller TestController
თუ შევალთ კონტროლერების საქაღალდეში, დაგვხვდება ახალი კონტროლერი.
Route::get('profile', [UserController::class, 'show'])->middleware('auth');
ასევე შესაძლებელია შუამავლის განსაზღვრა კონტროლერის მეთოდ კონსტრუქტორშიც :
class UserController extends Controller
{
/**
* Instantiate a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('auth');
$this->middleware('log')->only('index');
$this->middleware('subscribed')->except('store');
}
}
php artisan make:controller PhotoController --resource
ამ ბრძანების შედეგად შეიქმნება app/Http/Controllers/PhotoController.php კონტროლერი, რომელშიც უკვე აღწერილი იქნება ყველა ზემოთ
ნახსენები საჭირო მეთოდი.
ახლა აღვწეროთ რესურსის ტიპის მარშრუტი Route ფასადის resource მეთოდის დახმარებით :
Route::resource('photos', PhotoController::class);
ეს ერთადერთი ჩანაწერი ახდენს ოთხივე ოპერაციისათვის (CRUD) საჭირო ყველა მარშრუტის დეკლარირებას. საერთო ჯამში კი მიიღება ამდაგვარი სურათი :
მოთხოვნის ტიპი | URI | კონტროლერის ფუნქცია (მეთოდი) | მარშრუტის დასახელება |
---|---|---|---|
GET | /photos | index | photos.index |
GET | /photos/create | create | photos.create |
POST | /photos | store | photos.store |
GET | /photos/{photo} | show | photos.show |
GET | /photos/{photo}/edit | edit | photos.edit |
PUT/PATCH | /photos/{photo} | update | photos.update |
DELETE | /photos/{photo} | destroy | photos.destroy |
ინგ: Injection - ინექცია; დანერგვა; შემოღება; ჩადება;
თუ კონკრეტული კლასის მუშაობისათვის საჭიროა, გამოყენებულ იქნას სხვა კლასის ფუნქციონალი, ეს იმას ნიშნავს, რომ პირველი კლასი დამოკიდებულია მეორე კლასზე. ბუნებრივია უნდა მოვახდინოთ დამხმარე კლასის საწყის კლასში ინტეგრირება (Injection) და მხოლოდ ამის შემდეგ შეგვეძლება დამხმარე კლასის ფუნქციონალის გამოყენება. სწორედ ამ დამხმარე კლასს ეწოდება დამოკიდებულება (Dependency).
...
use Illuminate\Http\Request;
...
class SomeController extends Controller
{
...
public function post(Request $request)
{
$request->validate([
// ...
]);
// ...
}
...
}
namespace App\Http\Controllers;
use App\Repositories\UserRepository;
class UserController extends Controller
{
protected $users;
public function __construct(UserRepository $users)
{
$this->users = $users;
}
}
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class UserController extends Controller
{
public function store(Request $request)
{
$name = $request->name;
//
}
}
თუ მოხდა ისე, რომ კონტროლერს გადმოეცემა მარშრუტის პარამეტრიც,
მაშინ ეს პარამეტრი უნდა აღვწეროთ ინექციის შემდეგ.
მაგალითად თუ გვაქვს ასეთი მარშრუტი :
use App\Http\Controllers\UserController;
Route::put('/user/{id}', [UserController::class, 'update']);
კონტროლერის მეთოდს ეს პარამეტრი ინექციასთან ერთად გადაეცემა ამდაგვარად :
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class UserController extends Controller
{
public function update(Request $request, $id)
{
//
}
}
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\PostsController;
Route::get('/posts', [PostsController::class, 'index']); // პოსტების ჩამონათვალი
Route::get('/posts/{post}', [PostsController::class, 'show']); // კონკრეტული პოსტი
Route::post('/posts', [PostsController::class, 'store']); // ახალი პოსტის დამატება
ლარაველის მე-9-ე ვერსიაში შესაძლებელია მარშრუტთა დაჯგუფება კონტროლერის
მიხედვით, ამისათვის გამოიყენება Route ფასადის controller მეთოდი group
მეთოდთან ერთად :
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\PostsController;
Route::controller(PostsController::class)->group(function(){
Route::get('/posts', 'index'); // პოსტების ჩამონათვალი
Route::get('/posts/{post}', 'show'); // კონკრეტული პოსტი
Route::post('/posts', 'store'); // ახალი პოსტის დამატება
});
როგორც ვხედავთ, მარშრუტებისათვის, სათითაოდ ცალ-ცალკე კონტროლერის განსაზღვრა აღარ გვჭირდება და უბრალოდ კონტროლერის
მეთოდების დასახელებებს ვუთითებთ.
იმისათვის რათა მივიღოთ მიმდინარე HTTP მოთხოვნის ობიექტი, Illuminate\Http\Request კლასი, დამოკიდებულებათა ინექციის საშუალებით უნდა ჩავსვათ საჭირო კონტროლერში ან მარშრუტის ფუნქცია-დამმუშავებელში:
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class UserController extends Controller
{
public function store(Request $request)
{
$name = $request->input('name');
//
}
}
მარშრუტის შემთხვევაში :
use Illuminate\Http\Request;
Route::get('/', function (Request $request) {
//
});
$uri = $request->path(); // foo/bar
if ($request->is('foo/*'))
{
//
}
if ($request->routeIs('index'))
{
//
}
// http://127.0.0.1:8000/foo/bar?want_drink=true
$request->url() // http://127.0.0.1:8000/foo/bar
$request->fullUrl() // http://127.0.0.1:8000/foo/bar?want_drink=true
თუ გვინდა, რომ მიმდინარე მისამართს მივაწეროთ დამატებითი GET პარამერები, მაშინ უნდა გამოვიყენოთ fullUrlWithQuery მეთოდი :
// http://127.0.0.1:8000/foo/bar?want_drink=true
$request->url() // http://127.0.0.1:8000/foo/bar
$request->fullUrl() // http://127.0.0.1:8000/foo/bar?want_drink=true
$request->fullUrlWithQuery(['drink' => 'vodka'])) // http://127.0.0.1:8000/foo/bar?want_drink=true&drink=vodka
$method = $request->method();
if ($request->isMethod('get'))
{
//
}
$ipAddress = $request->ip();
<input type="text" name="name">
<input type="password" name="password">
public function login(Request $request)
{
print_r($request->all());
}
შედეგად ვიხილავთ დაახლოებით შემდეგი სახის მასივს :
Array
(
[name] => vaso
[password] => pass123
)
// http://127.0.0.1:8000/foo/bar?want_drink=true
$want = $request->input('want_drink')); // true
$drink = $request->input('drink', 'vodka')); // vodka
უნდა აღინიშნოს, რომ input() მეთოდი მუშაობს ნებისმიერი ტიპის HTTP მოთხოვნებთან.
$name = $request->query('name', 'Helen');
თუ მეთოდს საერთოდ არ გადავცემთ პარამეტრებს, მაშინ იგი დაგვიბრუნებს GET ტიპის ყველა პარამეტრს.
$archived = $request->boolean('archived');
$name = $request->name;
დინამიური მეთოდის გამოყენებისას ფრეიმვორკი პირველ რიგში გადაამოწმებს მოთხოვნაში ჩადებულ ველებს, თუ აქ ვერ მოიძებნა შესაბამისი ველი, მაშინ გადაამოწმებს
მარშრუტის პარამეტრებს.
$input = $request->only(['username', 'password']);
$input = $request->only('username', 'password');
მეთოდს პარამეტრად უნდა გადაეცეს შესაბამისი ველის დასახელება.
$input = $request->except(['credit_card']);
$input = $request->except('credit_card');
მეთოდს პარამეტრად უნდა გადაეცეს შესაბამისი ველის დასახელება (name ატრიბუტის მნიშვნელობა)
if ($request->has('name'))
{
//
}
if ($request->filled('name'))
{
//
}
$request->flash();
შენახული ინფორმაცია შეიძლება გამოიყურებოდეს ასე :
Array
(
[_token] => xtLofMZlLR1b4oEZeT9YPr8SZiOzqfLrGof9huOj
[_previous] => Array
(
[url] => http://127.0.0.1:8000/contact
)
[_flash] => Array
(
[old] => Array
(
)
[new] => Array
(
[0] => _old_input
)
)
[_old_input] => Array
(
[name] => vaso
[password] => pass123
)
)
_token არის საიტის უსაფრთხოების გასაღები და მის შესახებ მოგვიანებით ვისაუბრებთ. რაც შეეხება _old_input უჯრას - იგი შეიცავს ბოლო
მოთხოვნაში შესული ინფორმაციის შემცველ მასივს. ამ ინფორმაციასთან წვდომისათვის გამოიყენება სპეციალური ფუნქცია old(), ფორმის
წარმოდგენის ფაილი გადავაკეთოთ ასე :
<input type="name" name="name" value="{{ old('name') }}">
<input type="password" name="password" value="{{ old('password') }}">
ამის შემდეგ თუ ფორმას გავაგზავნით ვნახავთ, რომ აკრეფილი ინფორმაცია არ დაიკარგება და ველები ავტომატურად შეივსება.
$request->flashOnly(['username', 'email']);
$request->flashExcept('password');
კონკრეტულ გვერდზე რაიმე ინფორმაციის გამოტანის, ანუ ერთგვარი პასუხის დაბრუნების უმარტივესი გზა არის კონტროლერში ან მარშრუტის დამმუშავებელში ტექსტის დაბრუნება :
public function index()
{
return 'Hello World';
}
Route::get('/', function () {
return 'Hello World';
});
ასევე შესაძლებელია მასივის დაბრუნებაც, ფრეიმვორკი ავტომატურად გადააფორმატებს მას JSON ფორმატში :
Route::get('/', function () {
return [1, 2, 3];
});
public function __construct($content = '', $status = 200, array $headers = [])
{
$this->headers = new ResponseHeaderBag($headers);
$this->setContent($content);
$this->setStatusCode($status);
$this->setProtocolVersion('1.0');
}
ანუ შეგვიძლია განვსაზღვროთ პასუხის აღწერილობა, HTTP სტატუსი და სათაურები :
Route::get('/home', function () {
return response('Hello World', 200)
->header('Content-Type', 'text/plain');
});
return response('Hello World')->cookie('name', 'value', $minutes);
return response('Hello World')->withoutCookie('name');
/**
* იმ cookie-ბის დასახელებები, რომელთა შიფრაციაც არ მოხდება
*
* @var array
*/
protected $except = [
'cookie_name',
];
Route::get('/dashboard', function () {
return redirect('home/dashboard');
});
ზოგჯერ საჭიროა, რომ მომხმარებელი გადავამისამართოთ წინა გვერდზე (მაგალითად გაგზავნა ფორმა არავალიდური ინფორმაციებით),
ანუ დავაბრუნოთ უკან, ამისათვის გამოიყენება დამხმარე ფუნქცია back. ფუნქცია იყენებს სესიებს, ამიტომ დარწმუნებულები უნდა ვიყოთ,
რომ მარშრუტი, რომლის დამმუშავებელშიც back ფუნქციას ვიძახებთ, მოქცეულია web შუამავალში (როგორც ვიცით სწორედ ეს შუამავალი ახდენს,
სესიების მიმაგრებას მარშრუტებთან) :
Route::post('/user/profile', function () {
// მოთხოვნის ვალიდაცია...
return back()->withInput();
});
return redirect()->route('login');
თუ მარშრუტს აქვს პარამეტრები, მათი გადაცემა შეგვიძლია მეთოდის მეორე არგუმენტად :
// მარშრუტი შემდეგი URI-სათვის : /profile/{id}
return redirect()->route('profile', ['id' => 1]);
return redirect()->away('https://www.google.com');
Route::post('/user/profile', function () {
// ...
return redirect('dashboard')->with('status', 'ინფორმაცია წარმატებით განახლდა !');
});
გადამისამართების შემდეგ, წარმოდგენის ფაილში ამ ინფორმაციის გამოყენების სინტაქსი იქნება ასეთი :
@if (session('status'))
<div class="alert alert-success">
{{ session('status') }}
</div>
@endif
return response()->json([
'name' => 'Abigail',
'state' => 'CA',
]);
return response()->download($pathToFile);
<!-- resources/views/greeting.blade.php -->
<html>
<body>
<h1>{{ $name }} გამარჯობა ! </h1>
</body>
</html>
წარმოდგენის დაბრუნება შესაძლებელია view დამხმარე ფუნქციის მეშვეობით :
Route::get('/', function () {
return view('greeting', ['name' => 'შამილი']);
});
წარმოდგენის დაბრუნება შესძლებელია View ფასადის მეშვეობითაც :
use Illuminate\Support\Facades\View;
return View::make('greeting', ['name' => 'შამილი']);
დავუშვათ resources/views საქაღალდეში გავაკეთეთ ახალი საქაღალდე templates და წარმოდგენის ფაილები გადავიტანეთ მასში,
მაშინ წარმოდგენის მოთხოვნის სინტაქსი იქნება შემდეგნაირი :
return view('templates.greeting', ['name' => 'შამილი']);
resources/views საქაღალდეში შევქმნათ ახალი საქაღალდე templates და მასში გავაკეთოთ წარმოდგენის ახალი ფაილი template.php. შესაბამისად გადავაკეთოთ view ფუნქციაც :
return view('templates.template')
ამჟამად გვაქვს სტატიკური გვერდი სადაც ლოგიკის არავითარი ელემენტი არ არის და არც ცვლადებია გამოყენებული. გადავცეთ მას რაიმე ცვლადი :
<h1><?php echo $title; ?></h1>
ეს მოგვცემს შეცდომას რადგან $title ცვლადი განსაზღვრული არ არის. ცვლადი უნდა აღვწეროთ კონტროლერში, ან მარშრუტის დამმუშავებელში :
return view('templates.template',['title'=>'Hello World !']);
დავუშვათ გვინდა რამდენიმე ცვლადის, ანუ რამდენიმე ინფორმაციის ერთდროულად გამოყენება, ასეთ შემთხვევაში ხელსაყრელია დავიხმაროთ მასივი :
$data = array('title'=>'Hello World !' , 'title1'=>'Hello World 1');
return view('templates.template',$data);
return view('templates.template')->with('title','Hello World 2 !');
with მეთოდის გამოყენებისას რამდენიმე ცვლადის მიმაგრება ხდება ასე :
public function index()
{
$view = view('templates.template');
$view->with('title','Hello World !');
$view->with('title1','Hello World 1');
$view->with('title2','Hello World 2');
return $view;
}
არსებობს with მეთოდის გამოყენების კიდევ ერთი ვარიანტი :
return view('templates.template')->withTitle('Hello World');
ანუ with მეთოდის დასახელება პრეფიქსად ერთვის ცვლადის დიდი ასოთი დაწყებულ სახელს, შემდეგ კი ფრხილებში ეთითება ცვლადის
მნიშვნელობა.
$firstname = "ვასო";
$lastname = "ნადირაძე";
$age = "30";
$result = compact("firstname", "lastname", "age");
print_r($result); // Array ( [firstname] => ვასო [lastname] => ნადირაძე [age] => 30 )
რაც შეეხება laravel-ში ამ მეთოდის გამოყენების სინტაქსს, იგი შემდეგნაირია :
public function index()
{
$firstname = "ვასო";
$lastname = "ნადირაძე";
$age = "30";
return view('templates.template', compact('firstname', 'lastname', 'age'));
}
ამ ინფორმაციების, წარმოდგენის ფაილში გამოყენების ხერხებს განვიხილავთ შემდეგ თავში.
წარმოდგენის ფაილების გასაფორმებლად უნდა გამოვიყენოთ Laravel ფრეიმვორკის ფუნქციები. მაგალითად ნავიგაციური მენიუს HTML კოდი ხშირად არის შემდეგნაირი :
<ul>
<li><a href="#">Home</a></li>
<li><a href="#">Articles</a></li>
<li><a href="#">Article</a></li>
<li><a href="#">About</a></li>
</ul>
თითოეული ბმული დაკავშირებულია კონკრეტულ გვერდთან. გვერდის უკან კი მოიაზრება კონკრეტული მარშრუტი, ამიტომ ბმულების ფორმირებისას
ისინი უნდა დავაკავშიროთ ამ მარშრუტებთან. ამის გაკეთება კი, როგორც ვიცით, შესაძლებელია route ფუნქციის მეშვეობით :
<ul class="nav navbar-nav">
<li><a href="<?php echo route('home'); ?>">Home</a></li>
<li><a href="<?php echo route('articles'); ?>">Articles</a></li>
<li><a href="<?php echo route('article',array('id'=>10)); ?>">Article</a></li>
<li><a href="<?php echo route('about'); ?>">About</a></li>
</ul>
public function index()
{
if (view()->exists('templates.template'))
{
return view('templates.template')->withTitle('Hello World');
}
}
namespace App\Providers;
use Illuminate\Support\Facades\View;
class AppServiceProvider extends ServiceProvider
{
public function register()
{
//
}
public function boot()
{
View::share('key', 'value');
}
}
შესაძლებელია, რომ ამ პროცესმა უარყოფითი გავლენა იქონიოს აპლიკაციის სისწრაფეზე, ასე რომ view:cache Arttisan ბრძანებით ეგვიძლია დავქეშოთ წარმოდგენის ფაილები :
php artisan view:cache
ქეშირებული შაბლონები შეინახება storage/framework/views საქაღალდეში. ქეშის გასუფთავება კი მოხდება ასე :
php artisan view:clear
კომპილირებული შაბლონები ინახება storage/framework/views საქაღალდეში.
იმისათვის რათა წარმოდგენის ფაილი blade შაბლონიზატორმა დაამუშავოს, ფაილის გაფართოება უნდა იყოს blade.php. გადავარქვათ ჩვენს მიერ შექმნილ ფაილს სახელი:
template.blade.php
ამ ფაილს ახლა უკვე ამუშავებს შაბლონიზატორი Blade.
Route::get('/', function () {
return view('templates.template', ['name' => 'შამილი']);
});
წარმოდგენის ფაილი :
{{ $name }} გამარჯობა !
არ არის აუცილებელი მაინცდამაინც ცვლადის სახით მიმაგრებული ინფორმაცია გამოვიტანოთ ამ გზით, შესაძლებელია PHP-ს ნებისმიერი
ფუნქციის შედეგის გამოტანაც :
მიმდინარე UNIX დროითი ნიშნული არის {{ time() }}
<script>
var app = <?php echo json_encode($array); ?>;
</script>
იგივეს გაკეთება უფრო მარტივადაა შესაძლებელი შაბლონიზატორის @json დირექტივის გამოყენებით :
<script>
var app = @json($array);
</script>
public function index()
{
$script = "<script>alert('Hello')</script>";
return view('templates.template')->with('script', $script);
}
წარმოდგენის ფაილში შევიტანოთ ამდაგვარი ჩანაწერი :
{{ $script }}
ასევე შევქმნათ შესაბამისი მარშრუტი, რომელსაც ეს მეთოდი დაამუშავებს და შევიდეთ შესაბამის ბმულზე (ეს ყველაფერი უკვე ვიცით და ამიტომ
კოდის ნიმუშს აღარ დავწერ). შედეგად ვიხილავთ :
<script>alert('Hello')</script>
ნაგულისხმეობის პრინციპით შაბლონიზატორის {{ }} ჩანაწერი ავტომატურად იყენებს PHP-ს htmlspecialchars ფუნქციას,
XSS შეტევებისაგან თავის დასაცავად. ყველა HTML სიმბოლოს ჩანაცვლება ხდება შესაბამისი ნიშნულებით და HTML კოდი გარდაიქმნება სტრიქონად.
თუ წარმოდგენის ფაილში ამდაგვარ ჩანაწერს შევიტანთ :
{!! $script !!}
ვნახავთ, რომ Javascript-ის alert ფუნქცია მართლაც იმუშავებს.
@if (count($records) === 1)
ერთი ჩანაწერი
@elseif (count($records) > 1)
რამდენიმე ჩანაწერი
@else
არცერთი ჩანაწერი
@endif
მეტი კომფორტულობისათვის არსებობს @unless, @isset და @empty დირექტივებიც :
@unless (Auth::check())
თქვენ არ ხართ სისტემაში შესული
@endunless
@isset($records)
// $records განსაზღვრულია და არ არს null...
@endisset
@empty($records)
// $records ცარიელია
@endempty
@auth
// აუტენტიფიცირებულია
@endauth
@guest
// არააუტენტიფიცირებულია
@endguest
@switch($i)
@case(1)
First case...
@break
@case(2)
Second case...
@break
@default
Default case...
@endswitch
@for ($i = 0; $i < 10; $i++)
მიმდინარე მნიშვნელობა არის {{ $i }}
@endfor
@foreach ($users as $user)
<p>მომხმარებლის ID : {{ $user->id }}</p>
@endforeach
@forelse ($users as $user)
<li>{{ $user->name }}</li>
@empty
<p>მომხმარებლები ვერ მოიძებნა</p>
@endforelse
@while (true)
<p>ჩაციკლვა :))</p>
@endwhile
ციკლებთან მუშაობისას შეიძლება დაგვჭირდეს კონკრეტული იტერაციების გამოტოვება. ასეთ შემთხვევაში დაგვეხმარება @continue and @break
დირექტივები :
@foreach ($users as $user)
@if ($user->type == 1)
@continue
@endif
<li>{{ $user->name }}</li>
@if ($user->number == 5)
@break
@endif
@endforeach
იგივეს გაკეთება შესაძლებელია ასეც :
@foreach ($users as $user)
@continue($user->type == 1)
<li>{{ $user->name }}</li>
@break($user->number == 5)
@endforeach
@foreach ($users as $user)
@if ($loop->first)
პირველი იტერაცია
@endif
@if ($loop->last)
ბოლო იტერაცია
@endif
<p>მომხმარებლის ID : {{ $user->id }}</p>
@endforeach
თუ ვიმყოფებით ჩადგმულ ციკლში, $loop ცვლადის parent თვისების მეშვეობით შეგვიძვლია მივწვდეთ მშობელ ციკლს :
@foreach ($users as $user)
@foreach ($user->posts as $post)
@if ($loop->parent->first)
მშობელი ციკლის პირველი იტერაცია
@endif
@endforeach
@endforeach
$loop ცვლადის თვისებები :
თვიდება | აღწერა |
---|---|
$loop->index | მიმდინარე იტერაციის ინდექსი (იწყება 0-დან). |
$loop->iteration | მიმდინარე იტერაცია (იწყება 1-დან) |
$loop->remaining | დარჩენილი იტერაციების რაოდენობა |
$loop->count | ელემენტების რაოდენობა მასივის გავლისას ციკლში |
$loop->first | ვიყოფებით თუ არა ციკლის პირველ იტერაციაზე |
$loop->last | ვიყოფებით თუ არა ციკლის ბოლო იტერაციაზე |
$loop->even | არის თუ არა ლუწი მიმდინარე იტერაცია |
$loop->odd | არის თუ არა კენტი მიმდინარე იტერაცია |
$loop->parent | მშობელ ციკლთან წვდომა ჩადგმული ციკლიდან |
{{-- ეს კომენტარი არ შევა დაგენერირებულ HTML-ში --}}
<div>
@include('shared.errors')
<form>
</form>
</div>
გარდა იმისა, რომ მშობელ ფაილზე მიმაგრებული ინფორმაცია, მემკვიდრეობით ავტომატურად გადაეცემა შვილობილ ფაილსაც, შეგვიძლია დამატებითი ინფორმაციაც მივამაგროთ
ამ უკანასკნელს :
@include('view.name', ['status' => 'complete'])
როდესაც @include დირექტივის მეშვეობით ფაილის გამოძახებას ვაკეთებთ, Laravel-დააბრუნებს შეცდომას თუ მითითებული ფაილი ვერ მოიძებნება.
ამის თავიდან ასაცილებლად შეგვიძლია გამოვიყენოთ @includeIf დირექტივა :
@includeIf('view.name', ['status' => 'complete'])
თუ გვსურს წარმოდგენის ფაილი გამოვიძახოთ იმისდამიხედვით თუ რა მნიშვბნელობა აქვს მინიჭებული კონკრეტულ ლოგიკურ ოპერატორს. მაშინ უნდა გამოვიყენოთ
@includeWhen and @includeUnless დირექტივები :
@includeWhen($boolean, 'view.name', ['status' => 'complete'])
@includeUnless($boolean, 'view.name', ['status' => 'complete'])
@php
$counter = 1;
@endphp
განვიხილოთ მშობელი შაბლონის მარტივი მაგალითი :
<!-- resources/views/layouts/app.blade.php -->
<html>
<head>
<title>App Name - @yield('title')</title>
</head>
<body>
@section('sidebar')
მშობელი ბლეიდის გვერდითი არე
@show
<div class="container">
@yield('content')
</div>
</body>
</html>
მივაქციოთ ყურადღება @section და @yield დირექტივებს, პირველი მათგანის მეშვეობით ხდება შიგთავსისის კონკრეტული
ფრაგმენტის ანუ სექციების შექმნა, მეორე მათგანი კი უზრუნველჰყოფს ამ სექციების საჭირო ადგილებში გამოტანას.
ახლა შევქმნათ ამ მშობელი შაბლონის მემკვიდრე შაბლონი. ამისათვის გამოიყენება @extends დირექტივა:
<!-- resources/views/child.blade.php -->
@extends('layouts.app')
@section('title', 'გვერდის სათაური')
@section('sidebar')
@parent
<p>ეს არე დაემატება მშობელი შაბლონის გვერდით არეს</p>
@endsection
@section('content')
<p>შვილობილი შაბლონის შიგთავსი</p>
@endsection
<form method="POST" action="/profile">
@csrf
...
</form>
<!-- resources/views/layouts/app.blade.php -->
<html>
<head>
<title>App Name - @yield('title')</title>
</head>
<body>
@section('sidebar')
მშობელი ბლეიდის გვერდითი არე
@show
<div class="container">
@yield('content')
</div>
@stack('scripts')
</body>
</html>
შვილობილი შაბლონი :
<!-- resources/views/child.blade.php -->
@extends('layouts.app')
@section('title', 'გვერდის სათაური')
@section('sidebar')
@parent
<p>ეს არე დაემატება მშობელი შაბლონის გვერდით არეს</p>
@endsection
@section('content')
<p>შვილობილი შაბლონის შიგთავსი</p>
@endsection
@push('scripts')
<script src="/example.js"></script>
@endpush
ასევე შესაძლებელია დასტების თანმიმდევრობის განსაზღვრა :
@push('scripts')
ეს იქნება მეორე დასტა...
@endpush
// Later...
@prepend('scripts')
ეს იქნება პირველი დასტა...
@endprepend
$post = App\Models\Post::find(1);
echo url("/posts/{$post->id}");
// http://127.0.0.1:8000/posts/1
// მიმდინარე URL GET პარამეტრების (query string) გარეშე
echo url()->current();
// მიმდინარე URL GET პარამეტრებთან (query string) ერთად
echo url()->full();
// წინა მოთხოვნის სრული URL (GET პარამეტრებთან (query string) ერთად )
echo url()->previous();
ნებისმიერ ამ მეთოდთან წვდომა შესაძლებელია URL ფასადის მეშვეობითაც :
use Illuminate\Support\Facades\URL;
echo URL::current();
Route::get('/post/{post}', function () {
//
})->name('post.show');
route ფუნქციით ამ მარშრუტის შესატყვისი ბმული დაგენერირდება ასე :
echo route('post.show', ['post' => 1]);
// http://127.0.0.1:8000/post/1
რა თქმა უნდა შესაძლებელია, რომ route ფუნქციას გადავცეთ რამდენიმე პარამეტრი ერთდროულადაც :
Route::get('/post/{post}/comment/{comment}', function () {
//
})->name('comment.show');
echo route('comment.show', ['post' => 1, 'comment' => 3]);
// http://127.0.0.1:8000/post/1/comment/3
ნებისმიერი დამატებითი პარამეტრი, რომელიც აღწერილი არ იქნება მარშრუტის განსაზღვრისას, ბმულს დაემატება GET პარამეტრის
სახით :
echo route('post.show', ['post' => 1, 'search' => 'rocket']);
// http://127.0.0.1:8000/post/1?search=rocket
use App\Http\Controllers\HomeController;
$url = action([HomeController::class, 'index']);
თუ კონტროლერის მეთოდს გადაეცემა მარშრუტის პარამეტრები, შეგვიძლია ისინი აღვწეროთ ასოციაციურ მასივში და ეს მასივი
action ფუნქციას გადავცეთ მეორე პარამეტრად :
$url = action([UserController::class, 'profile'], ['id' => 1]);
'driver' => env('SESSION_DRIVER', 'file'),
ეს არის სესიათა დამუშავების მექანიზმი ნაგულისხმეობის პრინციპით. როგორც ვხედავთ ამ მექანიზმის მნიშვნელობად მითითებულია file, ეს ნიშნავს, რომ
სესიები ინახება კონკრეტულ ფაილებში, კომენტარებში აღწერილია სხვა შესაძლო მნიშვნელობებიც ("cookie", "database", "apc", "memcached", "redis", "array",
memcached არის ერთგვარი პროგრამული უზრუნველყოფა, რომლის მეშვეობითაც ხდება ინფორმაციის ჰეშირებული სახით შენახვა ოპერატიულ მეხსიერებაში).
'lifetime' => env('SESSION_LIFETIME', 120),
ეს არის წუთების რაოდენობა, რომლის ამოწურვის შემდეგაც სესიები გაუქმდდება თუ მომხმარებელი უმოქმედოდ იქნება აპლიკაციაში მთელი ამ ხნის განმავლობაში.
'expire_on_close' => false,
გაუქმდეს თუ არა სესიები ბრაუზერის დახურვისას.
'encrypt' => false,
დაიშიფროს თუ არა სესიაში შენახული ინფორმაცია.
'files' => storage_path('framework/sessions'),
სესიის ინფორმაციები ინახება ამ მისამართზე განთავსებულ ფაილებში.
'table' => 'sessions',
აქ უნდა განისაზღვროს მონაცემთა ბაზის ცხრილის დასახელება იმ შემთხვევაში, თუ driver პარამეტრის მნიშვნელობად ავირჩევთ database-ს.
ანუ სესიის ინფორმაციები შეინახება მბ-ში და კერძოდ აქ მითითებულ ცხრილში.
php artisan session:table
ეს ბრძანება შექმნის მიგრაციას ახალ ფაილს :
database/migrations/2021_05_28_100647_create_sessions_table
ცხრილის სტრუქტურა იქნება ამდაგვარი :
Schema::create('sessions', function (Blueprint $table) {
$table->string('id')->primary();
$table->foreignId('user_id')->nullable()->index();
$table->string('ip_address', 45)->nullable();
$table->text('user_agent')->nullable();
$table->text('payload');
$table->integer('last_activity')->index();
});
გავუშვათ მიგრაციის შესრულკების ბრძანება :
php artisan migrate
ახლა გადავაკეთოთ driver პარამეტრის აღწერაც :
'driver' => env('SESSION_DRIVER', 'database'),
ეს ყველაფერი კიდევ არ ნიშნავს, რომ სესიები მბ-ში შეინახება, დავაკვირდეთ ჩანაწერს: როგორც ვხედავთ env ფუნქციას გადაცემული აქვს ორი პარამეტრი.
SESSION_DRIVER პარამეტრი აღნიშნავს, რომ სესიები უნდა შეინახოს .env ფაილში SESSION_DRIVER პარამეტრის მნიშვნელობად მითითებული
მექანიზმის მიხედვით, ამ ფაილში ამ მომენტისათვის კი სავარაუდოდ ეს მდგომარეობაა :
...
SESSION_DRIVER=file
...
ეს იმას ნიშნავს, რომ სესიები ფაილებში ინახება, მიუხედავად იმისა, რომ driver პარამეტრს მეორე არგუმენტად გადაცემული აქვს database,
ამიტომ ჩავასწოროთ .env ფაილიც :
...
SESSION_DRIVER=database
...
public function show(Request $request)
{
$result = $request->session()->get('key','არ არსებობს');
dump($result);
}
სესიაში უჯრა სახელად key არ არსებობს, ამიტომ ბრაუზერში დაგვიბრუნდება "არ არსებობს".
public function show(Request $request)
{
$result = $request->session()->all();
dump($result);
}
public function show(Request $request)
{
$request->session()->put('key','value');
$result = $request->session()->all();
dump($result);
}
ამ კოდის შედეგი იქნება დაახლოებით ამდაგვარი რამ :
public function show(Request $request)
{
if ($request->session()->has('key'))
{
dump("1");
}
else
{
dump("0");
}
}
$request->session()->flash('status', 'შეტყობინება წარმატებით გაიგზავნა !');
ამ ინფორმაციის ნახვა კი ასე შეგვიძლია წარმოდგენის ფაილში :
@if(Session::has('status'))
<p>{{ Session::get('status') }}<p>
@endif
$request->session()->increment('count');
$request->session()->increment('count', $incrementBy = 2);
$request->session()->decrement('count');
$request->session()->decrement('count', $decrementBy = 2);
{{ dump(Session::all()) }}
ამ ფასადის გამოყენება, რა თქმა უნდა შესაძლებელია კონტროლერშიც.
სესიის კონკრეტული უჯრის წასაშლელად გამოიყენება forget მეთოდი, რომელსაც პარამეტრად უნდა გადავცეთ შესაბამისისი უჯრის დასახელება :
Session::forget('key2');
სესიის მთლიანად წასაშლელად გამოიყენება flush მეთოდი, რომელსაც პარამეტრის გადაცემა არ სჭირდება.
Session::flush();
dump(session('key'));
თუ გვსურს, რომ session ფუნქციის მეშვეობით სესიაში შევიტანოთ ახალი მნიშვნელობები :
session(['key' => 'value']);
პირველ რიგში routes/web.php ფაილში შევქმნათ მარშრუტები :
use App\Http\Controllers\PostController;
Route::get('/post/create', [PostController::class, 'create']);
Route::post('/post', [PostController::class, 'store']);
GET მარშრუტი გამოიტანს სიახლის დასამატებელ ფორმას, POST მარშრუტი კი ამ ფორმაში აკრეფილ ინფორმაციას შეინახავს მონაცემთა ბაზაში.
ახლა შევქმნათ კონტროლერი, ამ ეტაპზე მისი store მეთოდი დავტოვოთ ცარიელი:
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class PostController extends Controller
{
/**
* სიახლის დასამატებელი ფორმის გამოტანა
*
* @return \Illuminate\View\View
*/
public function create()
{
return view('post.create');
}
/**
* სიახლის შენახვა მბ-ში
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function store(Request $request)
{
// ინფორმაციის ვალიდაცია და შენახვა
}
}
ეს კონტროლერი, ისევე როგორც, ყველა სხვა კონტროლერი, არის Controller კლასის მემკვიდრე. თუ გავხსნით Controller კლასს, ვნახავთ, რომ მასში
აღწერილია შემდეგი კოდი :
namespace App\Http\Controllers;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
}
ეს კონტროლერი კი, თავის მხრივ, არის BaseController მშობელი კლასის მემკვიდრე და იყენებს სამი ტრეიტის,
ანუ დამატებითი კლასის ფუნქციონალს. ჩვენ გვაინტერესებს ტრეიტი ValidatesRequests, სწორედ ამ კლასის დახმარებითაა შესაძლებელი, ამა თუ იმ
კონტროლერში ინფორმაციის ვალიდაცია, ტრეიტი აღწერილია შემდეგ ფაილში vendor/laravel/framework/src/Illuminate/Foundation/Validation/ValidatesRequests.php.
ინფორმაციის ვალიდაციისათვის უნდა მივმართოთ Illuminate\Http\Request კლასის ობიექტის validate მეთოდს :
public function store(Request $request)
{
$validated = $request->validate([
'title' => 'required|unique:posts|max:255',
'body' => 'required',
]);
// ინფორმაცია ვალიდურია...
}
როგორც ვხედავთ ვალიდაციის წესები პარამეტრად გადავეცით validate მეთოდს (ვალიდაციის წესების სრული სია შეგიძლიათ იხილოთ
აქ). თუ ყველა წესი შესრულდება, კონტროლერი
ჩვეულებრივად გააგრძელებს მუშაობას, წინააღმდეგ შემთხვევაში კი შესაბამისი პასუხი დაუყოვნებლივ დაუბრუნდება მომხმარებელს და ვალიდაციის შემდეგ აღწერილი
ინსტრუქციები აღარ შესრულდება.
არსებობს ვალიდაციის წესების გადაცემის სხვაგვარი სინტაქსიც :
$validatedData = $request->validate([
'title' => ['required', 'unique:posts', 'max:255'],
'body' => ['required'],
]);
$request->validate([
'title' => 'bail|required|unique:posts|max:255',
'body' => 'required',
]);
ამ შემთხვევაში თუ title ატრიბუტის unique წესი დაირღვევა, მაშინ max წესის გადამოწმება აღარ მოხდება.
Illuminate\View\Middleware\ShareErrorsFromSession შუამავლის დამსახურებით, $errors ცვლადი ხელმისაწვდომია აპლიკაციის ნებისმიერ წარმოდგენის ფაილში და სწორედ მასში ინახება შეტყობინებები დარღვევების შესახებ ($errors ცვლადი არის Illuminate\Support\MessageBag ფაილში აღწერილი კლასის ობიექტი).
ვალიდაცია აღწერილი გვაქვს store მეთოდში და თუ ვამბობთ, რომ წესების დარღვევისას სისტემა უკან ამისამართებს მომხმარებელს, შესაბამისად გადავალთ create მეთოდში, რომელშიც სიახლის დასამატებელი ფორმის წარმოდგენის ფაილს ვაგენერირებთ, ან ფაილში შეცდომების ნახვა შემდეგნაირად შეგვიძლია :
<!-- /resources/views/post/create.blade.php -->
<h1>სიახლის დამატებაt</h1>
@if ($errors->any())
<div class="alert alert-danger">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<!-- სიახლის დასამატებელი ფორმა -->
<!-- /resources/views/post/create.blade.php -->
<label for="title">სიახლის სათაური</label>
<input id="title" type="text" name="title" class="@error('title') is-invalid @enderror">
@error('title')
<div class="alert alert-danger">{{ $message }}</div>
@enderror
$title = $request->old('title');
ასევე შეგვიძლია გამოვიყენოთ გლობალური დამხმარე old :
<input type="text" name="title" value="{{ old('title') }}">
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
class PostController extends Controller
{
public function store(Request $request)
{
$validator = Validator::make($request->all(), [
'title' => 'required|unique:posts|max:255',
'body' => 'required',
]);
if ($validator->fails())
{
return redirect('post/create')->withErrors($validator)->withInput();
}
// სიახლის შენახვა...
}
}
withErrors მეთოდი სესიაში შეინახავს შეტყობინებს შეცდომების შესახებ და ასევე, საშუალებას მოგვცემს წარმოდგენის ფაილში გამოვიყენოთ $errors
ცვლადი.
make მეთოდში შესაძლებელია ვალიდაციის შეცდომის შეტყობინებების განსაზღვრაც :
$validator = Validator::make($input, $rules, $messages = [
'required' => ':attribute არის აუცილებელი ველი',
]);
შეტყობინებაში :attribute ჩანაწერი ავტომატურად ჩანაცლდება ველის დასახელებით.
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\PostController;
Route::get('/post/create', [PostController::class, 'create']);
Route::post('/post', [PostController::class, 'store']);
შევქმნათ წარმოდგენის ფაილი resources/views/post/create.blade.php :
@if ($errors->any())
<div class="alert alert-danger">
<ul>
@foreach ($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<form action="{{ route('store_post') }}" method="post">
@csrf
<input type="text" name="title" class="@error('title') is-invalid @enderror" value="{{ old('title') }}">
<textarea name="body" class="@error('title') is-invalid @enderror">{{ old('body') }}</textarea>
<input type="submit" value="გაგზავნა">
</form>
PostController :
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class PostController extends Controller
{
public function create()
{
return view('post.create');
}
public function store(Request $request)
{
$validated = $request->validate([
'title' => 'required|alpha|min:5',
'body' => 'required',
]);
}
}
ფორმის სანახავად შევიდეთ მისამართზე http://127.0.0.1:8000/post/create. ფორმა გავაგზავოთ შემდეგი ვარიანტებით :
იმისათვის რათა, კონკრეტული ველის ყველა შეცდომის შეტყობინება გამოვიტანოთ უნდა გამოვიყენოთ $errors ობიექტის get მეთოდი, რომელსაც პარამეტრად უნდა გადავცეთ ველის დასახელება :
...
@if ($errors->any())
<div class="alert alert-danger">
<ul>
@foreach ($errors->get('title') as $message)
<li>{{ $message }}</li>
@endforeach
</ul>
</div>
@endif
...
იმის დასაგენად, დაფიქსირდა თუ არა კონკრეტულ ველზე ვალიდაციის შეცდომა, გამოიყენება $errors ობიექტის has მეთოდი,
რომელსაც პარამეტრად უნდა გადავცეთ ასევე ველის დასახელება :
@if($errors->has('email'))
...
@endif
'default' => env('DB_CONNECTION', 'mysql'),
ეს ჩანაწერი განსაზღვრავს თუ მონაცემთა ბაზის მართვის რომელ სისტემასთან ვმუშაობთ.
ამავე ფაილში აღწერილია მბ-სთან დასაკავშირებელი პარამეტრები სხვადასხვა სისტემებისათვის. mysql-ისათვის ეს პარამეტრებია :
...
'mysql' => [
'driver' => 'mysql',
'url' => env('DATABASE_URL'),
'host' => env('DB_HOST', '127.0.0.1'),
'port' => env('DB_PORT', '3306'),
'database' => env('DB_DATABASE', 'forge'),
'username' => env('DB_USERNAME', 'forge'),
'password' => env('DB_PASSWORD', ''),
'unix_socket' => env('DB_SOCKET', ''),
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'prefix' => '',
'prefix_indexes' => true,
'strict' => true,
'engine' => null,
'options' => extension_loaded('pdo_mysql') ? array_filter([
PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'),
]) : [],
],
...
როგორც ვხედავთ ზოგიერთი პარამეტრის მნიშვნელობა ბრუნდება env ფუნქციით, ეს ფუნქცია კავშირს ამყარებს .env ფაილთან და სწორედ
იქიდან მოაქვს ინფორმაცია. მივაქციოთ ყურადღება, რომ env ფუნქციას მეორე არგუმენტებად გადაცემული აქვს მნიშვნელობები, რომლებსაც სისტემა
ავტომატურად გამოიყენებს თუ .env ფაილში არ განვსაზღვრავთ შესაბამის პარამეტრებს. შევიტანოთ ცვლილებები .env ფაილში:
APP_NAME=Laravel
APP_ENV=local
APP_KEY=base64:Y2FMuhHrSmNjAh5NRuHY6NPlVjhl/YDVmHhe115iwXU=
APP_DEBUG=true
APP_LOG_LEVEL=debug
APP_URL=http://localhost
DB_CONNECTION=mysql
DB_HOST=localhost
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=
BROADCAST_DRIVER=log
CACHE_DRIVER=file
SESSION_DRIVER=file
QUEUE_DRIVER=sync
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_DRIVER=smtp
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
PUSHER_APP_ID=
PUSHER_APP_KEY=
PUSHER_APP_SECRET=
ახლა შევქმნათ მიგრაცია, ამისათვის, როგორც ვთქვით, დაგვჭირდება ფრეიმვორკის კონსოლი, გავხსნათ ბრძანებათა კონსოლი და გავუშვათ ბრძანება :
php artisan make:migration create_articles_table
ეს ბრძანება შექმნის მიგრაციის ახალ ფაილს - 2021_06_02_074019_create_articles_table, რომელშიც აღწერილი იქნება შესაბამისი კლასი.
ფაილის დასახელებაში გარდა ჩვენს მიერ მითითებული სათაურისა დამატებულია მიმდინარე თარიღი და მიმდინარე დროის ნიშნული. მიგრაციების ფაილების
ნახვა შესაძლებელია შემდეგ მისამართზე database/migrations. ახლად შექმნილ ფაილში აღწერილი იქნება შემდეგი კლასი :
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->id();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('articles');
}
}
როგორვ ვხედავთ, ეს კლასი არის გლობალური კლასის - Migration-ის მემკვიდრე. აღწერილია ორი მეთოდი up() და
down(). პირველ მათგანში მითითებული ინსტრუქციები შესრულდება მაშინ, როდესაც გამოვიძახებთ CreateArticlesTable მიგრაციას,
ხოლო მეორე მათგანის ინსტრუქციები შესრულდება მაშინ, როცა შევწყვეტთ კონკრეტული მიგრაციების გამოყენებას (მაგალითად გავაუქმებთ ბოლოს
გაშვებულ მიგრაციებს).
'php artisan make:migration create_articles_table' ჩანაწერიდან სისტემამ ავტომატურად დაადგინა, რომ ცხრილის შექმნის მიგრაციას ვქმნით და Schema ფასადსაც შესაბამისი 'create' მეთოდით მიმართა.
up() მეთოდი მიმართავს Schema კლასს, ეს არის ცხრილების სპეციალური კონსტრუქტორი, მისი დახმარებით ხდება მბს ცხრილებთან მუშაობა. ამ კლასის create მეთოდი ქმნის ახალ ცხრილს, მეთოდს პირველ პარამეტრად უნდა გადაეცეს ცხრილის სახელი, მეორე პარამეტრი კი არის ქოლბექ ფუნქცია, რომელიც უნდა შესრულდეს ცხრილის შექმნის შემდეგ. ამ ფუნქციას, თავის მხრივ, მითითებული აქვს არგუმენტი, რომლის მეშვეობითაც შეგვიძლია მივმართოთ უშუალოდ ცხრილის ობიექტს.
დავამატოთ რამდენიმე ველი ცხრილს :
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->id(); // id ველი იქნება : INT, AUTO_INCREMENT, PRIMARY KEY
$table->string('name', 100); // name ველი იქნება : Varchar 100
$table->text('text'); // text ველი იქნება : Text
$table->string('img', 255); // img ველი იქნება : Varchar 255
$table->timestamps(); // შეიქმნება timestamp ტიპის ორი ველი created_at და updated_at
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('articles');
}
}
ველთა ტიპების სრული სია შეგიძლიათ იხილოთ აქ.
php artisan migrate
ამ ბრძანების შემდეგ შესრულდება ყველა მიგრაცია, რომელიც database/migrations საქაღალდეშია.
php artisan migrate:rollback
თუ ახლა phpmyadmin-ს შევამოწმებთ იქ დაგვხვდება მხოლოდ ერთი ცხრილი migrations.
php artisan make:migration change_articles_table --table=articles
შეიქმნება ახალი მიგრაცია :
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class ChangeArticlesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('articles', function (Blueprint $table) {
//
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('articles', function (Blueprint $table) {
//
});
}
}
დავამატოთ ველი :
public function up()
{
Schema::table('articles', function (Blueprint $table) {
$table->string('alias', 100); // Varchar 100
});
}
ამასთანავე არ უნდა დაგვავიწყდეს, რომ down() მეთოდში მისათითებელია ინსტრუქცია, რომელიც წაშლის ამ ველს მიმდინარე მიგრაციის
გაუქმების შემთხვევაში. ველების წასაშლელად გამოიყენება $table ობიექტის dropColumn() მეთოდი, რომელსაც პარამეტრად უნდა გადაეცეს
შესაბამისი ველის დსასახელება :
public function down()
{
Schema::table('articles', function (Blueprint $table) {
$table->dropColumn('alias');
});
}
'php artisan make:migration change_articles_table' ჩანაწერიდან სისტემამ ავტომატურად დაადგინა, რომ ცხრილის რედაქტირების მიგრაციას ვქმნით და Schema ფასადსაც შესაბამისი 'table' მეთოდით მიმართა.
ისღა დაგვრჩენია გავუშვათ მიგრაცია შესრულებაზე :
php artisan migrate
use Illuminate\Support\Facades\Schema;
Schema::rename($from, $to);
ცხრილის წასაშლელად უნდა გამოვიყენოთ Schema ფასადის drop ან dropIfExists მეთოდი :
Schema::drop('users');
Schema::dropIfExists('users');
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateUsersTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('users', function (Blueprint $table) {
//
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('users');
}
}
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('users', function (Blueprint $table) {
//
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('users');
}
};
ახლა განვმარტოთ თუ რის გამო მოხდა ეს ცვლილება. განვიხილოთ ასეთი სიტუაცია :
php artisan make:seeder ArticlesSeeder
შეიქმნება ფაილი database/seeders/ArticlesSeeder.php :
namespace Database\Seeders;
use Illuminate\Database\Seeder;
class ArticlesSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
//
}
}
როგორც ვხედავთ კლასი ArticlesSeeder არის Seeder კლასის მემკვიდრე და მას გააჩნია მეთოდი run(), ამ მეთოდში აღწერილი
ინსტრუქციები შესრულდება მაშინ, როცა ავამუშავებთ ჩვენს მიერ შექმნილ მექანიზმს. ამ მეთოდში აღვწეროთ მბ-ს ცხრილში ინფორმაციის შესატანი
ინსტრუქციები, მართალია ჯერ არ ვიცით თუ როგორ უნდა ვიმუშავოთ მბ-სთან, მაგრამ ოდნავ გავუსწროთ მოვლენებს და მოვიყვანოთ მარტივი მაგალითი :
namespace Database\Seeders;
use DB;
use Illuminate\Database\Seeder;
class ArticlesSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
DB::table('articles')->insert([
[
'name' => 'Blog Post 2',
'text' => 'Blog Post 2 testing post 2 and its text',
'img' => 'pic2.jpg'
],
[
'name' => 'Blog Post 3',
'text' => 'Blog Post 3 testing post 3 and its text',
'img' => 'pic3.jpg'
]
]);
}
}
ამის შემდეგ database/seeders/DatabaseSeeder.php ფაილში აღწერილი DatabaseSeeder კლასის run მეთოდში უნდა
ჩავამატოთ ჩვენს მიერ შექმნილი, მბ-ში ინფორმაციის შემტანი ფაილის (ArticlesSeeder.php) შესაბამისი ჩანაწერი, ეს საჭიროა იმისათვის,
რომ ინფორმაციის შეტანის ბრძანების გაშვებისას სისტემამ ეს ფაილიც გამოიძახოს.
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
/**
* Seed the application's database.
*
* @return void
*/
public function run()
{
$this->call(ArticlesSeeder::class);
}
}
ამის შემდეგ საჭიროა, რომ ხელახლა მოვახდინოთ composer-ის ავტოჩამტვირთველის გენერირება, რათა ახალდამატებული კლასიც შევიდეს ჩატვირთული კლასების სიაში,
ეს ხდება შემდეგი ბრძანებით :
composer dump-autoload
ახლა უკვე შეგვიძლია გავუშვათ მბ-ში ინფორმაციის შეტანის ბრძანება :
php artisan db:seed
არსებობს მეორე ვარიანტიც : თუ DatabaseSeeder კლასში არ ჩავამატებთ ზემოთ აღწერილ ჩანაწერს,
მაშინ ინფორმაციის შეტანის ბრძანება უნდა გავუშვათ შემდეგი სახით :
php artisan db:seed --class=ArticlesSeeder
namespace App\Http\Controllers;
use DB;
use App\Http\Controllers\Controller;
class PostController extends Controller
{
public function index()
{
$articles = DB::select("SELECT * FROM articles");
dump($articles);
}
}
ბრძანებას დავამატოთ WHERE ფილტრი, მაგრამ მანამდე აღვნიშნოთ ერთი რამ : laravel-ი მბ-სთან მუშაობისას იყენებს PDO ინტერფეისს, რაც იმას
ნიშნავს, რომ ბრძანებების გაშვება ხდება წინასწარგანსაზღვრის პრინციპით (პრეპარირებული განაცხადები). ასეთ შემთხვევაში შეგვიძლია
select მეთოდს მეორე პარამეტრად, მასივის სახით გადავცეთ ის მნიშვნელობები, რომლებიც
ჩაანაცვლებენ პრეპარირებული განაცხადის ნიშნულებს, მარკერებს :
$articles = DB::select("SELECT * FROM articles WHERE id=?", [2]);
dump($articles);
select მეთოდი აბრუნებს შედეგთა ნაკრებს მასივის სახით, მასივისა, რომლის თითოეული ელემენტიც არის PHP stdClass-ის ობიექტის
სახით წარმოდგენილი კონკრეტული ჩანაწერი მონაცემთა ბაზიდან :
foreach ($articles as $article)
{
echo $article->name;
}
$insert = DB::insert("INSERT INTO articles (name, text, img) VALUES(?,?,?)", ['Article 4','Article 4 Text','img4.jpg']);
dd($inser);
$update = DB::update("UPDATE articles SET name=? WHERE id > ?", ['Renamed Article', 1]);
dd($update);
ეს მეთოდი აბრუნებს ზემოქმედებული ჩანაწერების რაოდენობას.
$delete = DB::delete("DELETE FROM articles WHERE id=?", [1]);
dd($delete);
მეთოდი აბრუნებს წაშლილი ჩანაწერების რაოდენობას.
$statement = DB::statement("DROP TABLE test");
dd($statement); // true/false
$unprepared = DB::unprepared('UPDATE articles SET text = "New text" WHERE id = 2');
dd($unprepared); // true/false
ინგ : Builder - მწარმოებელი, მწარმოებელი-ქარხანა, მშენებელი
მოთხოვნათა კონსტრუქტორის უკან მოიაზრება სპეციალური კლასი Builder (ფსევდონიმი queryBuilder ანუ მოთხოვნათა მშენებელი :)) ), რომელსაც გააჩნია კონკრეტული მეთოდები, რომელთაგანაც თითოეული უზრუნველჰყოფს ბრძანების სრული ტანის კონკრეტული ნაწილის ფორმირებას, ჩვენ აღარ გვიწევს მოთხოვნის ხელით დაწერა. აღნიშნული კლასი აღწერილია vendor/laravel/framework/src/Illuminate/Database/Builder.php ფაილში.იმისათვის რათა გამოვიყენოთ ეს კლასი, პირველ რიგში უნდა შევქმნათ მბ-ს ცარიელი მოთხოვნის ობიექტი კონკრეტული ცხრილისათვის, ამისათვის უნდა მივმართოთ DB ფასადის table მეთოდს, რომელსაც პარამეტრად უნდა გადავცეთ ცხრილის დასახელება.
$articles = DB::table('articles')->get();
dd($articles);
ამ ჩანაწერმა რეალურად შექმნა შემდეგი ბრძანება
"SELECT * FROM articles"
$article = DB::table('articles')->first();
ამ ჩანაწერმა რეალურად შექმნა შემდეგი ბრძანება :
"SELECT * FROM articles LIMIT 1"
$name = DB::table('articles')->value('name');
ამ ჩანაწერმა რეალურად შექმნა შემდეგი ბრძანება :
"SELECT 'name' FROM articles LIMIT 1"
$names = DB::table('articles')->pluck('name');
ამ ჩანაწერმა რეალურად შექმნა შემდეგი ბრძანება :
"SELECT 'name' FROM articles"
$count = DB::table('articles')->count();
ამ ჩანაწერმა რეალურად შექმნა შემდეგი ბრძანება :
"SELECT COUNT(*) FROM articles"
$max = DB::table('articles')->max('id');
ჩანაწერმა რეალურად შექმნა შემდეგი ბრძანება :
"SELECT MAX('id') FROM articles"
ასევე არსებობს min, avg და sum მეთოდებიც, რომლებიც ანალოგიურად მუშაობს.
$articles = DB::table('articles')->select('id','name');
თუ ამ ბრძანებას გავუშვებთ, შედეგად დაგვიბრუნდება Builder კლასის ობიექტი და არა ის შედეგი რაც გვსურს, იმიტომ რომ select მეთოდს ჯერ
არ შეუსრულებია თავისი საქმე, ამისათვის მას უნდა მივაშველოთ get მეთოდი :
$articles = DB::table('articles')->select('id','name');
ამ ჩანაწერმა რეალურად შექმნა შემდეგი ბრძანება :
"SELECT id, name FROM articles"
SELECT * FROM articles WHERE id > 10
Laravel-ში კი შემდეგნაირი :
$articles = DB::table('articles')->select('name')->where('id','>',2)->get();
ამ ჩანაწერმა რეალურად შექმნა შემდეგი ბრძანება
"SELECT 'name' FROM articles WHERE id > 2"
როგორც ვხედავთ where მეთოდს გადაეცა სამი პარამეტრი: იმ ველის დასახელება რომლის მიხედვითაც ვფილტრავთ, პირობითი ოპერატორი და შესადარებელი
მნიშვნერლობა. თუ პირობით ოპერატორტს საერთოდ არ მივუთითებთ ფრეიმვორკი იგულისხმებს, რომ ეს ოპერატორი არის ტოლობის ოპერატორი.
რამდენიმე პირობითი ფილტრის ერთდროულად გამოყენების სინტაქსი ასეთია :
$articles = DB::table('articles')->select('id','name')
->where('id','>',2)
->where('name','like','%A%')
->get();
ამ ჩანაწერმა რეალურად შექმნა შემდეგი ბრძანება :
"SELECT 'name' FROM articles WHERE id > 2 AND name LIKE '%A%'"
როგორც ვხედავთ, პირობითი ოპერატორები ერთმანეთთან დაკავშირდა ლოგიკური "და" -ს მეშვეობით. ჩნდება კითხვა : როგორ მოვიქცეთ თუ გვჭირდება მაგალითად ლოგიკური "ან" ? ამისათვის where ოპერატორს უნდა დავუმატოთ მეოთხე არგუმენტი :
$articles = DB::table('articles')->select('id','name')
->where('id','>',2)
->where('name','like','%A%','or')
->get();
ამ ჩანაწერმა რეალურად შექმნა შემდეგი ბრძანება :
"SELECT 'name' FROM articles WHERE id > 2 OR name LIKE '%A%'"
რამდენიმე პირობითი ოპერატორის გამოყენება შესაძლებელია where მეთოდზე მხოლოდ ერთი მიმართვითაც, ასეთ შემთხვევაში მას არგუმენტად უნდა
გადაეცეს მასივი, რომელიც თავის თავში მოიცავს ფილტრის პირობების შემცველ ქვე-მასივებს :
$articles = DB::table('articles')->select('id','name')
->where([
['id','>',2],
['name','like','a%','or']
])
->get();
$articles = DB::table('articles')->whereBetween('id',[2,5])->get();
ამ ჩანაწერმა რეალურად შექმნა შემდეგი ბრძანება :
"SELECT 'name' FROM articles WHERE id BETWEEN 2 AND 5"
ამ მეთოდის შებრუნებული მეთოდია whereNotBetween.
ამ ორი მეთოდიას ანალოგიურია მეთოდები whereIn და whereNotIn ამიტომ მათ მაგალითებს აღარ მოვიყვანთ.
$articles = DB::table('articles')->get()->groupBy('name');
ამ ჩანაწერმა რეალურად შექმნა შემდეგი ბრძანება :
"SELECT * FROM articles GROUP BY name"
$articles = DB::table('articles')->take(2)->get();
ამ ჩანაწერმა რეალურად შექმნა შემდეგი ბრძანება :
"SELECT * FROM articles LIMIT 2"
$insert = DB::table('articles')->insert([
['name' => 'test name', 'text' => 'test text', 'img' => 'test.jpg'],
['name' => 'test name 1', 'text' => 'test text 1', 'img' => 'test.jpg']
]);
dd($insert);
ამ ჩანაწერმა რეალურად შექმნა შემდეგი ორი ბრძანება :
"INSERT INTO articles (name, text) VALUES ('test name', 'test text', 'test.jpg')"
"INSERT INTO articles (name, text) VALUES ('test name 1', 'test text 1', 'test.jpg')"
მეთოდი აბრუნებს TRUE მნიშვნელობას წარმატების შემთხვევაში, წინააღმდეგ შემთხვევაში ბრუნდება მნიშვნელობა FALSE.
ანალოგიურად მუშაობს insertGetId მეთოდიც, უბრალოდ ის აბრუნებს ბოლოს დამატებული ჩანაწერის id-ს.
$update = DB::table('articles')->where('id',2)->update(['name' => 'hello world']);
ამ ჩანაწერმა რეალურად შექმნა შემდეგი ბრძანება :
"UPDATE articles SET name='hello world' WHERE id=2"
მეთოდი აბრუნებს ზემოქმედებული ჩანაწერების რაოდენობას.
$delete = DB::table('articles')->where('id',2)->delete();
ამ ჩანაწერმა რეალურად შექმნა შემდეგი ბრძანება :
"DELETE FROM articles WHERE id=2"
$article = DB::table('articles')->find(3);
ამ ჩანაწერმა რეალურად შექმნა შემდეგი ბრძანება :
"SELECT * FROM articles WHERE id = 3"
მეთოდი აბრუნებს ზემოქმედებული ჩანაწერების რაოდენობას.
$users = DB::table('users')
->join('contacts', 'users.id', '=', 'contacts.user_id')
->join('orders', 'users.id', '=', 'orders.user_id')
->select('users.*', 'contacts.phone', 'orders.price')
->get();
$users = DB::table('users')->leftJoin('posts', 'users.id', '=', 'posts.user_id')->get();
$users = DB::table('users')
->select(DB::raw('count(*) as user_count, status'))
->where('status', '<>', 1)
->groupBy('status')
->get();
ეს ჩანაწერი დააგენერირებდა ასეთ ბრძანებას :
select count(*) as user_count, status from `users` where `status` <> 1 group by `status`
ეს ყველაფერი მოგვცემდა ასეთ შედეგს :
{
"age":32,
"sex":"male",
"salary":4500,
"languages":[
"ka","en"
]
}
{
"age":30,
"sex":"male",
"salary":5500,
"languages":[
"ka"
]
}
{
"age":28,
"sex":"female",
"salary":2500,
"languages":[
"ka","ru","en"
]
}
{
"age":21,
"sex":"male",
"salary":1500,
"languages":[
"ka"
]
}
properties ველის მიხედვით მოვძებნოთ მომხმარებლები, რომელთა ხელფასიც 5500 ლარია :
$users = DB::table('users')->where('properties->salary', 5500)->get();
იგივეს გაკეთება ასეც შეგვიძლია :
$users = DB::table('users')->whereJsonContains('properties->salary', 5500)->get();
თუ ვიყენებთ MySQL ან PostgreSQL მონაცემთა ბაზებს, შეგვიძლია, რომ whereJsonContains მეთოდს სასურველი პარამეტრები გადავცეთ
მასივის სახით :
$users = DB::table('users')->whereJsonContains('properties->languages', ['en', 'ka'])->get();
ახლა გავიგიოთ რომელმა მომხმარებლებმა იციან ორი ენა, ამაში დაგვეხმარება whereJsonLength მეთოდი :
$users = DB::table('users')->whereJsonLength('properties->languages', 2)->get();
ახლა გავიგიოთ რომელმა მომხმარებლებმა იციან ორ ენაზე მეტი :
$users = DB::table('users')->whereJsonLength('properties->languages', '>', 2)->get();
$users = DB::table('users')->whereBetween('id', [1, 100])->get();
$users = DB::table('users')->whereNotBetween('id', [1, 100])->get();
$users = DB::table('users')->whereIn('id', [1, 2, 3])->get();
$users = DB::table('users')->whereNotIn('id', [1, 2, 3])->get();
$users = DB::table('users')->whereNull('updated_at')->get();
$users = DB::table('users')->whereNotNull('updated_at')->get();
$users = DB::table('users')->whereDate('created_at', '2016-12-31')->get();
$users = DB::table('users')->whereMonth('created_at', '12')->get();
$users = DB::table('users')->whereDay('created_at', '31')->get();
$users = DB::table('users')->whereYear('created_at', '2016')->get();
$users = DB::table('users')->whereTime('created_at', '=', '11:20:45')->get();
$users = DB::table('users')->whereColumn('first_name', 'last_name')->get();
შეგვიძლია მეთოდს დავამატოთ მესამე პარამეტრიც :
$users = DB::table('users')->whereColumn('updated_at', '>', 'created_at')->get();
ასევე შეგვიძლია, რომ შედარების პირობები გადავცეთ მასივის სახით :
$users = DB::table('users')
->whereColumn([
['first_name', '=', 'last_name'],
['updated_at', '>', 'created_at'],
])->get();
მოდელის შსაქმნელად კონსოლში უნდა ავკრიფოთ შემდეგი ბრძანება :
php artisan make:model Article
მოდელის სახელის განსაზღვრისას შეზღუდვები არ არსებობს, მაგრამ როგორც წესი მოდელს სახელს არქმევენ ხოლმე მბს იმ ცხრილის დასახელების მიხედვით,
რომელთან სამუშაოდაც უნდა გამოვიყენოთ ეს მოდელი, მაგალითად თუ ცხრილს ჰქვია articles მაშინ სასურველია შესაბამის მოდელს დავარქვათ
Article. ცხრილს სახელი ჰქვია მრავლობით ფორმაში რადგან იგი შეიცავს რამდენიმე article-ს ანუ ჩანაწერის შესახებ ინფორმაციას, მოდელი კი
როგორც ვთქვით მუშაობს ცხრილის კონკრეტულ ჩანაწერებთან, ამიტომ მოდელს დავარქვით ცხრილის სახელი მხოლობით ფორმაში.
მოდელები ინახება app/Models საქაღალდეში, ბრძანების შედეგად შექმნილი მოდელიც შეინახება აქ.
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Article extends Model
{
//
}
ჩვენს მიერ შექმნილი ნებისმიერი მოდელი იქნება Model მშობელი კლასის მემკვიდრე, რომელიც აღწერილია შემდეგ ფაილში :
vendor/laravel/framework/src/Illuminate/Database/Model.php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Article extends Model
{
protected $table = 'articles';
}
უნდა აღინიშნოს, რომ ამ შემთხვევაში $table თვისების განსაზღვრა საჭირო არ არის, რადგანაც ცხრილისა და მოდელის
სახელებს შორის გვაქვს ის დამოკიდებულება რაც ზემოთ ვთქვით : მოდელს სახელად აქვს ცხრილის დასახელების მხოლობითი
ფორმა. ასეთ შემთხვევაში კი სისტემა ავტომატურად მიხვდება, თუ რომელ ცხრილთან უნდა იმუშავოს.
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Article extends Model
{
protected $primaryKey = 'article_id';
}
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Article extends Model
{
public $incremeting = FALSE;
}
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Article extends Model
{
public $timestamps = FALSE; // ამ შემთხვევაში ეს ველები შეივსება მნიშვნელობა Null-ით
}
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Article extends Model
{
protected $fillable = ['name','text','img'];
}
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Article extends Model
{
protected $guarded = ['name','text','img'];
}
namespace App\Http\Controllers;
use App\Models\Article;
use App\Http\Controllers\Controller;
class PostController extends Controller
{
public function index()
{
foreach (Article::all() as $article)
{
echo $article->name;
}
}
}
all() მეთოდმა რეალურად გაუშვა შემდეგი ბრძანება :
SELECT * FROM articles
შედეგად კი დააბრუნა მოდელთა კოლექცია, რას ნიშნავს ეს ? როგორვ ვთქვით, მოდელი არის ობიექტის სახით აღწერილი, ცხრილის კონკრეტული ჩანაწერი,
კონკრეტული ჩანაწერის აბსტრაქცია, აქედან გამომდინარე, თუ ცხრილში რამდენიმე ჩანაწერია, დაბრუნდება რამდენიმე ჩანაწერის ობიექტური წარმოდგენა ანუ ამ
წარმოდგენათა (მოდელთა) ნაკრები.
ჩნდება კითხვა, როგორ გამოვიყენოთ მიღებული ინფორმაცია ანუ ცხრილის ველთა მნიშვნელობები ? ეს ხდება მოდელის იმავე სახელწოდებების მქონე თვისებების მიხედვით რაც აქვს ცხრილთა ველებს :
...
foreach (Article::all() as $article)
{
echo $article->name;
}
...
რასაკვირველია მოდელში შესაძლებელია მთხოვნათა კონსტრუქტორის გამოყენებაც :
$a$articles = Article::where('id', '>', 2)->orderBy('name')->take(3)->get();
// მოდელის ამოღება პირველადი გასაღების მიხედვით (primary key)
$article = Article::find(1);
// პირველივე ისეთი მოდელის ამოღება, რომელიც აკმაყოფილებს მითითებულ პირობებს
$article = Article::where('status', 1)->first();
// პირველივე ისეთი მოდელის ამოღება, რომელიც აკმაყოფილებს მითითებულ პირობებს
$article = Article::firstWhere('active', 1);
შეიძლება მოხდეს ისე, რომ დაგვჭირდეს კონკრეტული ჩანაწერის ამოღება და მისი დაბრუნება თუ იგი მოიძებნება,
წინააღმდეგ შემთხვევაში კონკრეტული ფუნქციონალის შესრულება :
$model = Article::where('id', 65673)->firstOr(function () {
echo 'ვერ მოიძებნა';
});
namespace App\Http\Controllers;
use App\Models\Article;
use Illuminate\Database\Eloquent\ModelNotFoundException as ModelNotFoundException;
class PostController extends Controller
{
public function index()
{
try
{
$article = Article::findOrFail(31);
// მოდელი მოიძებნა...
}
catch (ModelNotFoundException $e)
{
if ($e instanceof ModelNotFoundException)
{
dd($e->getMessage()); // No query results for model [App\Models\Article] 31
}
}
}
}
$article = Article::firstOrCreate([
'name' => 'Article Name',
'text' => 'Article Text',
]);
მუშაობის პროცესის გასამარტივებლად ხშირად იყენებენ ხოლმე მიდგომას, რომლის მიხედვითაც კონკრეტული მოდელების შესაქმნელად საჭირო ინფორმაცია ერთ სივრცეშია ხოლმე
მოქცეული (მაგალითად მასივში როგორც ზემოთ მოვიქეცით) და მოდელისათვის კონკრეტული ველების ცალ-ცალკე მიკუთცნება აღარ არის საჭირო. ამ მიდგომას ეწოდება
მასიური განსაზღვრებადობა (Mass Assignment), რომელიც, რიგ შემთხვევებში, არც ისე უსაფრთხო შეიძლება იყოს.
ნახსენები მასივის როლში ხშირად შეიძლება მოგვევლინოს HTTP მოთხოვნის ტანი ($request->all()). წარმოვიდგინოთ ასეთი შემთხვევა: დავუშვათ მომხმარებლების ცხრილში გვაქვს 'admin' ველი, რომლის შესაძლო მნიშვნელობებიცაა 0 (არ არის ადმინსტრატორი) ან 1 (ადმინსტრატორია). ბუნებრივია მომხმარებლებს არ უნდა მივცეთ ამ ველის შეცვლის უფლება. არადა თუ იგი ფორმაში ახალ ველს ჩაამატებს სახელით - admin და მნიშვნელობით 1, რა თქმა უნდა ეს ველი HTTP მოთხოვნის ტანშიც შევა და შესაბამისად მომხმარებელი არალეგალური გზით გაიხდის თავს ადმინად )) :
// მომხარებლის დამატების კოდი
$user = new User(request()->all());
ამ ყველაფრის თავიდან ასაცილებლად Laravel-ში შემოღებულია შევსებადი და დაცული ველების ცნებები.
დავუბრუნდეთ ჩვენს კოდს :
$article = Article::firstOrCreate([
'name' => 'Article Name',
'text' => 'Article Text',
]);
თუ ახლა ამ კოდსს გავუშვებთ ვიხილავთ შეცდომას, უფრო სწორად ფრეიმვორკის მიერ დაგენერირებულ გამონაკლისს (MassAssignmentException), ეს იმიტომ, რომ Article
მოდელში, $fillable მეთოდის მეშვეობით არ არის აღწერილი ცხრილის იმ ველთა დასახელებები, რომლებშიც ნებადართულია ინფორმაციის შეტანა
მასიური განსაზღვრებადობის გზით :
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Article extends Model
{
protected $fillable = ['name', 'text'];
}
ინფორმაციის შეტანამდე firstOrCreate მეთოდი გადაამოწმებს უკვე ხომ არ არსებობს ცხრილში ისეთი ჩანაწერი რომლისთვისაც name ველის მნიშვნელობა არის
'Article Name', თუ ესეთი ჩანაწერი არსებობს მაშინ მეთოდი დააბრუნებს ამ ჩანაწერის მოდელს. ხოლო თუ არ არსებობს ასეთი ჩანაწერი, მაშინ მეთოდი შექმნის მას
და დააბრუნებს ახალშექმნილ მოდელს.
$article = new Article;
$article->name = 'ტესტი';
$article->text = 'ტესტი';
$article->img = 'ტესტი';
$article->save();
save მეთოდით ჩანაწერების დამატებისას ავტომატურად ხდება ცხრილის created_at და updated_at ველების შევსება.
$article = Article::create([
'name' => 'სათაური',
'text' => 'ტექსტი',
'img' => 'test.jpg',
]);
create() მეთოდით ჩანაწერების შექმნისას მოდელში აუცილებლად უნდა გვქონდეს აღწერილი მასიურ განსაზღვრებადობასთან დაკავშირებული
fillable და guarded თვისებები :
protected $fillable = ['name', 'text','img'];
ან :
protected $fillable = [];
$article = Article::find(3);
$article->name = "ახალი სათაური";
$article->save();
updated_at ველის მნიშვნელობა ავტომატურად განახლდება.
Article::where('id','>',1)->update([
'name' => 'ახალი სათიურები',
'text' => 'ახალი ტექსტები'
]);
$article = Article::find(4);
$article->delete();
Article::destroy(9);
თუ რამდენიმე ჩანაწერის წაშლა გვსურს ერთდროულად, მაშინ მეთოდს პარამეტრად უნდა გადაეცეს მასივი სადაც შეტანილი იქნება ამ ჩანაწერთა იდენტიფიკატორები.
php artisan make:migration change_article_table_soft --table=articles
იმისათვის რათა მიგრაციაში განვსაზღვროთ აღნიშნული ველი, მიგრაციის up მეთოდში უნდა გამოვიყენოთ softDeletes მეთოდი. სწორედ ეს მეთოდი
დაამატებს ცხრილში deleted_at ველს. შესაბამისად მიგრაციის down მეთოდში მოვახდინოთ მისი უგულებელყოფა :
...
public function up()
{
Schema::table('articles', function (Blueprint $table) {
$table->softDeletes();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('articles', function (Blueprint $table) {
$table->dropColumn('deleted_at');
});
}
...
ახლა გავუშვათ ამ მიგრაციის შესრულების ბრძანება :
php artisan migrate
თუ ახლა შევამოწმებთ articles ცხრილს, ვნახავთ, რომ მას დამატებული ექნება deleted_at ველი.
ახლა გამოვიყენოთ softDelete() მეთოდი. ამისათვის მოდელში უნდა დავამატოთ სპეციალური კლასი softDeletes, ეს კლასი მდებარეობს შემდეგ მისამართზე : vendor/laravel/framework/src/Illuminate/Database/Eloquent/SoftDeletes.php.
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Article extends Model
{
use SoftDeletes;
protected $fillable = ['name', 'text'];
}
კონტროლერში კი ხდება შემდეგი :
$article = Article::find(10);
$article->delete();
თუ ახლა შევამოწმებთ ცხრილს ვნახავთ რომ id=10 ჩანაწერი განახლებული იქნება და deleted_at ველში მითითებული იქნება წაშლის თარიღი.
როგორც ვხედავთ ეს ჩანაწერი წაშლილი არ არის მაგრამ იგი აღარ შევა შედეგთა ნაკრებში, თუ ამოვარჩევთ მაგალითად ცხრილის ყველა ჩანაწერს.
იმის დასადგენად წაიშლა თუ არა ჩანაწერი, გამოიყენება trashed მეთოდი :
$article = Article::find(8);
$article->delete();
if ($article->trashed())
{
die('წაიშლა');
}
წაშლილი ჩანაწერის აღსადგენად გამოიყენება withTrashed და restore მეთოდები :
Article::withTrashed()->find(10)->restore();
onlyTrashed მეთოდის დახმარებით ხდება softDelete მეთოდით წაშლილი ჩანაწერების ამოღება :
$articles = Article::onlyTrashed()->get();
მაგალითად გვაქვს სტატიების ცხრილი articles და იმ მომხმარებლების ცხრილი - users, რომლებიც ამატებენ ამ სტატიებს. ასეთ შემთხვევაში, როგორც წესი, მომხმარებლის საიდენტიფიკაციო ნომერი (id) იწერება ხოლმე articles ცხრილის user_id ველში. ანუ articles ცხრილის user_id ველი არის users ცხრილთან კავშირის საგარეო გასაღები. პრაქტიკაში ასეთი კავშირები საკმაოდ ხშირია და ამიტომ laravel-ში ჩადგმულია ფუნქციონალი, რომელიც ამარტივებს ამ კავშირებთან მუშაობას.
laravel-ში არსებობს ცხრილთა შორის კავშირის რამდენიმე ვარიანტი, მათ შორის ძირითადებია :
Laravel-ის ინსტალაციის შემდეგ database/migrations საქაღალდეში ავტომატურად შეიქმნებოდა მიგრაციის რამდენიმე ფაილი, მათ შორის users ცხრილის შესაქმნელი xxxx_xx_xx_xxxxxx_create_users_table. შესაბამისად, როდესაც პირველად გავუშვით 'php artisan migrate' ბრძანება, ეს ცხრილიც შეიქმნებოდა.
ახლა შევქმნათ ტელეფონის ნომრების ცხრილი :
php artisan make:migration create_phones_table
აღვწეროთ ველები :
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreatePhonesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('phones', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->string('phone', 100);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('phones');
}
}
გავუშვათ მიგრაციის ბრძანება :
php artisan migrate
ვიხილავთ ამდაგვარ შეტყობინებას :
ახლა კი ყურადღება მივაქციოთ მიგრაციის შემდეგ ჩანაწერს :
$table->foreignId('user_id')->constrained()->onDelete('cascade');
foreignId მეთოდი პარამეტრად გადაცემულ მნიშვნელობას - 'user_id'-ს, მოხსნის '_id' ბოლოსართს, მიღებულ სიტყვას დაამატებს მრავლობითი ფორმის აღმნიშვნელ
's' ასოს და ამგვარად მიიღებს სიტყვა 'users' - ს, რაც ნიშნავს, რომ phones ცხრილის user_id ველი უკავშირდება users ცხრილის id ველს.
constrained()->onDelete('cascade') ჩანაწერი კი აღნიშნავს, რომ users ცხრილიდან კონკრეტული მომხმარებლის წაშლის შემთხვევაში, მისი შესაბამისი ტელეფონის ნომერიც წაიშლება phones ცხრილში.
ახლა შევქმნათ ტელეფონების მოდელი :
php artisan make:model Phone
იმისათვის რათა User მოდელი დავაკავშიროთ Phone მოდელთან, User მოდელში უნდა ჩავსვათ phone მეთოდი,
რომელიც, თავის მხრივ, გამოიძახებს hasOne მეთოდს და დააბრუნებს შესაბამის შედეგს. hasOne მეთოდი აღწერილია
Illuminate\Database\Eloquent\Model მშობელ მოდელში.
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* მომხმარებელთან დაკავშირებული ტელეფონის ნომრის ამოღება
*/
public function phone()
{
return $this->hasOne(Phone::class);
}
}
ახლა გამოვიყენოთ დამყარებული კავშირი :
$user = User::find(1); // select * from users where id=1 limit 1
dump($user->phone); // select * from phones where user_id = 1 and user_id is not null limit 1
მას შემდეგ რაც User მოდელში, hasOne() მეთოდის მეშვეობით განვსაზღვრეთ კავშირი 'ერთი-ერთთან', მოდელთან მიმართებაში
შეგვიძლია გამოვიყენოთ დინამიური ანუ ცვალებადსახელიანი მეთოდი, რომლის სახელიც ემთხვევა
User მოდელის იმ მეთოდის სახელს, რომელშიც მოხდა კავშირის განსაზღვრა.
სისტემა საგარეო გასაღებს (foreign key) ადგენს მშობელი კლასის სახელიდან გამომდინარე, მაგალიოთად ამ შემთხვევაში იგი ავტომატურად გულისხმობს, რომ Phone მოდელს აქვს ველი user_id. თუ გვსურს, რომ ეს მიდგომა გადავფაროთ, hasOne მეთოდს, მეორე პარამეტრად უნდა გადავცეთ ჩვენთვის სასურველი ველის დასახელება :
return $this->hasOne(Phone::class, 'foreign_key');
ამ მომენტისათვის, User მოდელი დაკავშირებულია მოდელ Phone-სთან, მაგრამ უკუკავშირი არ არის დამყარებული. ამის გასაკეთებლად
Phone მოდელში ჩავამატოთ user მეთოდი, რომელშიც გამოვიყენებთ belongsTo() მეთოდს (ინგ: belongs - კუთვნილება,
ეკუთვნის) :
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Phone extends Model
{
/**
* მომხმარებელი რომელსაც ეკუთვნის ტელეფონის კონკრეტული ნომერი
*/
public function user()
{
return $this->belongsTo(User::class);
}
}
ამ შემთხვევაში სისტემა საგარეო გასაღებს (foreign key) ადგენს ურთიერთკავშირის აღმწერელი მეთოდის დასახელებითა და '_id' სუფიქსის კომბინაციით. ამ შემთხვევაში მეთოდის დასახელებაა - 'user', სუფიქსთან ერთად კი მიიღება 'user_id', შესაბამისად სისტემა ჩათვლის, რომ Phone მოდელს აქვს ველი 'user_id'. თუ გვსურს, რომ ეს მიდგომა გადავფაროთ, belongsTo მეთოდს, მეორე პარამეტრად უნდა გადავცეთ ჩვენთვის სასურველი ველის დასახელება :
public function user()
{
return $this->belongsTo(User::class, 'foreign_key');
}
ტელეფონის კონკრეტული ნომრის მფლობელის დადგენა შესაძლებელია ასე :
$phone = Phone::find(1); // select * from phones where id = 1 limit 1
dump($phone->user); // select * from users where id = 1 limit 1
php artisan make:migration change_articles_table
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class ChangeArticlesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('articles', function (Blueprint $table) {
$table->foreignId('user_id')->constrained()->onDelete('cascade');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
$table->dropColumn('user_id');
}
}
ბუნებრივია, რომ ერთმა მომხმარებელმა შესაძლებელია დაამატოს რამდენიმე სიახლე, შესაბამისად users ცხრილი სიახლეების ცხრილ -articles-თან
დაკავშირდება კავშირის ტიპით - ერთი მრავალთან.
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
/**
* მომხმარებელთან დაკავშირებული ტელეფონის ნომრის ამოღება
*/
public function phone()
{
return $this->hasOne(Phone::class);
}
/**
* მომხმარებელის მიერ დამატებული სიახლეების ამოღება
*/
public function articles()
{
return $this->hasMany(Article::class);
}
}
როგორც ვხედავთ, გამომდინარე იქედან, რომ ერთი მომხმარებელი შეიძლება იყოს რამდენიმე სიახლის ავტორი,
მეთოდის დასახელება მრავლობით ფორმაშია გასაზღვრული.
კონკრეტული მომხმარებლის სიახლეებთან წვდომა შესაძლებელია ასე :
$articles = User::find(1)->articles;
foreach ($articles as $article)
{
//
}
'User::find(1)->articles' ჩანაწერის შედეგად გაეშვებოდა ორი ბრძანება :
select * from users where id = 1 limit 1
select * from articles where user_id = 1 and user_id is not null and deleted_at is null
მეორე ბრძანებას წითლად მონიშნული ჩანაწერი მიემატა იმის გამო, რომ ჩვენ ადრე Article მოდელში გამოვიყენეთ
ე.წ 'მსუბუქი წაშლის სისტემა (softDeletes).
ეს ჩანაწერი :
dump(User::find(1)->articles());
მოგვცემს HasMany ობიექტს :
$article = User::find(1)->articles->where('id',3)->first();
როგორც ვნახეთ, users და articles ცხრილებს შორის დამყარდა კავშირი ერთი ბევრთან.
ახლა განვსაზღროთ უკუკავშირიც, ამისათვის სიახლეების მოდელში ჩავამატოთ შემდეგი ჩანაწერი :
public function user()
{
return $this->belongsTo(User::class);
}
თუ ვამბობთ, რომ უნდა განვსაზღროთ 'ერთი ბევრთან' კავშირის უკუკავშირი, ბუნებრივია ამ კავშირის ფორმულირება
იქნება 'ბევრი ერთთან', სწორედ ამიტომაა მეთოდის დასახელება განსაზღვრული
მხოლობით ფორმაში.
დავაბრუნოთ იმ სიახლის ავტორის სახელი, რომლის id-იც არის 3
$article = Article::find(3);
return $article->user->name;
$articles = Article::all();
dump($articles);
შესრულდება შემდეგი ბრძანება და ბრუნდება მოდელების შემდეგი კოლექცია :
$articles = Article::all();
foreach($articles as $article)
{
echo $article->user->name . '<br>';
}
ასეთ შემთხვევაში მივიღებთ შემდეგ სურათს :
ასეთ შემთხვევაში გამოიყენება ე.წ 'ძუნწი ჩატვირთვა' :)) ამ შემთხვევაში დამატებითი ინფორმაციის ჩატვირთვა ხდება კოლექციასთან ერთად. ოპტიმიზირებული ჩატვირთვისას უნდა გამოვიყენოთ მეთოდი with, რომელიც უზრუნველჰყოფს ინფორმაციის ჩატვირთვას ძირითად ცხრილთან დაკავშირებული სხვა ცხრილებიდანაც. მეთოდს პარამეტრად უნდა გადაეცეს იმ მოდელის დასახელება, რომელთან დაკავშირებაც გვსურს :
$articles = Article::with('user')->get();
foreach($articles as $article)
{
echo $article->user->name . '<br />';
}
ასეთ შემთხვევაში მივიღებთ შემდეგ სურათს :
$users = User::has('articles')->get();
foreach($users as $user)
{
echo $user->name . '<br />';
}
has მეთოდთან ერთად შესაძლებელია სხვადასხვა პირობითი ოპერატორების გამოყენებაც, დავუშვათ გვინდა ამოვარჩიოთ ის მომხმარებლები, რომლებსაც ორზე მეტი სიახლე აქვთ დამატებული:
$users = User::has('articles','>=', 2)->get();
foreach($users as $user)
{
echo $user->name . '<br />';
}
php artisan make:migration create_roles_table
...
public function up()
{
Schema::create('roles', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->timestamps();
});
}
...
php artisan make:migration create_role_user_table
...
public function up()
{
Schema::create('role_user', function (Blueprint $table) {
$table->id();
$table->integer('user_id')->unsigned()->default(1);
$table->foreign('user_id')->references('id')->on('users');
$table->integer('role_id')->unsigned()->default(1);
$table->foreign('role_id')->references('id')->on('roles');
$table->timestamps();
});
}
...
მივაქციოთ ყურადღება, რომ ბოლოს შექმნილი ცხრილის დასახელება 'role_user' შემთხვევითი არ არის - მისი პირველი ნაწილი
'role' არის 'roles' ცხრილთან სამუშაო მოდელის დასახელება, მეორე ნაწილი 'user' კი არის 'users'
ცხრილთან სამუშაო მოდელის დასახელება.
გავუშვათ ამ მიგრაციების შესრულების ბრძანება და შევქმნათ ცხრილები.
php artisan make:model Role
მომხმარებლების მოდელში კი განვსაზღვროთ შემდეგი კავშირი ცხრილებს შორის : ერთი მომხმარებელი უკავშირდება რამდენიმე
როლს :
...
public function roles()
{
return $this->belongsToMany(Role::class);
}
...
ახლა თუ სასურველ კონტროლერში შევიტანთ შემდეგ კოდს :
$user = User::find(1);
$roles = $user->roles;
foreach ($roles as $role)
{
echo $role->name . '<br>';
}
ბრაუზერში ვიხილავთ ჩვენს მიერ დამატებული ორივე როლის დასახელებას (admin, moderator).
ახლა მაგალითისათვის ამოვარჩიოთ მომხმარებლის ის როლი რომლის საიდენტიფიკაციო ნომერიცაა 2 :
$user = User::find(1);
$role = $user->roles()->where('roles.id',2)->first();
dump($role);
მივაქციოთ ყურადრება, რომ 'where' ფილტრში დაგვჭირდა იმის დაკონკრეტება, თუ რომელი ცხრილის საიდენტიფიკაციო
ნომრის მიხედვით ვცდილობთ ინფორმაციის ამოღებას, ეს იმიტომ მოხდა, რომ 'id' ველი სამივე ცხრილშია
(users, roles, role_user) და თუ ასე არ მოვიქცეოდით დაფიქსირდებოდა კონფლიქტი, შეცდომა.
ზუსტად ანალოგიურად მოხდება უკუკავშირის დამყარება : "ერთი როლი უკავშირდება რამდენიმე მომხმარებელს".
$user = User::find(1);
$article = new Article([
'name' => 'მესამე სიახლე',
'text' => 'მესამე სიახლის ტექსტი'
]);
$user->articles()->save($article);
როგორც ვხედავთ save მეთოდს არგუმენტად გადაეცა იმ ინფორმაციის შემცველი მოდელი,
რომლის შეტანაც გვსურს ბაზაში.
რამდენიმე ჩანაწერის ერთდროულად დამატებისათვის გამოიყენება მეთოდი saveMany :
$user = User::find(1);
$user->articles()->saveMany([
new Article(['name'=>'მეხუთე სიახლე', 'text'=>'მეხუთე სიახლის ტექსტი']),
new Article(['name'=>'მეექვსე სიახლე', 'text'=>'მეექვსე სიახლის ტექსტი'])
]);
არსებობს ინფორმაციია შეტანის კიდევ ერთი მეთოდი create, რომელსაც პარამეტრად გადაეცემა უშუალოდ შესატანი ინფორმაცია და არა ამ ინფორმაციის შემცველი მოდელი :
$user = User::find(1);
$user->articles()->create([
'name' => 'მეოთხე სიახლე',
'text' => 'მეოთხე სიახლის ტექსტი'
]);
$user = User::find(1);
$user->articles()->where('id', 3)->update([
'name' => 'მესამე სიახლის ახალი სათაური'
]);
ამ ჩანაწერში უნდა გავითვალისწინოთ ერთი რამ : როგორც ვხედავთ ჯერ ვიღებთ მომხმარებლის შესახებ ინფორმაციას (id=1) და
შემდეგ ვუშვებთ ინფორმაციის განახლების შესახებ ბრძანებას. თუ სიახლე რომლის შეცვლაც გვსურს (id=3), არ ეკუთვნის
ამორჩეულ მომხმარებელს, მაშინ სისტემა არ განაახლებს ამ ჩანაწერს, და ეს ასეც უნდა იყოს.
აუტენთიფიკაციის პარამეტრები განსაზღვრულია config/auth.php ფაილში. მომხმარებლის აუტენთიფიკაციის პროცესი შედგება ორი ძირითადი ელემენტისაგან: პირველი ეს არის ე.წ მცველი - guard, მეორე კი - პროვაიდერი provider.
მცველი განსაზღვრავს თუ რა სახით იქნას შენახული ინფორმაცია იმ მომხმარებლის შესახებ, რომელიც აკეთებს მოთხოვნას, ანუ როგორ შევინახოთ ინფორმაცია იმის შესახებ, რომ მოთხოვნა გააკეთა მაგალითად აუტენთიფიცირებულმა მომხმარებელმა, ეს ინფორმაცია შესაძლებელია შენახულ იქნას სესიაში ან სპეციალურ სტრიქონში სახელად token.
პროვაიდერი განსაზღვრავს თუ როგორ და რა სახით შეიძლება მიიღოს მომხმარებელმა ინფორმაცია მბ-დან ან სხვა წყაროდან.
აუტენტიფიკაციის სისტემასთან მუშაობისთვის საჭიროა წარმოდგენის ფაილები, მარშრუტები და კონტროლერები. არსებობს ამ ყველაფრის შექმნის რამდენიმე ვარიანტი. განვიხილოთ ერთ-ერთი მათგანი - Laravel BreezeLaravel Breeze-ს ინსტალაცია ხდება შემდეგი ბრძანების მეშვეობით :
composer require laravel/breeze --dev
ამის შემდეგ უნდა გავუშვათ Artisan-ის ბრძანება :
php artisan breeze:install
ამ ყველაფრის შედეგად ვიხილავთ ამდაგვარ შეტყობინებებს :
Breeze scaffolding installed successfully.
Please execute the "npm install && npm run dev" command to build your assets.
იმისათვის რათა ხელი მიგვიწვდებოდეს CSS სტილებთან საჭიროა შემდეგი ბრძანებების გაშვება :
npm install
npm run dev
ამ მომენტისათვის აუტენტიფიკაციის შაბლონების სტილები ჩვენთვის ნაკლებად მნიშვნელოვანია, მაგრამ თუ მაინც გსურთ, რომ
გქონდეთ ავტორიზაციის ლამაზი ფორმა, მაშინ დააინსტალირეთ node.js (იხილეთ
Typescript-ის ცნობარის მე-2-ე თავი) და გაუშვით ზემოთ
მოყვანილი ორი ბრძანება :))
Breeze-ს ინსტალაციის შემდეგ, შეიქმნებოდა routes/auth.php ფაილი, რომელშიც აღწერილი იქნება აუტენტიფიკაციის სისტემის მარშრუტები და რომლის გამოძახებაც ავტომატურად მოხდებოდა routes/web.phpფაილში :
require __DIR__.'/auth.php';
კონტროლერები განთავსდებოდა App/Http/Controllers/Auth საქაღალდეში.
წარმოდგენის ფაილები განთავსდებოდა resources/views/auth საქაღალდეში.
ისღა დაგვრჩენია ვესტუმროთ http://127.0.0.1:8000/login და http://127.0.0.1:8000/register მისამართებს.
Laravel-ის ინსტალაციის შემდეგ ავტომატურად შეიქმნებოდა მომხმარებლებთან სამუშო App/Models/User მოდელი და ასევე მიგრაციის ორი ფაილი შემდეგი ცხრილებისათვის : users და password_resets. სწორედ users ცხრილში შეინახება რეგისტრაციისას მომხმარებლის მიერ აკრეფილი ინფორმაციები. password_resets ცხრილს კი შევეხებით ოდნავ მოგვიანებით.
routes/web.php ფაილს თუ გადავამოწმებთ, შევნიშნავთ, რომ Breeze-ს ინსტალაციის შემდეგ, მასში ასევე ჩაემატებოდა შემდეგი ჩნაწერი :
Route::get('/dashboard', function () {
return view('dashboard');
})->middleware(['auth'])->name('dashboard');
ეს არის რეგისტრირებული მომხმარებლის პირადი კაბინეტის მარშრუტი, რომელსაც მიმაგრებული აქვს auth
შუამავალი. ბუნებრივია მომხმარებლს კაბინეტში შესვლა შეუძლია მხოლოდ მაშინ, როდესაც მას აუტენტიფიკაცია გავლილი აქვს
და სწორედ ამას ემსახურება ეს შუამავალიც. ამაში ადვილად დავრწმუნდებით თუ /dashboard გვერდზე შევალთ
აუტენტიფიკაციის გარეშე. ასეთ შემთხვევაში სისტემა გადაგვამისამართებს /login გვერდზე.
დავაკვირდეთ app/Http/Kernel.php ფაილის შემდეგ ფრაგმენტს :
...
protected $routeMiddleware = [
'auth' => \App\Http\Middleware\Authenticate::class,
...
];
...
use Illuminate\Support\Facades\Auth;
// აუტენტიფიცირებული მომხმარებელი
$user = Auth::user(); // App\Models\User Object
// აუტენტიფიცირებული მომხმარებლის ID
$id = Auth::id(); // 6
user მეთოდი აბრუნებს აუტენტიფიცირებული მომხმარებლის ობიექტს, id მეთოდი კი აუტენტიფიცირებული
მომხმარებლის იდენტიფიკატორს.
აუტენტიფიცირებულ მომხმარებელთან წვდომა შესაძლებელია Illuminate\Http\Request კლასის მეშვეობითაც :
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class TestController extends Controller
{
public function index(Request $request)
{
dd($request->user());
}
}
use Illuminate\Support\Facades\Auth;
if(Auth::check())
{
echo 'აუტენტიფიცირებულია';
}
else
{
return redirect()->route('login');
}
მიუხედავად იმისა, რომ ამ მეთოდით შესაძლებელია მომხმარებლის აუტენტიფიცირება/არააუტენტიფიცირების გადამოწმება,
უკეთესი ვარიანტია თუ საჭირო მარშრუტებს შუამავლების მეშვეობით დავიცავთ ხოლმე და თუ მომხმარებელი ამ შუამავალს
გაივლის, შესაბამისად აღარც იმის გადამოწმება დაგვჭირდება არის თუ არა იგი აუტენტიფიცირებული.
Route::get('/profile', function () {
// მხოლოდ აუტენტიფიცირებული მომხმარებლები
})->middleware('auth');
protected function redirectTo($request)
{
return route('somewhere');
}
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class LoginController extends Controller
{
public function authenticate(Request $request)
{
$credentials = $request->validate([
'email' => ['required', 'email'],
'password' => ['required'],
]);
if (Auth::attempt($credentials))
{
$request->session()->regenerate();
return redirect()->intended('dashboard');
}
return back()->withErrors(['email' => 'არასწორი ელ_ფოსტა']);
}
}
რა თქმა უნდა, საჭიროა მარშრუტის განსაზღვრაც და ვინაიდან აქამდე Laravel Breeze პაკეტს ვიყენებდით შევიტანოთ
შესაბამისი ცვლილებები routes/auth.php ფაილშიც :
use App\Http\Controllers\LoginController;
Route::post('/login', [LoginController::class, 'authenticate'])->middleware('guest');
attempt მეთოდს პარამეტრად გადაეცემა მასივი, რომელშიც თავმოყრილია აუტენტიფიკაციისათვის საჭირო ინფორმაციები,
ამ შემთხვევაში - ელ_ფოსტა და პაროლი (ინგ: Attempt - გასინჯვა, შემოწმება, გამოცდა, მცდელობა).
ამის შემდეგ მონაცემთა ბაზაში მოიძებნება მომხმარებლის მიერ აკრეფილი ელ_ფოსტის
შესაბამისი ჩანაწერი (email ველის მიხედვით). თუ ასეთი ჩანაწერი მოიძებნება მაშინ უკვე დარდება ამ ჩანაწერის password
ველისა და მომხმარებლის მიერ აკრეფილი პაროლის მნიშვნელობები. როგორც ვიცით password ველში ჰეშირებული
პაროლია შენახული, თუმცა ეს იმას არ ნიშნავს, რომ აუტენტიფიკაციისას ჩვენც უნდა დავჰეშოთ აკრეფილი პაროლი - სისტემა
ამას ავტომატურარდ გააკეთებს. თუ პაროლიც დაემთხვა მაშინ მოხდება აუტენტიფიცირებული მომხმარებლის შესახებ ინფორმაციის
სესიაში შენახვა. მეთოდი აბრუნებს ლოგიკურ მნიშვნელობას - true, თუ აუტენტიფიკაცია წარმატებულია, წინააღმდეგ შემთხვევაში
ბრუნდება მნიშვნელობა - false.
intended მეთოდი მომხმარებელს გადაამისამართებს იმ მარშრუტზე, რომელთან წვდომასაც იგი ცდილობდა აუტენტიფიკაციამდე (ინგ: Intended - განზრახული, ჩაფიქრებული).
თუ გვსურს, რომ attempt მეთოდს დავუმატოთ სხვა გადასამოწმებელი პარამეტრებიც, უნდა მოვიქცეთ ასე :
if (Auth::attempt(['email' => $email, 'password' => $password, 'active' => 1]))
{
// წარმატებული აუტენტიფიკაცია
}
use Illuminate\Support\Facades\Auth;
$remember = $request->has('remember');
if (Auth::attempt(['email' => $email, 'password' => $password], $remember))
{
// მომხმარებელი დამახსოვრებულია ...
}
როდესაც მომხმარებელი აუტენტიფიკაციისას აწვება 'დამიმახსოვრე' ღილაკს, იკვრება მოქმედებათა შემდეგი ჯაჭვი :
გენერირდება უნიკალური ჰეშირებული სტრიქონი, რომელიც ინახება როგორც ბრაუზერში (Cookie-ს სახით), ასევე სერვერზე
('users' ცხრილის 'remember_token' ველში), ამის შემდეგ თუ მომხმარებელი დახურავს და ისევ გახსნის ბრაუზერს,
სისტემა გადაამოწმებს არსებობს თუ არა დამახსოვრებული ჰეშ-სტრიქონი Cookie-ში, თუ კი - მაშინ შეამოწმებს შეესაბამება
თუ არა ეს სტრიქონი ბაზაში არსებულ რომელიმე ჩანაწერს და თუ ეს ასეა მაშინ მომხმარებელი ავტომატურად ხდება
აუტენტიფიცირებული. Cookie-ში შენახული ინფორმაცია იარსებებს მანამ, სანამ მომხმარებელი არ გამოვა სისტემიდან (logout),
უბრალოდ ბრაუზერის დახურვით ეს ინფორმაცია არ იშლება.
use App\Models\User;
use Illuminate\Support\Facades\Auth;
$user = User::find(1);
Auth::login($user);
Auth::login($user, $remember = true);
იმისათვის რათა login მეთოდმა იმუშავოს, საჭიროა, რომ მომხმარებლის ობიექტი დაკავშირებული იყოს
Illuminate\Contracts\Auth\Authenticatable კონტრაქტთან, სწორედ ამას ემსახურება, Laravel-ში ნაგულისხმევად
შექმნილი app/Models/User მოდელის შემდეგი ჩანაწერი :
namespace App\Models;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
...
}
Auth::loginUsingId(1, $remember = true);
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
public function logout(Request $request)
{
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect('/');
}
როგორც წესი, კონტროლიორების განსაზღვრა ხდება ხოლმე Gate ფასადთან მიმართვით App\Providers\AuthServiceProvider პროვაიდერის boot მეთოდში. მაგალითისათვის შევქმნათ კონტროლიორი, რომელიც გადაწყვეტს აქვს თუ არა კონკრეტულ მომხმარებელს კონკრეტული სიახლის დარედაქტირების უფლება :
use App\Models\Post;
use App\Models\User;
use Illuminate\Support\Facades\Gate;
public function boot()
{
$this->registerPolicies();
Gate::define('update-post', function (User $user, Post $post) {
return $user->id === $post->user_id;
});
}
კონტროლიორის გამოყენება ხდება Gate ფასადის allows და denies მეთოდების მეშვეობით.
შევნიშნოთ, რომ კონტროლიორს პარამეტრად არ გადავცემთ აუტენტიფიცირებულ მომხმარებლს, რადგან ამას Laravel-ი
ავტომატურად გააკეთებს :
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use App\Models\Post;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;
class PostController extends Controller
{
public function update(Request $request, Post $post)
{
if (! Gate::allows('update-post', $post))
{
abort(403);
}
// სიახლის განახლება ...
}
}
თუ გვსურს, რომ კონკრეტულ ქმედებაზე გადავამოწმოთ, არა აუტენტიფიცირებული, არამედ სხვა მომხმარებლის უფლებები, მაშინ
უნდა გამოვიყენიოთ Gate ფასადის forUser მეთოდი :
if (Gate::forUser($user)->allows('update-post', $post))
{
// მომხმარებელს შეუძლია განაახლოს სიახლე ...
}
if (Gate::forUser($user)->denies('update-post', $post))
{
// მომხმარებელს არ შეუძლია განაახლოს სიახლე ...
}
ასევე შესაძლებელია, რომ გადავამოწმოთ მომხმარებლის უფლებები ერთდროულად რამდენიმე ქმედებაზე. ამისათვის გამოიყენება
Gate ფასადის any და none მეთოდები :
if (Gate::any(['update-post', 'delete-post'], $post))
{
// მომხმარებელს შეუძლია სიახლის წაშლა ან რედაქტირება ...
}
if (Gate::none(['update-post', 'delete-post'], $post))
{
// მომხმარებელს არ შეუძლია სიახლის არც წაშლა და არც რედაქტირება ...
}
/lang
/en
messages.php
/ka
messages.php
ფრეიმვორკის დაინსტალირების შემდეგ ამ საქაღალდეში, ნაგულისხმეობის პრინციპით, იქმნება ლოკალიზაციის ერთადერთი საქაღალდე
ინგლისური ენისათვის lang/en.
როგორც წესი, თითოეული ლოკალიზაცია ინახება საქაღალდეში, რომლის დასახელებაც ემთხვევა შესაბამისი ენის კოდს (მაგ: ინგლისური - en, ქართული - ka და ა.შ). ლოკალიზაციის ქვე-საქაღალდეებში ინახება ე.წ ფაილი ლექსიკონები, მათში აღწერილია კონკრეტული სტრიქონების თარგმანები კონკრეტულ ენაზე. ამ სტრიქონებს ეწოდებათ ენობრივი კონსტანტები. ფაილ ლექსიკონებში ბრუნდება უბრალო ასოციაციური მასივები, რომლის გასაღებებიც გადასათარგმნი სტრიქონებია, ხოლო ამ გასაღებთა მნიშვნელობები - ამ სტრიქონების თარგმანები შესაბამის ენაზე.
თითოეული ფაილი ლექსიკონი განკუთვნილია, პროექტის კონკრეტული ელემენტისათვის, მაგალითად ვალიდაციის ელემენტს აქვს თავისი ფაილი (validation.php), გვერდების გადანომრვის ელემენტს - თავისი (pagination.php), აუთენტიფიკაციის გვერდს - თავისი (auth.php) და ა.შ.
ამის შემდეგ უბრალოდ უნდა გავხსნათ ახლადშექმნილ ლოკალიზაციაში არსებული ლექსიკონ-ფაილები და მათში აღწერილ მასივებში, გასაღებების მნიშვნელობები მივუთითოთ ქართულად.
ეს რაც შეეხებოდა უკვე არსებულ ლექსიკონ-ფაილებს. ახლა შევქმნათ საკუთარი ლექსიკონ-ფაილი და ვნახოთ თუ როგორ ხდება მისი გამოყენება, lang/ka საქაღალდეში შევქმნათ ფაილი - messages.php :
return [
'welcome' => 'კეთილი იყოს თქვენი მობრძანება',
'hello' => 'მოგესალმებით'
];
ლოკალიზაციის შექმნის შემდეგ საჭიროა config/app.php ფაილში აღწერილი ასოციაციური მასივის locale
გასაღების მნიშვნელობის ჩასწორება :
'locale' => 'ka'
ამავე მასივის fallback_locale გასაღებში მითითებულია ალტერნატიული ლოკალიზაციის დასახელება :
'fallback_locale' => 'en'
ანუ თუ locale გასაღებში მითითებული ლოკალიზაცია რაიმე მიზეზის გამო მიუწვდომელი იქნება სისტემისათვის, მაშინ
ფრეიმვორკი შეეცდება გამოიყენოს ალტერნატიული ლოკალიზაცია.
use Lang;
$title = Lang::get('messages.welcome');
როგორც ვხედავთ, მეთოდს პარამეტრად გადაეცა ჯერ სასურველი ფაილ-ლექსიკონის (messages) დასახელება, შემდეგ კი იმ
ენობრივი კონსტანტის დასახელება (welcome), რომლის გადათარგმნაც გვსურს და რომელიც ამავე ფაილშია აღწერილი.
Lang::get ჩანაწერი შეიძლება ჩაიწეროს უფრო მოკლედაც. შემდეგი ორი ჩანაწერი ერთმანეთის ტოლფასია :
$title = Lang::get('messages.welcome');
$title = trans('messages.welcome');
შეიძლება, მოხდეს ისე, რომ ენობრივი კონსტანტის თარგმანი იყოს დინამიური, მაგალითად სხვადსხვა შემთხვევაში შეიძლება
დაგვჭირდეს სხვადასხვა ტექსტები :
კეთილი იყოს თქვენი მობრძანება გიორგი
კეთილი იყოს თქვენი მობრძანება მარიამ
კეთილი იყოს თქვენი მობრძანება ცოტნე
ასეთ შემთხვევაში ენობრივი კონსტანტის თარგმნისას ორწერტილით უნდა გამოიყოს თარგმანის სტატიკური და დინამიური ნაწილები
(ორწერტილის შემდეგ არ უნდა იყოს გამოტოვებული ადგილი), ჩვენს შემთხვევაში სტატიკურია ტექსტი -
"კეთილი იყოს თქვენი მობრძანება", ხოლო სახელები დინამიურია :
return [
'welcome' => 'კეთილი იყოს თქვენი მობრძანება :name',
'hello' => 'მოგესალმებით'
];
ამის შემდეგ Lang ფასადის get მეთოდს, მასივის სახით, არგუმენტად უნდა გადავცეთ სასურველი სახელი :
use Lang;
$title = Lang::get('messages.welcome', array('name' => 'ვასო'));
იმის გასარკვევად აღწერილია თუ არა ესა თუ ის ენობრივი კონსტანტა ამა თუ იმ ფაილ-ლექსიკონში, გამოიყენება Lang
ფასადის has მეთოდი, რომელსაც არგუმენტებად უნდა გადაეცეს საურველი ფაილ-ლექსიკონისა და ენობრივი კონსტანტის
დასახელებები :
use Lang;
if(Lang::has('messages.welcome'))
{
$title = Lang::get('messages.welcome', array('name' => 'ვასო'));
}
use Illuminate\Support\Facades\App;
$locale = App::currentLocale();
if (App::isLocale('ka'))
{
//
}