Laravel 9.*



1. რა არის Laravel ?

Laravel - MVC პრინციპზე დაფუძნებული პლატფორმა, ფრეიმვორკი, ინსტრუმენტთა ნაკრები იმ პროგრამისტებისათვის, რომლებიც PHP-ის მეშვეობით ამზადებენ ვებ-გვერდებს. ფრეიმვორკის დახმარებით გაცილებით სწრაფად და მარტივად ვქმნით აპლიკაციებს და აღარ გვიწევს კოდის წერის დაწყება ნოლიდან. ფაქტიურად ფრეიმვორკი წარმოადგენს მომავალი პროექტის კარკასს, ჩონჩხს, მასში ჩადებულია ისეთი კლასები რომელთა მეშვეობითაც პროგრამისტს აღარ უწევს მთელი რიგი სტანდარტული და რუტინული სამუშაოების ჩატარება, მაგალითად: საიტზე შესვლის წერტილის შექმნა, ადამიანისათვის გასაგები URL-ების შექმნა, შემავალი პარამეტრებისა და მონაცემების ვალიდაციის მექანიზმის შექმნა, მონაცემთა ბაზასთან მუშაობის ორგანიზება და ა.შ

Laravel-ის პირველი ვერსია შეიქმნა 2011 წელს (ავტ. Taylor Otwell).

2. ინსტალაცია
Laravel-თან სამუშაოდ დაგვჭირდება : რა თქმა უნდა ბრაუზერი, რომელიმე ტექსტ-ედიტორი ან IDE (Integrated Development Environment - პროგრამული უზრუნველყოფა, რომელიც გამოიყენება აპლიკაციების შექმნისას და რომელიც ერთიან გრაფიკულ ინტერფეისში (GUI - graphical user interface) აერთიანებს პროგრამისტისათვის საჭირო სხვადასხვა ხელსაწყოებსა და ინსტრუმენტებს. მაგ: NetBeans, PhpStorm ...), PHP ინტერპრეტატორი და მონაცემთა ბაზის სერვერი.

დიდი ალბათობით, Laravel-ის შესწავლისას მომხმარებელს უკვე ექნება შეხება PHP-ს რომელიმე ინტერპრეტატორთან და მონაცემთა ბაზის სერვერთან, ეს შეიძლება იყოს როგორც ცალკე დაინსტალირებული პროგრამები, მაგ: ვებ-სერვერი Apache, PHP ინტერპრეტატორი, მბ სერვერი MYSQL, ასევე გამზადებული ნაკრებები მაგ: Denver, OpenServer, XAMPP, WAMP, LAMP და ა.შ. შესაძლებელია მათი გამოყენებაც.

ინსტალაცია Composer-ით

რა არის Composer-ი ?

Composer-ი არის სხვადასხვა დამოკიდებულებების მენეჯერი, რომლის საშუალებითაც ხდება, ამა თუ იმ ბიბლიოთეკების, დამოკიდებულებებისა და პაკეტების ინტეგრირება PHP პროგრამულ უზრუნველყოფაში. Windows ოპერაციულ სისტემაში Composer-ის ინსტალაცია საკმაოდ მარტივია, უბრალოდ უნდა გადმოვწეროთ საინსტალაციო ფაილი და დავაინსტალიროთ (ავტორები: Nils Adermann და Jordi Boggiano).

ინტალაციის პროცესი მოგვთხოვს გზას PHP-მდე, რომელიც შეიძლება გამოიყურებოდეს ასე:





ინსტალაციის შემდეგ ბრძანებათა ველიდან გავუშვათ შემდეგი ბრძანება : composer თუ შედეგად ვიხილავ ამდაგვარ სურათს:



ესეიგი ყველაფერი რიგზეა.

ახალი პროექტის შექმნა

ახალი პროექტის შესაქმნელად გამოიყენება composer-ის ბრძანება create-project, ბრძანებასთან ერთად ასევე უნდა მივუთითოთ სასურველი პაკეტის დასახელება და იმ დირექტორიის დასახელება სადაც გვსურს, რომ შეიქმნას ახალი პროექტი: composer create-project <PACKAGE_NAME> <MY_PROJECT>

პაკეტების საცავი (Repository)

ამა თუ იმ დამოკიდებულების ან პაკეტის ინსტალაციის პროცესს თუ მარტივი ენით განვმარტავთ, მივიღებთ ასეთ სურათს : მას შემდეგ რაც გაეშვება ინსტალაციის ბრძანება, Composer-ი მიაკითხავს რაღაც წყაროს, რესურსს, სადაც განთავსებულია სასურველი პაკეტის შესაბაისი ფაილები, დამოკიდებულებები და ა.შ და ამ ყველაფერს გადმოწერს ჩვენს ლოკალურ სისტემაში.

ჩნდება კითხვები : მაინც სად არის ინახება ეს პაკეტები ? საიდან მოაქვს Composer-ს ეს ყველაფერი ?

როდესაც Composer-ი ინსტალირდება, ნაგულისხმეობის პრინციპით ხდება ერთადერთი საცავის რეგისტრაცია, ეს საცავია ვებ-გვერდი Packagist.org.

***

მაშ ასე, ბრძანებათა ველის მეშვეობით გადავდივართ სასურველ დირექტორიაში და ვუშვებთ შემდეგ ბრძანებას : composer create-project laravel/laravel laravel ბრძანების გაშვების შემდეგ დაიწყება Laravel-ის ინსტალაცია და აგრეთვე შეიქმნება laravel საქაღალდე შესაბამისი ფაილებისა და საქაღალდეების სტრუქტურით :



როგორ გავხსნათ პროექტი ?

რა არის Artisan ?

ინგ: Artisan - ხელოსანი, ოსტატი.

Artisan-ი არის Laravel-ში ჩადგმული ბრძანებათა ინტერფეისის სახელწოდება, რომლის მეშვეობითაც შესაძლებელია სხვადასხვა საჭირო ბრძანებებისა და ინსტრუქციების საკმაოდ მარტივად გაშვება აპლიკაციაზე მუშობის პროცესში. Artisan ბრძანებათა სრული ჩამონათვალის სანახავად უნდა გავუშვათ შემდეგი ბრძანება :

php artisan list

***

ბრძანებათა ველიდან გადავიდეთ ახლად შექმნილ საქაღალდეში - laravel და ავამუშავოთ პროექტი შემდეგი ბრძანების დახმარებით : php artisan serve შედეგი იქნება :



თუ ახლა შევალთ ამ მისამართზე: http://127.0.0.1:8000, ვიხილავთ ჩვენს პროექტს :



3. რა არის MVC ?
MVC (Model–View–Controller ანუ მოდელი-წარმოდგენა-კონტროლერი) - არის კომპიუტერულ ინჟინერიაში გავრცელებული, კოდირების შაბლონი, მეთოდი, იდეა, რომელიც საშუალებას იძლევა განცალკევდეს ვებ-აპლიკაციის ლოგიკა და მისი წარმოდგენა ანუ ის ნაწილი რომელსაც მომხმარებელი ხედავს ბრაუზერში.



ეს განცალკევება აპლიკაციას ხდის უფრო მოქნილს და ადვილად კორექტირებადს, რადგან თუ გვსურს ვიზუალური მხარის ჩასწორება საქმე გვაქვს მხოლოდ მასთან და არა ლოგიკურ ფუნქციონალთან და პირიქით.

წარმოდგენა

MVC-ს ელემენტ - 'წარმოდგენა (view)'-ს ინფორმაცია გამოაქვს ეკრანზე. ეს არის აპლიკაციის დიზაინერული ნაწილი მინიმალური ლოგიკით. წარმოდგენა შეიძლება შედგებოდეს რამოდენიმე შაბლონისაგან.



კონტროლერი

MVC-ს ელემენტი - 'კონტროლერი' არის მომხმარებლისა და აპლიკაციის წარმოდგენის დამაკავშირებელი ბლოკი. იგი იღებს ინფორმაციას მომხმარებლისაგან, ამუშავებს, ამოწმებს მას და ეს დამუშავებული ინფორმაცია ეგზავნება MVC-ს მესამე ელემენტს - 'მოდელს', კონტროლერი მისგან ღებულობს შესაბამის პასუხს და წყვეტს თუ წარმოდგენის რომელმა შაბლონმა უნდა დაუბრუნოს ეს პასუხი მომხმარებელს.



მოდელი

MVC-ს მესამე ელემენტში - 'მოდელი', ხდება სისტემის მუშაობის ლოგიკის ძირითადი ნაწილის აღწერა. მოდელი მუშაობს მონაცემებთან, ახდენს მონაცემებით მანიპულირებას. მოდელი იღებს კონტროლერისაგან რაიმე მოთხოვნას, შემდეგ მბ-დან მოაქვს შესაბამისი ინფორმაცია და უბრუნებს კონტროლერს. სხვა სიტყვებით, რომ ვთქვათ მოდელის ფუნქციაა მიიღოს მონაცემი, დაამუშავოს იგი და დააბრუნოს პასუხი. პრაქტიკაში მოდელის რეალიზება ხშირად ხდება ხოლმე კლასის სახით, რომელსაც აქვს მონაცეთა დამუშავებისათვის განსაზღვრული მეთოდები.



მუშაობის კლასიკური სქემა

მომხმარებელმა გააკეთა მოთხოვნა საიტზე, მოთხოვნა გადაეცა საიტზე შესვლის ერთადერთ წერტილს (index.php, frontController), ამ წერტილში მოსულ მოთხოვნას ამუშავებს მარშრუტიზაციის კომპონენტი (Router), ერთგვარი გზამკვლევი მოთხოვნისათვის, როუტერმა უნდა განსაზღვროს თუ რომელმა კონტროლერმა უნდა დაამუშავოს მოთხოვნა



ვთქვათ ეს კონტროლერია NewsController, ეს კონტროლერი მოთხოვნიდან ღებულობს ინფორმაციას თუ რა სახის სიახლეების ნახვა უნდა მომხმარებელს, შემდეგ მიმართავს მოდელს საჭირო ინფორმაციის მისაღებად, პასუხის მიღების შემდეგ კონტროლერი იყენებს საჭირო წარმოდგენას პასუხის ბრაუზერში გამოსატანად, ასე იკვრება მთლიანი წრე.
4. ფრეიმვორკის სტრუქტურა

დირექტორიები

App დირექტორია

App დირექტორია მოიცავს ჩვენი აპლიკაციის ძირითად კოდს, მასშია მოთავსებული აპლიკაციის კლასები. ამ საქაღალდეს უფრო დაწვრილებით განვიხილავთ ოდნავ მოგვიანებით.

Bootstrap დირექტორია

ამ საქაღალდის დასახელებას საერთო არაფერი აქვს ფრეიმვორკ Bootstrap-თან :)) აქ თავმოყრილია ფაილები, რომლებიც ახორციელებენ ფრეიმვორკ Laravel-ის თავდაპირველ ჩატვირთვას და ახდენენ კლასების ავტოჩატვირთვას (ინგლ: Bootstrap - ახალი ვარიანტის შესაქმნელად სისტემის ან პროცესის არსებული ვარიანტის გამოყენება. Bootstrapping - თვითრეგულირება, თვითაწყობა, თვითჩატვირთვა, თვითუზრუნველყოფა).

ამ საქაღალდეში აგრეთვე მოთავსებულია კატალოგი cache, სადაც თავმოყრილი ფაილები გამოიყენება ფრეიმვორკის მუშაობის დროს მიმდინარე პროცესების ოპტიმიზაციისათვის (მაგ: მარშრუტიზაცია, ფაილთა ქეშირება)

Config დირექტორია

ეს საქაღალდე, როგორც მისივე დასახელებიდანაც ჩანს, შეიცავს აპლიკაციის კონფიგურაციულ ფაილებს. სასურველია მომხმარებელი გაეცნოს ამ ფაილებს და შეისწავლოს თუ რომელ მათგანში რა არის განსაზღვრული.

Database დირექტორია

ეს საქაღალდე, როგორც მისივე დასახელებიდანაც ჩანს, შეიცავს მონაცემთა ბაზასთან დაკავშირებულ ფაილებსა და საქაღალდეებს, აქ ხდება მბ-ს პარამეტრების განსაზღვრა (ჰოსტი, მომხმარებელი, პაროლი, მბ-ს სახელი და ა.შ).

Public დირექტორია

ეს დირექტორია შეიცავს index.php ფაილს, რომელიც არის ჩვენ აპლიკაციაში შესასვლელი წერტილი, დირექტორია აგრეთვე შეიცავს ისეთ ხელსაწყო-ატრიბუტებს როგორებიცაა სურათები, ჯავასკრიპტის ფაილები, CSS ფაილები...

Resources დირექტორია

ეს საქაღალდე შეიცავს წარმოდგენის ფაილებს (views), არაკომპილირებად ინსტრუმენტებს როგორებიცაა LESS, SASS ან JavaScript. აქვეა მოთავსებული თარგმანებთან დაკავშირებული ფაილებიც.

Routes დირექტორია

ეს საქაღალდე შეიცავს აპლიკაციის მარშრუტების განმსაზღვრელ ფაილებს, ნაგულისხმეობის პრინციპით Laravel-ში ჩართულია მარშრუტთა რამოდენიმე ფაილი :web.php, api.php, console.php და channels.php.

Storage დირექტორია

ეს დირექტორია შეიცავს კომპილირებულ Blade შაბლონებს, ფაილებზე დაფუძნებულ სესიებს, ქეშ-ფაილებს და ფრეიმვორკის ფარგლებში შექმნილ სხვა ფაილებს. საქაღალდე იყოფა app, framework და logs საქაღალდეებად.

Vendor დირექტორია

ეს დირექტორია შეიცავს Composer-ის დამოკიდებულებებს.

App დირექტორია დაწვრილებით

როგორც აღვნიშნეთ, აპლიკაციის ძირითადი ნაწილი განთავსებულია აქ. მას ავტომატურად ტვირთავს Composer-ი PSR-4 (PHP Standard Recommendation) ავტოჩატვირთვის სტანდარტით. ამ საქაღალდეს App დასახელება ენიჭება ნაგულისმეობის პრინციპით, სახელის გადარქმევა კი შესაძლებელია შემდეგი ბრძანებით php artisan app:name <name-of-your-application>

Console დირექტორია

აქ მოთავსებულია სამომხმარებლო Artisan ბრძანებები. ამ ბრძანებების გენერირება ხდება make:command ბრძანებით.

Exceptions დირექტორია

შეიცავს აპლიკაციაში დაფიქსირებულ გამონაკლისთა დამმუშავებლებს, თუ გვსურს რომ განვსაზღვროთ თუ როგორ დარეგისტრირდეს ან გამოისახოს კონკრეტული გამონაკლისი, შესაბამისად უნდა დავარედაქტიროთ, ამ საქაღალდეში არსებული Handler კლასი.

Http დირექტორია

შეიცავს კონტროლერებს, ფორმის მოთხოვნებს. აქვე თავსდება აპლიკაციისაკენ მიმართული მოთხოვნების დამუშავების თითქმის მთლიანი ლოგიკა.

Providers დირექტორია

ეს დირექტორია შეიცავს ჩვენი აპლიკაციის სერვის-პროვაიდერებს.

ზემოთ ჩამოთვლილ თითოეულ საქაღალდესა და დირექტორიას შევეხებით სწავლების პროცესში, კონკრეტული ამოცანების გადაჭრისას.
5. ფრეიმვორკის კონფიგურაციის გამართვა
ფრეიმვორკ Laravel-ს, ინსტალაციის შემდეგ თითქმის არ ჭირდება დამატებით რაიმე კონფიგურაციული პარამეტრების განსაზღვრა, თუმცა არსებობს გამონაკლისებიც, მაგალითად მონაცემთა ბაზასთან დასაკავშირებელი პარამეტრები. ასევე შეიძლება დაგვჭირდეს დროის სარტყელის მითითებაც და ა.შ. როგორც უკვე ვიცით, კორფიგურაციული პარამეტრების განსაზღვრა ხდება config დირექტორიაში არსებული ფაილების მეშვეობით.

პროექტის კეთების ეტაპი შესაძლებელია დავყოთ სამ ნაწილად:

  • პირველ ეტაპზე ვმუშაობთ ლოკალურ სივრცეში, ეს შეიძლება იყოს OpenServer, Virtualbox, XAMPP...
  • მეორე ეტაპზე ხდება პროექტის ტესტირება, სავარაუდო შეცდომების გასწორება...
  • მესამე ეტაპზე კი პროექტი იტვირთება უკვე რეალურ ჰოსტინგზე ანუ ეშვება სამუშაო გარემოში
შევთანხმდეთ, რომ სამივე ეტაპზე, კონფიგურაციული პარამეტრების სხვადასხვაგვარად განსაზღვრაა საჭირო , მაგალითად მბ-სთან დასაკავშირებელი პარამეტრები სულ სხვაა ლოკალურ სივრცეში და სულ სხვა იქნება რეალურ სერვერზე. ამიტომ იმ პარამეტრთა ჯგუფის მიხედვით, რომელთა შეცვლაც საჭიროა მუშაობის პროცესის ეტაპებიდან გამომდინარე, შეიძლება მოვახდინოთ კონფიგურაციული პარამეტრების ერთგვარი არეების, გარემოების ფორმირება. ამ გარემოში შემავალ პარამეტრებს ეწოდებათ გარემოს ცვლადები , რომლებიც, 'გასაღები=მნიშვნელობა' წყვილების სახით ინახება .env ფაილში (ინგ: Environment - გარემოცვა; გარემო). ამ ფაილის სტრუქტურა შემდეგნაირია :

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}"
            
  • APP_NAME - აპლიკაციის დასახელება.

  • APP_ENV - ანუ სამუშაო გარემო (Application Environment), აპლიკაციის კეთების პროცესის მიმდინარე ფაზა, ეტაპი, მასზეა დამოკიდებული ყველა სხვა პარამეტრის მნიშვნელობა.

  • APP_KEY - ამ პარამეტრში შენახულია შემთხვევითი სტრიქონი, რომელიც გამოიყენება ფრეიმვორკის მუშაობის პროცესში, სხვადასხვა მონაცემების დასაშიფრად. APP_KEY-ს მნიშვნელობა ცნობილია მხოლოდ ჩვენი აპლიკაციის შიგნით და არსად სხვაგან. შესაბამისად მხოლოდ ჩვენი აპლიკაციისთვისაა შესაძლებელი, ორმხრივი ალგორითმებისა და ამ გასაღების მეშვეობით დაშიფრული ინფორმაციის დეშიფრაცია.

    სად გამოიყენება APP_KEY ?

    გამოიყენება ფრეიმვორკის ნებისმიერ ადგილას სადაც საჭიროა მონაცემების შიფრაცია ორმხრივი ალგორითმების დახმარებით (მაგ: Sessions, CSRF tokens, Cookies)

    სად არ გამოიყენება APP_KEY ?

    არ გამოიყენება იმ ადგილას სადაც ხდება მონაცემების ჰეშირება ცალმხრივი ალგორითმების დახმარებით (მაგ: Passwords, password_reset_token)

  • APP_DEBUG - შეცდომების გამოტანის პარამეტრი, თუ მითითებულია მნიშვნელობა true მაშინ სისტემა გამოიტანს დაფიქსირებული შეცდომის შესახებ შეტყობინებას, ხოლო თუ მითითებულია false მაშინ არ გამოიტანს. პროექტის შექმნის ეტაპზე სასურველია ეს პარამეტრი იყოს ჩართული, ხოლო სამუშაო გარემოში კი - გამორთული.

  • DB განყოფილება - მოიცავს მონაცემთა ბაზასთან სამუშაოდ აუცილებელ პარამეტრებს.

  • CACHE_DRIVER - ქეშირების სისტემის პარამეტი.

  • SESSION_DRIVER - სესიებთან სამუშაო პარამეტრი.

  • REDIS განყოფილება - REDIS-თან სამუშაო პარამეტრები. (REDIS - remote dictionary server : «გასაღები — მნიშვნელობა» ფორმატის ინფორმაციის შესანახი საცავი, მონაცემთა ბაზის მართვის სისტემა ღია წყაროთი.)

  • MAIL განყოფილება -ელ_ფოსტასთან დაკავშირებული პარამეტრები.

მუშაობა გარემოს ცვლადებთან

გარემოს ცვლადების მნიშვნელობები მოქცეულია სუპერგლობალურ ცვლადში - $_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/, ვიხილავთ შემდეგ სურათს :



config საქაღალდე

app.php ფაილი

ეს ფაილი მოიცავს ფრეიმვორკის მუშაობისთვის აუცილებელ გლობალურ პარამეტრებს. თუ მას გავხსნით შევამჩნევთ, რომ ბრუნდება ასოციაციური მასივი, მისი პირველი გასაღები არის "name" '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, მასში შეტანილია ფასადების ანუ სისტემური კლასების ფსევდონიმები. თუ რა არის ფასადი და როგორ ვიმუშაოთ მასთან, ცოტა მოგვიანებით განვმარტავთ.

database.php ფაილი

ეს ფაილი გამოიყენება მონაცემთა ბაზასთან წვდომისათვის. ამ ფაილშიც ბრუნდება ასოციაციური მასივი, რომლის ერთ-ერთი გასაღები არის: 'default' => env('DB_CONNECTION', 'mysql') ანუ მონაცემთა ბაზის სამართავი ნაგულისმები სისტემა, შევამჩნიოთ რომ ამ გასაღების მნიშვნელობაც .env ფაილიდან მოდის env ფუნქციის მეშვეობით, მაგრამ იქ მონაცემთა ბაზის სამართავი სისტემის დასახელება განსაზღვრული ჯერჯერობით არ გვაქვს, ამიტომ ან უნდა განვსაზღროთ იგი .env ფაილში DB_CONNECTION=mysql ანდა არ განვსაზღვროთ და სისტემა გამოიყენებს ნაგულისმებ მნიშვნელობას, რომელიც env ფუნქციას მეორე პარამეტრად აქვს გადაცემული.

წვდომა კონფიგურაციულ პარამეტრებთან

გლობალური დამხმარე ფუნქციის - config-ის მეშვეობით, შესაძლებელია მივწვდეთ კონფიგურაციულ პარამეტრებს ჩვენი აპლიკაციის ნებისმიერი წერტილიდან. ეს ხდება 'კონფიგურაციული_ფაილის_დასახელება.პარამეტრი' სინტაქტსის მეშვეობით. შესაძლებელია ფუნქციას გადაეცეს ნაგულისმები მნიშვნელობაც იმ შემთხვევისათვის თუ შესაბამის ფაილში შესაბამისი კონფიგურაციული პარამეტრი ვერ მოიძებნება :
$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 Mode)

config/app.php ფაილში აღწერილი debug პარამეტრი განსაზღვრავს თუ რა სახით მიიღოს ინფორმაცია მომხმარებელმა რაიმე ხარვეზის ან შეცდომის დაფიქსირების მომენტში. ნაგულისმეობის პრინციპით ამ პარამეტრის მნიშვნელობა ასეა აღწერილი : 'debug' => (bool) env('APP_DEBUG', false),
სატესტო გარემოში ჩვენივე კომფორტისათვის ჯობია, რომ ამ პარამეტრის მნიშვნელობა იყოს true ანუ შეცდომების აღმოფხვრის რეჟიმი იყოს ჩართული. რეალურ გარემოში მისი ჩართვა კი დაუშვებელია, რადგან შეცდომის შესახებ ინფორმაციის გამოტანისას ეკრანზე გამოჩნდება ისეთი კონფიგურაციული პარამეტრების მნიშვნელობები, რომელთა სხვებისათვის ჩვენებაც დაუშვებელია.
6. არქიტექტორული კონცეფციები

მოთხოვნის დამუშავების სქემა

მომხმარებლის მიერ გაკეთებული ნებისმიერი მოთხოვნა მიემართება პროექტის ერთადერთი გლობალური შესავალი წერტილისაკენ - public/index.php ფაილისაკენ, ამის შემდეგ ეს ფაილი ტვირთავს Composer-ის მიერ შექმნილ კლასთა ავტოჩამტვირთველ vendor/autoload.php ფაილს 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,
];

...
            

ამ მასივში აღწერილია ის შუამავლები, რომლებიც აუცილებლად უნდა გაიაროს მომხმარებლის მიერ გაკეთებულმა მოთხოვნამ (შუამავლების შესახებ ვისაუბრებთ ოდნავ მოგვიანებით).

ავტოჩატვირთვისა და ყველა საჭირო პროცესის ინიციალიზაციის შემდეგ მოთხოვნა გადაეცემა მარშრუტიზატორს და ფრეიმვორკი ღებულობს გადაწყვეტილებას თუ რომელმა მარშრუტმა უნდა დაამუშავოს ესა თუ ის მოთხოვნა. მარშრუტმა მოთხოვნა შეიძლება გადასცეს კონკრეტულ კონტროლერს ან უბრალოდ ფუნქციას, თუ მოთხოვნა კონტროლერს გადაეცემა, იგი ჩაატარებს შესაბამის სამუშაოებს (ინფორმაციის გადამოწმება, ვალიდაცია და ა.შ) და თავის მხრივ მიმართავს მოდელს, თუ აუცილებელია მოდელი მიმართავს მონაცემთა ბაზას , ბაზა დაუბრუნებს მას პასუხს, მოდელი ამ პასუხს დაუბრუნებს კონტროლერს, კონტროლერი კი მიღებულ პასუხს გადასცემს წარმოდგენის შაბლონს, წარმოდგენის შაბლონი უზრუნველჰყოფს შედეგის ბრაუზერში გამოტანას. ხოლო თუ მარშრუტიზატორი მოთხოვნას არ გადასცემს კონტროლერს, არამედ გადასცემს ფუნქციას, მაშინ ეს ფუნქცია დაამუშავებს მოთხოვნას და შედეგს გადასცემს პირდაპირ წარმოდგენას.

სერვისების კონტეინერი

რა არის სერვისი ?

მარტივად თუ ვიტყვით სერვისის უკან მოიაზრება ფუნქციონალი რომელიც გვიმარტივებს ცხოვრებას :)) როდესაც Laravel-ს დავაინსტალირებთ ჩვენ უკვე გვექნება საკმაოდ ბევრი მზა ასეთი სერვისი: მონაცემთა ბაზასთან სამუშაო სერვისი, ფაილებთან სამუშაო სერვისი და ა.შ, მაგრამ შეიძლება გადავაწყდეთ ისეთი ამოცანებს, რომელთა გადასაჭრელი გზები, ფრეიმვორკის საბაზისო კომპლექტაციაში გათვალისწინებული არ იყოს, ანუ დაგვჭირდეს ჩვენი საკუთარი, სამომხმარებლო ფუნქციონალის ჩამატება სისტემაში. შეგვიძლია ამ ფუნქციონალსაც სერვისები ვუწოდოთ.

რას ნიშნავს დამოკიდებულებათა ინექცია (DI - Dependency injection) ?

ინგ: Injection - ინექცია; დანერგვა; შემოღება; ჩადება;

თუ კონკრეტული კლასის მუშაობისათვის საჭიროა, გამოყენებულ იქნას სხვა კლასის ფუნქციონალი, ეს იმას ნიშნავს, რომ პირველი კლასი დამოკიდებულია მეორე კლასზე. ბუნებრივია უნდა მოვახდინოთ დამხმარე კლასის საწყის კლასში ინტეგრირება (Injection) და მხოლოდ ამის შემდეგ შეგვეძლება დამხმარე კლასის ფუნქციონალის გამოყენება. სწორედ ამ დამხმარე კლასს ეწოდება დამოკიდებულება (Dependency).
...

use Illuminate\Http\Request;

...

class SomeController extends Controller
{
    ...

    public function post(Request $request)
    {
        $request->validate([
            // ...
        ]);

        // ... 
    }

    ...
}
            
ამ მაგალითზე შეიძლება ითქვას, რომ SomeController კლასში მოვახდინეთ Request დამოკიდებულების ინექცია.

რა არის სერვისების კონტეინერი ?

სერვისების კონტეინერი (იგივე დამოკიდებულებათა ინექციების კონტეინერი) არის ერთგვარი სივრცე, გარემო, რომლის დახმარებითაც ხდება კლასებს შორის ურთიერთდამოკიდებულებების მართვა და ამ დამოკიდებულებების სხვადასხვა ადგილას გამოყენება. სერვისების კონტეინერთან წვდომა შესაძლებელია app() ჩანაწერით: კოპირება
Route::get('/', function(){
    
    echo '<pre>';
    print_r(app());
    echo '</pre>';
    die;    
    
});
            
თუ ახლა აპლიკაციის მთავარ გვერდზე შევალთ, ვიხილავთ ამდაგვარ სურათს :



როგორც ვხედავთ, სერვისების კონტეინერი არის Illuminate\Foundation\Application კლასის ეგზემპლიარი და ინახავს ინფორმაციას ხელმისაწვდომი ანუ დარეგისტრირებული სერვისების შესახებ. Illuminate\Foundation\Application კლასი კი, თავის მხრივ, არის Illuminate\Container\Container.php კლასის ანუ, ძირითადი კონტეინერული კლასის მემკვიდრე.

საკუთარი სერვისის შექმნა

შევქმნათ საქაღალდე app/Services და მასში კი სერვისი - MathService. სერვისში აღვწეროთ მარტივი მეთოდი, რომელიც დაგვიბრუნებს მასივის ელემენტების ჯამს : კოპირება
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 ეს იმას ნიშნავს, რომ სისტემამ ვერ იპოვა ის დამოკიდებულება, რომელიც მოვთხოვეთ. სწორედ ამ პრობლემის მოგვარებაში დაგვეხმარება სერვისისების კონტეინერი და სერვისის პროვაიდერი.

რა არის სერვისის პროვაიდერი ?

სერვისების პროვაიდერებს უჭირავთ ცენტრალური ადგილი Laravel-ის არქიტექტურულ სტრუქტურაში. მათი მეშვეობით ხდება აპლიკაციის საწყისი ჩატვირთვა, უფრო კონკრეტულად - პროვაიდერები ტვირთავენ საჭირო ელემენტებსა და სერვისებს, მოვლენათა დამმუშავებლებს (event listeners), შუამავლებს და ა.შ. თუ გავხსნით config/app.php ფაილს, ვნახავთ მასში აღწერილი მასივის providers ველს, სწორედ ამ ველში შეტანილი პროვაიდერების ჩატვირთვა ხდება აპლიკაციის გაშვებისას. ეს ჩანაწერები ამატებენ სხვადასხვა ფუნქციონალს სერვისების კონტეინერში.

ნებისმიერი პროვაიდერი არის Illuminate\Support\ServiceProvider კლასის მემკვიდრე და შეიცავს ორ მეთოდს : register და boot. როდესაც აპლიკაცია იტვირთება, სისტემა აკითხავს ყველა არსებული პროვაიდერის register მეთოდს და ასრულებს თითოეულ მათგანში აღწერილ ინსტრუქციებს. პროვაიდერის შექმნა შესაძლებელია შემდეგი ბრძანების მეშვეობით :

კოპირება
php artisan make:provider MathServiceProvider
            

register მეთოდი

პროვაიდერის register მეთოდში ხდება სერვისების რეგისტრაცია ანუ შენახვა სერვისების კონტეინერში, კონტეინერთან წვდომა შესაძლებელია app თვისების მეშვეობით, ხოლო უშუალოდ ინფორმაციის მიმაგრება სერვისების კონტეინერის bind მეთოდით : კოპირება
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) {
        //
    });
}

...
            

boot მეთოდი

იმ მომენტში, როდესაც მოხდება წარმოდგენის ფაილების გენერირება და ჩატვირთვა, შესაძლებელია დაგვჭირდეს რაიმე ფუნქციონალის შესრულება (მაგალითად წარმოდგენის შაბლონს გადავცეთ რაიმე ინფორმაცია). ასეთი ოპერაციების აღწერა ხდება პროვაიდერის boot მეთოდში :
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 () {
            //
        });
    }
}
            
წარმოდგენის ფაილებზე უფრო დაწვრილებით ვისაუბრებთ მოგვიანებით.

ფასადები

ფასადი არის სტატიკური ინტერფეისი სერვისების კონტეინერში დარეგისტრირებული კლასებისათვის, რომლის მეშვეობითაც საკმაოდ მარტივად ხდება ამ კლასებთან წვდომა. Laravel-ის ფასადები განთავსებულია vendor/laravel/framework/src/Illuminate/Support/Facades საქაღალდეში, თითოეული მათგანი არის, ამავე საქაღალდეში არსებულ - Facade.php ფაილში აღწერილი აბსტრაქტული კლასის მემკვიდრე და ატარებს Illuminate\Support\Facades სახელსივრცეს (namespace).
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Route;

Route::get('/cache', function () {
    return Cache::get('key');
});
            

საკუთარი ფასადის შექმნა

პირველ რიგში გავამარტივოთ ჩვენი App/Services/MathService.php სერვის, იგი გადავაკეთოთ ამდაგვარად : კოპირება
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()
    {
        //
    }
}
            

მაგიური მეთოდები __call() და __callStatic

PHP ში __call() მეთოდი გამოიყენება, როდესაც მივმართავთ კლასის არარსებულ ან დაცულ მეთოდს, __callStatic() მეთოდი კი გამოიყენება მაშინ, როდესაც მივმართავთ კლასის არარსებულ ან დაცულ სტატიკურ მეთოდს. ორივე მათგანს გადაეცემა ორი პარამეტრი: მეთოდის დასახელება და არგუმენტები : 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' სტატიკურ კონტექსტში

***

მშობელ კლას - Facade-ში აღწრილია მაგიური მეთოდი __callStatic :
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
    }
}
            
7. მარშრუტები
მანამ სანამ ფრეიმვორკ Laravel-ის მარშრუტიზატორების შესწავლაზე გადავალთ, გავარკვიოთ თუ რა არის URI, URL და URN

URI

იშიფრება როგორც Uniform Resource Identifier, ანუ მუდმივი წყაროს იდენტიფიკატორი, წყაროში იგულისხმება ნებისმიერი რესურსი, რომელიც შეიძლება მოითხოვოს მომხმარებელმა: საიტის რომელიმე გვერდი, სურათი, css სტილი და ა.შ. კონკრეტული მაგალითი: http://vnadiradze.ge/info/laravel/index.html URI შედგება ორი ნაწილისაგან : URL და URN.

URL

იშიფრება როგორც Uniform Resource Locator ანუ მუდმივი წყაროს მაჩვენებელი, ლოკატორი. იგი მოიცავს წყაროსთან მიმართვის ტიპს (პროტოკოლს) და წყაროს მდებარეობას ანუ დომენს. კონკრეტული მაგალითი: http://vnadiradze.ge

URN

იშიფრება როგორც Uniform Resource Name ანუ მუდმივი წყაროს სახელი. იგი განსაზღვრავს წყაროს დასახელებას. კონკრეტული მაგალითი: /info/laravel/index.html

მარშრუტის შექმნა

HTTP GET

როგორც ვიცით მარშრუტები ინახება routes/web.php ფაილში. იმისათვის რათა შევქმნათ მარშრუტი პირველ რიგში უნდა მივმართოთ შესაბამის ფასადს - Route, თუ რა არის ფასადი, ამის შესახებ დაწვრილებით ოდნავ მოგვიანებით ვისაუბრებთ, ამ ეტაპზე კი შემოვიფარგლოთ შემდეგი განმარტებით : ფასადი არის კლასი, რომელიც ანხორციელებს წვდომას ფრეიმვორკ Laravel-ის ელემენტებთან. კონკრეტულ ფასადთან მიმართვისას ჩვენ რეალურად მივმართავთ config/app საქაღალდეში აღწერილი მასივის ბოლო ელემენტ aliases-სში მოთავსებული ფასადების ფსევდოკლასებიდან ერთ-ერთს. Route ფასადის შემთხვევაში ეს კლასი არის : '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');
});
            

HTTP POST

POST მეთოდით მოთხოვნის გასაგზავნად public საქაღალდეში დავამატოთ ფაილი form.html რომელშიც შევიტანთ რაიმე მარტივ ფორმას კოპირება
<!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 მეთოდი, მეთოდს პირველ პარამეტრად გადეცემა მასივი, რომელშიც შეტანილია მოთხოვნათა სასურველი ტიპები : კოპირება
Route::match(['get','post'] , '/comments' , function(){
    print_r($_POST);
});
             
ამ შემთხვევაში უკვე შეგვეძლება ფორმის გაგზავნის გარეშე http://127.0.0.1:8000/comments გვერდზე შესვლა.

მარშრუტის შექმნა ნებისმიერი ტიპის მოთხოვნისათვის

იმისათვის რათა მარშრუტი შეესაბამებოდეს ნებისმიერი ტიპის მოთხოვნას ერთდროულად, უნდა გამოვიყენოთ Route ფასადის any მეთოდი კოპირება
Route::any('/comments' , function(){
    print_r($_POST);
});
            

მარშრუტისა და კონტროლერის დაკავშირება

მიუხედავად იმისა, რომ ჯერ არ ვიცნობთ კონტროლერებს, ოდნავ გავუსწოთ მოვლენებს და მოვიყვანოთ მაგალითი, თუ როგორ ხდება ამა თუ იმ მარშრუტთან კონკრეტული კონტროლერის დაკავშირება. პირველ რიგში routes/web.php ფაილში უნდა დავამყაროთ წვდომა შესაბამის კონტროლერთან, შემდეგ მივმართოთ სასურველ კონტროლერს და ამ კონტროლერის სასურველ მეთოდს : კოპირება
use Illuminate\Support\Facades\Route;

use App\Http\Controllers\UserController;

Route::get('/user', [UserController::class, 'index']);
            

მარშრუტთა გადამისამართება

თუ გვჭირდება ისეთი მარშრუტის შექმნა, რომელიც სხვა URI-ზე უნდა გადამისამართდეს, მაშინ შეგვიძლია გამოვიყენოთ Route ფასადის redirect მეთოდი : კოპირება
Route::redirect('/here', '/there');
            

წარმოდგენის მარშრუტები

თუ გვჭირდება ისეთი მარშრუტის შექმნა, რომელსაც უბრალოდ წარმოდგენის ფაილის დაბრუნება ევალება, მაშინ შეგვიძლია გამოვიყენოთ Route ფასადის view მეთოდი, რომელსაც პირველ პარამეტრად გადაეცემა URI ფრაგმენტი, მეორე პარამეტრად კი წარმოდგენის ფაილის დასახელება: კოპირება
Route::view('/welcome', 'welcome');
            
ამის შემდეგ http://127.0.0.1:8000/welcome მისამართზე შესვლისას ჩაიტვირთება resources/views/welcome.blade.php ფაილი.

მარშრუტთა პარამეტრები

სავალდებულო პარამეტრები

აქამდე განხილულ მაგალითებში მარშრუტებს პირველ პარამეტრად გადავცემდით URI-ს კონკრეტულ ნაწილს, სტატიკურ სტრიქონს, მაგრამ შეიძლება დაგვჭირდეს ცვალებადი, დინამიური პარამეტრების გადაცემაც (მაგალითად კონკრეტული მომხმარებლის id-ის). ცვლადი პარამეტრის გადაცემის სინტაქსი ასეთია: კოპირება
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

არასავალდებულო პარამეტრები

ამ მაგალითებში მარშრუტს ვუთითებდით ისეთ პარამეტრებს, რომელთა განსაზღვრაც აუცილებელი იყო, მაგრამ შეიძლება განისაზღვროს მარშრუტი ისეთი მოთხოვნისათვის რომელიც შეიძლება შეიცავდეს ან არ შეიცავდეს პარამეტრს, ასეთ შემთხვევაში პარამეტრის მითითებისას მას ეწერება კითხვის ნიშანი, ხოლო ფუნქცია დამმუშავებელს კი ეს პარამეტრი გადეცემა ნაგულისმები მნიშვნელობით - null კოპირება
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 გვერდზეც.

მეთოდი where

შეიძლება მოხდეს ისე, რომ დაგვჭირდეს ცვლადი პარამეტრის ტიპის გაფილტვრა, მაგალითად id პარამეტრის მნიშვნელობა უნდა იყოს მხოლოდ და მხოლოდ რიცხვითი ტიპის, ამაში დაგვეხმარება რეგულარული გამოსახულებები და where მეთოდი, მისი გამოყენების სინტაქსი ასეთია: კოპირება
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]+']);
            

მარშრუტის პარამეტრის ტიპის განსაზღვრა გლობალურად

შეიძლება მოხდეს ისე, რომ id პარამეტრის გაფილტვრა, ანუ ტიპის განსაზღვრა დაგვჭირდეს 100 მარშრუტისათვის, 100-ჯერ ერთი და იგივეს წერა, რა თქმა უნდა მოუხერხებელი და არაკომფორტულია. ასეთ შემთხვევაში დაგვეხმარება სერვის-პროვაიდერი ანუ კლასი app/Providers/RouteServiceProvider.php, კონკრეტულად კი მისი მეთოდი boot, ფრეიმვორკის ინსტალაციის პირველ ეტაპზე ეს მეთოდი გამოიყურება ასე:
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;
});
            

მარშრუტთა ჯგუფები

პროექტზე მუშაობისას შესაძლებელია მოხდეს ისე, რომ ბევრ URI-ს გააჩნდეს საერთო ფრაზა, საერთო სეგმენტი, მაგალითად : 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';
    });

});            
            

სახელდებული მარშრუტები, გადამისამართება გვერდებზე, შიდა ბმულები

ხშირადაა საჭირო პროექტის ერთი გვერდიდან მეორეზე გადასასვლელი ბმულების გაკეთება ან ავტომატურად გადასვლა (redirect). შიდა ბმულების შექმნისას საკმაოდ ხელსაყრელია მარშრუტებზე სახელის დარქმევა და შემდეგ ამ სახელების გამოყენება. კოპირება
Route::get('/user/profile', function () {
    //
})->name('profile');
            
სახელის დარქმევა შესაძლებელია ასეც : Route::get('/user/profile', [UserProfileController::class, 'show'])->name('profile');
მარშრუტთა დასახელებები აუცილებლად უნდა იყოს უნიკალური.
სახელდებული მარშრუტის მიხედვით URL-ის გენერირება და გადამისამართება მოხდბა ასე: კოპირება
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
            

მიმდინარე მარშრუტის გადამოწმება

თუ გვსურს დავადგინოთ მიმდინარე მარშრუტი მიემართა თუ არა კონკრეტული სახელდებული მარშრუტისაკენ, შეგვიძლია გამოვიყენოთ route დამხმარის named მეთოდი. მაგალითად გვაქვს მარშრუტთა შუამავალი და მასში გვინდა მარშრუტის სახელის გადამოწმება : კოპირება
/**
 * 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 მეთოდი : კოპირება
Route::middleware(['first', 'second'])->group(function () {

    Route::get('/', function () {
        // გაივლის first & second შუამავლებს...
    });

    Route::get('/user/profile', function () {
        // გაივლის first & second შუამავლებს...
    });

});
            

სადაზღვევო მარშრუტები :))

როდესაც შევდივართ ისეთ ბმულზე, რომლის შესაბამისი მარშრუტიც არ არის განსაზღვრული, აპლიკაციის გამონაკლისთა დამმუშავებელს ავტომატურად გადავყავართ 404 გვერდზე. შეგვიძლია შევქმნათ მარშრუტი, რომელში აღწერილი ფუნქციაც შესრულდება ყველა უმარშრუტო მისამართზე შესვლისას და აღარ მოხდება 404 გვერდზე გადამისამართება, ამაში დაგვეხმარება Route ფასადის fallback მეთოდი: კოპირება
Route::fallback(function () {
    echo "გვერდი ვერ მოიძებნა";
});
            

მიმდინარე მარშრუტთან წვდომა

Route ფასადის current, currentRouteName და currentRouteAction მეთოდების დახმარებით შესაძლებელია მივიღოთ სხვადასხვა ინფორმაციები, მიმდინარე მოთხოვნის დამმუშავებელი მარშრუტის შესახებ : კოპირება
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 საქაღალდეში.
გვახსოვდეს, რომ თუ გაკეთებული გვაქვს მარშრუტთა ქეშირება და ამ დროს შევქმნით სხვა ახალი მარშუტს, მაშინ უნდა მოვახდინოთ მარშრუტთა ქეშის გასუფთავება და შემდეგ ხელახლა შევქმნათ იგი.
ქეშირების გასუფთავება შესაძლებელია Artisan-ის route:clear ბრძანებით : php artisan route:clear
8. შუამავლები




HTTP middleware ანუ შუამავალი კლასი არის HTTP მოთხოვნის დამამუშავებელი ფილტრი. მაგალითად Laravel-ში განხილულია შუამავალი კლასები, რომლებიც ამოწმებენ გავლილი აქვს თუ არა მომხმარებელს ავტორიზაცია. თუ კონკრეტულ მარშრუტთან დაკავშირებულია შესაბამისი შუამავალი კლასი და მომხმარებელს არ გაუვლია ავტორიზაცია ისე აკეთებს HTTP მოთხოვნას, მაშინ შუამავალი კლასი მას გადაამისამართებს ავტორიზაციის გვერდზე. თუ მარშრუტთან არ არის დაკავშირებული, მაშინ შუამავალი კლასი საქმეში არ ერთვება.

შუამავალი კლასის შექმნა

შუამავალი კლასის შექმნის სინტაქსი ასეთია : 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
];
            

გლობალური შუამავლები

თუ გვსურს, რომ შუამავალი გავრცელდეს ჩვენს აპლიკაციაში გაკეთებულ ყველა HTTP მოთხოვნაზე, მაშინ იგი უნდა აღვწეროთ app/Http/Kernel.php კლასის $middleware თვისებაში.

შუამავალი კლასის მიმაგრება მარშრუტებზე

ჩვენი შუალედური კლასის ფსევდონიმია checkIP, მივამაგროთ იგი მარშრუტს : კოპირება
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 ფაილში კლასის სრული დასახელების გამოყენებითაცაა შესაძლებელი შუამავლების მიმაგრება მარშრუტებზე.
withoutMiddleware მეთოდი ვერ აუქმებს გლობალურ შუამავლებს, იგი ეხება მარტო app/Http/Kernel.php კლასის $routeMiddleware თვისებაში აღწერილ შუამავლებს.

შუამავალთა ჯგუფები

ზოგჯერ ხელსაყრელია, რომ რამოდენიმე შუამავალი გაერთიანდეს ერთი გასაღების ქვეშ, ანუ მოხდეს მათი დაჯგუფება. გასაღების მეშვეობით შედარებით მარტივად მოხდება შუამავალთა გამოყენება სხვადასხვა ადგილას. ეს ჯგუფები აღწერილია app/Http/Kernel.php კლასის $middlewareGroups თვისებაში.

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'));
    });
}
            
9. კონტროლერები
ამ ეტაპზე ჩვენ უკვე ვიცით მარშრუტის შექმნა და მის დამმუშავებელ ფუნქციასთან მუშაობა, მაგრამ შევთანხდეთ, რომ თუ დამმუშავებლის კოდი საკმაოდ დიდია და მარშრუტებიც ბევრი გვექნება, ამ ყველაფრის ერთ ფაილში წერა მოუხერხებელი იქნება და საჭირო გახდება მარშრუტთა დამმუშავებლების ცალკე ფაილებში გატანა. ამაში დაგვეხმარება კონტროლერები.

კონტროლერები ინახება app/Http/Controllers საქაღალდეში. ნაგულისხმეობის პრინციპით ამ საქაღალდეში უკვე შექმნილია ერთი საბაზისო კონტროლერი Controller.php სწორედ მისი მემკვიდრეები უნდა იყვნენ ის კონტროლერები, რომლებსაც მომავალში შევქმნით.

კონტროლერის შექმნა

კონტროლერის დასახელება უნდა ემთხვეოდეს იმ კლასის დასახელებას, რომელსაც ამ კონტროლერში აღვწერთ. შევქმნათ კონტროლერი UserController.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 მისამართზე.

კონტროლერის შექმნა artisan ბრძანებით

კონტროლერების ხელით შექმნა ცოტა მოუხერხებელია, ამის გაკეთება გაცილებიდ მარტივია Laravel-ის კონსოლის - artisan-ის მეშვეობით. ბრძანებათა ველიდან გადავდივართ ჩვენი პროექტის საქაღალდეში, კონტროლერის შექმნის ბრძანების სინტაქსი კი ასეთია : 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');
    }
}
            

რესურსების კონტროლერები

წარმოვიდგინოთ, რომ აპლიკაციაში გვაქვს Photo და Movie მოდელები, რომელთა მეშვეობითაც შესაძლებელია რესურსების (ფოტო, ფილმი) შექმნა, წაკითხვა, რედაქტირება და წაშლა. ასეთ შემთხვევებში კონტროლერებს აქვთ ხოლმე, ოთხი ძირითადი მეთოდი : create, read, update, delete (CRUD). ნაცვლად იმისა, რომ სათითაო მეთოდისათვის, სათითაო ფუნქცია ვწეროთ ხელით კონტროლერში, შეგვიძლია კონტროლერის შექმნის ბრძანება გავუშვათ --resource ჩანაწერთან ერთად : 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

დამოკიდებულების ინექცია (Dependency Injection) და კონტროლერები

რა არის დამოკიდებულების ინექცია

ინგ: 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)
    {
        //
    }
}
            

მარშრუტთა დაჯგუფებები კონტროლერის მიხედვით

დავუშვათ გვაქვს პოსტებთან სამუშო კონტროლერი - PostsController, როგორც ვიცით, ლარაველის მე-8-ე ვერსიაში, შესაბამისი მარშრუტების დაგენერირება მოხდებოდა ასე : კოპირება
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'); // ახალი პოსტის დამატება
});
            
როგორც ვხედავთ, მარშრუტებისათვის, სათითაოდ ცალ-ცალკე კონტროლერის განსაზღვრა აღარ გვჭირდება და უბრალოდ კონტროლერის მეთოდების დასახელებებს ვუთითებთ.
10. მოთხოვნის მიღება და დამუშავება, კლასი Request
აპლიკაციის გახსნის შემდეგ პირველი რაც ხდება ისაა, რომ სისტემა ღებულობს ამა თუ იმ შიგთავსის გამოტანის მოთხოვნას მომხმარებლისაგან. ამ თავში ვისაუბრებთ Illuminate\Http\Request კლასის შესახებ, რომლის მეშვეობითაც ხდება ნებისმიერი მოთხოვნის დამუშავება.

იმისათვის რათა მივიღოთ მიმდინარე 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) {
    //
});
            

Request კლასის მეთოდები

path() მეთოდი

ეს მეთოდი აბრუნებს URI-ს იმ ნაწილს, რომელიც შეიცავს მომხმარებლის მიერ გაკეთებული მოთხოვნის გზას, მაგალითად თუ URI არის http://127.0.0.1:8000/foo/bar, მეთოდი დააბრუნებს foo/bar - ს : $uri = $request->path(); // foo/bar

is() მეთოდი

ეს მეთოდი აბრუნებს მნიშვნელობას - true, თუ მომხმარებლის მიერ გაკეთებული მოთხოვნის შესაბამისი გზა (ანუ URI-ს ფრაგმენტი) ემთხვევა იმ შაბლონს, რომელსაც პარამეტრად გადავცემთ მეთოდს.
if ($request->is('foo/*')) 
{
    //
}
            

routeIs() მეთოდი

ეს მეთოდი აბრუნებს მნიშვნელობას - true, თუ მომხმარებლის მიერ გაკეთებული მოთხოვნა ემთხვევა, რომელიმე სახელდებულ მარშრუტს :
if ($request->routeIs('index')) 
{
    //
}
            

url(), fullUrl() და fullUrlWithQuery() მეთოდები

პირველი მათგარი აბრუნებს მიმდინარე მისამართს GET პარამეტრების გარეშე, ხოლო მეორე მათგანი - GET პარამეტრებთან ერთად :
// 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() და isMethod() მეთოდები

ეს მეთოდი აბრუნებს მომხმარებლის მიერ გაკეთებული მოთხოვნის ტიპს სტრიქონული სახით (get, post, ...). isMethod() მეთოდი აბრუნებს მნიშვნელობას - true, თუ გაკეებული მოთხოვნის ტიპი ემთხვევა არგუმენტად გადაცემული სტრიქონის მნიშვნელობას :
$method = $request->method();

if ($request->isMethod('get')) 
{
    // 
}
            

ip() მეთოდი

ეს მეთოდი აბრუნებს იმ მომხმარებლის IP მისამართს, რომელმაც მოთხოვნა გააკეთა ჩვენს აპლიკაციაში : $ipAddress = $request->ip();

all() მეთოდი

ეს მეთოდი აბრუნებს ასოციაციურ მასივს, რომლის ინდექსებიცაა html ფორმის ველთა დასახელებები, ხოლო ამ ინდექსების მნიშვნელობები კი ველებში შეყვანილი ინფორმაციებია, მაგალითად თუ გავგზავნით (submit) ასეთი ველების მქონე ფორმას : <input type="text" name="name">
<input type="password" name="password">
public function login(Request $request)
{
    print_r($request->all());
}
            
შედეგად ვიხილავთ დაახლოებით შემდეგი სახის მასივს :
Array
(
    [name] => vaso
    [password] => pass123
)
            

input() მეთოდი

ეს მეთოდი გამოიყენება მოთხოვნიდან კონკრეტული ველის მნიშვნელობის ამოსაღებად, პირველ პარამეტრად მეთოდს უნდა გადავცეთ ველის დასახელება, მეორე პარამეტრად კი შეგვიძლია გადავცეთ ალტერნატიული მნიშვნელობა ისეთი ველისათვის, რომელიც მოთხოვნაში არ იქნება:
// 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 მოთხოვნებთან.

query() მეთოდი

ეს მეთოდი მუშაობს მხოლოდ GET ტიპის მოთხოვნებთან, შეგვიძლია მასაც გადაცხეთ ალტერნატიული მნიშვნელობა არარსებული ველისათვის :
$name = $request->query('name', 'Helen');
            
თუ მეთოდს საერთოდ არ გადავცემთ პარამეტრებს, მაშინ იგი დაგვიბრუნებს GET ტიპის ყველა პარამეტრს.

boolean() მეთოდი

როდესაც ვამუშავებთ ისეთ HTML ელემენტებს, როგორიცაა მაგალითად 'checkbox', შესაძლებელია, რომ აპლიკაციამ მათი მონიშნულობის (მოპწიჩკულობის :) ) აღმნიშვნელად მიიღოს სტრიქონული ტიპის ტექსტები, მაგალითად : 'true' ან 'on'. სწორედ ასეთ ელემენტებთან სამუშაოდ გამოიყენება boolean მეთოდი. იგი აბრუნებს ჭეშმარიტ მნიშვნელობას (true), 'checkbox' ტიპის ველის 'name' ატრიბუტის შემდეგი მნიშვნელობებისათვის : 1, '1', true, 'true', 'on', და 'yes', ყველა სხვა შემთხვევაში ბრუნდება მნიშვნელობა false. $archived = $request->boolean('archived');

ველის ამოღება დინამიური მეთოდების დახმარებით

შესაძლებელია, რომ მოთხოვნის ველები განვიხილოთ როგორც Illuminate\Http\Request კლასის ობიექტის თვისებები და ისე მივწვდეთ მათ. მაგალითად თუ აპლიკაციაში გვაქვს ფორმა, რომლის ერთ-ერთი ველის დასახელებაცაა 'name', ამ ველის მნიშვნელობა შეიძლება გავიოთ ასე : $name = $request->name; დინამიური მეთოდის გამოყენებისას ფრეიმვორკი პირველ რიგში გადაამოწმებს მოთხოვნაში ჩადებულ ველებს, თუ აქ ვერ მოიძებნა შესაბამისი ველი, მაშინ გადაამოწმებს მარშრუტის პარამეტრებს.

only() მეთოდი

ეს მეთოდი მუშაობს all მეთოდის ანალოგიურად, იმ განსხვავებით, რომ თუ ეს უკანასკნელი აბრუნებს გაგზავნილი ინფორმაციის შემცველი მასივის ყველა ინდექსსა და მათ მნიშვნელობებს, only მეთოდი აბრუნებს მხოლოდ იმ ინდექსებს, რომლებსაც პარამეტრებად გადავცემთ :
$input = $request->only(['username', 'password']);

$input = $request->only('username', 'password');
            
მეთოდს პარამეტრად უნდა გადაეცეს შესაბამისი ველის დასახელება.

except() მეთოდი

ეს მეთოდი არის all და only მეთოდების ერთფგვარი შებრუნებული მეთოდი :)) იგი აბრუნებს გაგზავნილი ინფორმაციის შემცველ მასივს იმ ინდექსების გამოკლებით, რომლებსაც პარამეტრებად გადავცემთ :
$input = $request->except(['credit_card']);

$input = $request->except('credit_card');
            
მეთოდს პარამეტრად უნდა გადაეცეს შესაბამისი ველის დასახელება (name ატრიბუტის მნიშვნელობა)

has() მეთოდი

მეთოდი აბრუნებს მნიშვნელობას - true თუ მოთხოვნაში ჩადებული ინფორმაციის შემცველი მასივი შეიცავს იმ ინდექსს, რომელსაც პარამეტრად გადავცემთ მეთოდს, წინააღმდეგ შემთხვევაში მეთოდი აბრუნებს მნიშვნელობას false.
if ($request->has('name')) 
{
    //
}
            

filled() მეთოდი

მეთოდი აბრუნებს მნიშვნელობას - true თუ მოთხოვნაში ჩადებული ინფორმაციის შემცველი მასივი შეიცავს იმ ინდექსს, რომელსაც პარამეტრად გადავცემთ მეთოდს და ამასთანავე ველის მნიშვნელობა არ იქნება ცარიელი, წინააღმდეგ შემთხვევაში მეთოდი აბრუნებს მნიშვნელობას false.
if ($request->filled('name')) 
{
    //
}
            

flash() მეთოდი

შესაძლებელია საჭირო გახდეს მომხმარებლის მიერ აკრეფილი ინფორმაციის გადამოწმება, ვალიდაცია. თუ ეს ინფორმაცია არ აკმაყოფილებს ვალიდაციის პირობებს და თუ ფორმაც მარტივია და შედგება რამოდენიმე ველისაგან, მომხმარებელს უბრალოდ გადავამისამართებთ ისევ ფორმის გვერდზე, მაგრამ თუ ფორმა რთულია და შეიცავს ძალიან ბევრ ველებს, მაშინ მომხმარებელს ამ ველების თავიდან შევსება მოუწევს, რაც არც თუ ისე მოსახერხებელია. ამ პრობლემის გადასაწყვეტად გამოიყენება request ობიექტის flash() მეთოდი, რომელიც მოთხოვნის ტანში ჩადებულ ინფორმაციას ინახავს სესიაში. $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') }}">
            
ამის შემდეგ თუ ფორმას გავაგზავნით ვნახავთ, რომ აკრეფილი ინფორმაცია არ დაიკარგება და ველები ავტომატურად შეივსება.

flashOnly() მეთოდი

ეს მეთოდი სესიაში ინახავს მხოლოდ იმ ველების მნიშვნელობებს, რომლებსაც გადავცემთ პარამეტრებად : $request->flashOnly(['username', 'email']);

flashExcept() მეთოდი

ეს მეთოდი სესიაში ინახავს ყველა ველის მნიშვნელობას გარდა იმ ველებისა, რომლებსაც გადავცემთ პარამეტრებად : $request->flashExcept('password');
11. პასუხი სერვერიდან, კლასი Response
წინა თავში განვიხილეთ თუ როგორ ხდება მოთხოვნის მიღება და დამუშავება. მოთხოვნის გაშვების შემდეგ, ბუნებრივია სერვერიდან ბრუნდება პასუხი. ამ თავში განვიხილავთ თუ როგორ აკეთებს Laravel-ი ამას და რა საშუალებები არსებობს პასუხის რეალიზებისათვის.

კონკრეტულ გვერდზე რაიმე ინფორმაციის გამოტანის, ანუ ერთგვარი პასუხის დაბრუნების უმარტივესი გზა არის კონტროლერში ან მარშრუტის დამმუშავებელში ტექსტის დაბრუნება :

public function index()
{
    return 'Hello World';
}
            
Route::get('/', function () {
    return 'Hello World';
});
            
ასევე შესაძლებელია მასივის დაბრუნებაც, ფრეიმვორკი ავტომატურად გადააფორმატებს მას JSON ფორმატში :
Route::get('/', function () {
    return [1, 2, 3];
});
            

პასუხის ობიექტი

მოთხოვნათა პასუხების აბსტრაქცია ანუ განზოგადებული სახე არის Response კლასი, რომელიც აღწერილია vendor/laravel/framework/src/Illuminate/http/Response.php ფაილში. ამ კლასის მეთოდ-კონსტრუქტორს თუ დავაკვირდებით, გავიგებთ რა პარამეტრები შეგვიძლია გადავცეთ პასუხებთან სამუშაო დამხმარე ფუნქცია response-ს :
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');
});
            

Cookie-ს მიმაგრება პასუხზე

პასუხზე Cookie-ს მიმაგრება შესაძლებელია cookie მეთოდით, მეთოდს უნდა გადავეთ სამი პარამეტრი : დასახელება, მნიშვნელობა და Cookie-ს ქმედითუნარიანობის ხანგრძლივობა წუთებში : return response('Hello World')->cookie('name', 'value', $minutes);

Cookie-ს წაშლა პასუხიდან

პასუხიდან Cookie-ს წასაშლელად გამოიყენება withoutCookie მეთოდი, რომელსაც პარამეტრად უნდა გადავცეთ Cookie-ს დასახელება : return response('Hello World')->withoutCookie('name');

Cookie-ბი და შიფრაცია

ნაგულისხმეობის პრინციპით Laravel-ი ახდენს Cookie-ბის შიფრაციას, შესაბამისად შენახული ინფორმაცია მომხმარებლისათვის არ არის ხელმისაწვდომი. თუ გვსურს, რომ არ მოხდეს რომელიღაც კონკრეტული Cookie-ს შიფრაცია, მაშინ უნდა გამოვიყენოთ App\Http\Middleware\EncryptCookies შუამავლის $except მეთოდი :
/**
 * იმ cookie-ბის დასახელებები, რომელთა შიფრაციაც არ მოხდება
 *
 * @var array
 */
protected $except = [
    'cookie_name',
];
            

გადამისამართებები

რომელიმე გვერდზე გადასამისამართებლად გამოიყენება Illuminate\Http\RedirectResponse კლასის ობიექტები,არსებობს ამ ობიექტებთან წვდომის რამოდენიმე ვარიანტი, ერთ-ერთია დამხმარე ფუნქცუა redirect, რომელსაც პარამეტრად უნდა გადავცეთ ბმული სადაც გვსურს გადამისამართება :
Route::get('/dashboard', function () {
    return redirect('home/dashboard');
});
            
ზოგჯერ საჭიროა, რომ მომხმარებელი გადავამისამართოთ წინა გვერდზე (მაგალითად გაგზავნა ფორმა არავალიდური ინფორმაციებით), ანუ დავაბრუნოთ უკან, ამისათვის გამოიყენება დამხმარე ფუნქცია back. ფუნქცია იყენებს სესიებს, ამიტომ დარწმუნებულები უნდა ვიყოთ, რომ მარშრუტი, რომლის დამმუშავებელშიც back ფუნქციას ვიძახებთ, მოქცეულია web შუამავალში (როგორც ვიცით სწორედ ეს შუამავალი ახდენს, სესიების მიმაგრებას მარშრუტებთან) :
Route::post('/user/profile', function () {
    
    // მოთხოვნის ვალიდაცია...

    return back()->withInput();

});
            

გადამისამართება სახელდებულ მარშრუტებზე

სახელდებულ მარშრუტზე გადასამისამართებლად, გამოიყენება redirect ფუნქციის route მეთოდი, რომელსაც პარამეტრად უნდა გადავცეთ მარშრუტის დასახელება : return redirect()->route('login'); თუ მარშრუტს აქვს პარამეტრები, მათი გადაცემა შეგვიძლია მეთოდის მეორე არგუმენტად :
// მარშრუტი შემდეგი URI-სათვის : /profile/{id}

return redirect()->route('profile', ['id' => 1]);
            

გადამისამართება გარე ბმულებზე

ხანდახან საჭიროა, რომ გადამისამართება მოვახდინოთ ისეთ ბმულზე, რომელიც მდებარეობს ჩვენი აპლიკაციის გარეთ. ასეთ შემთხვევაში უნდა გამოვიყენოთ redirect ფუნქციის away მეთოდი, რომელსაც პარამეტრად გადაეცემა სასურველი ბმული : return redirect()->away('https://www.google.com');

გადამისამართება სესიის ინფორმაციასთან ერთად

გადამისამართებისას შეიძლება დაგვჭირდეს სესიაში რაიმე ინფორმაციის შენახვა და შემდეგ ამ ინფორმაციის იმ გვერდზე გამოყენება, სადაც გადამისამართებას ვაკეთებთ. ამ შემთხვევაში გამოიყენება redirect ფუნქციის with მეთოდი, რომელსაც პარამეტრად გადაეცემა სესიის გასაღები და ამ გასაღების შესაბამისი მნიშვნელობა :
Route::post('/user/profile', function () {
    // ...

    return redirect('dashboard')->with('status', 'ინფორმაცია წარმატებით განახლდა !');
});
            
გადამისამართების შემდეგ, წარმოდგენის ფაილში ამ ინფორმაციის გამოყენების სინტაქსი იქნება ასეთი :
@if (session('status'))
    <div class="alert alert-success">
        {{ session('status') }}
    </div>
@endif
            

JSON პასუხები

response ფუნქციის json მეთოდი, პასუხის სათაურ (header) Content-Type-ის მნიშვნელობად ავტომატურად სვამს application/json-ს.
return response()->json([
    'name' => 'Abigail',
    'state' => 'CA',
]);
            

ფაილის გადმოწერა

თუ გვსურს, რომ დაბრუნებული პასუხის შედეგად მომხმარებლის ბრაუზერმა მოახდინოს კონკრეტული ფაილის გადმოწერა, უნდა გამოვიყენოთ response ფუნქციის download მეთოდი, რომელსაც პარამეტრად უნდა გადავცეთ სასურველი ფაილის მისამართი : return response()->download($pathToFile);
12. წარმოდგენის შაბლონები, ინფორმაციის მიმაგრება მათზე
რა თქმა უნდა საკმაოდ არაკომფორტულია HTML კოდების პირდაპირ მარშრუტებში და კონტროლერებში დაბრუნება. წარმოდგენა არის MVC შაბლონის ერთ-ერთი ელემენტი, რომელიც უზრუნველჰყოფს ინფორმაციის ბრაუზერში გამოტანას და მისი საშუალებით ასევე ხდება HTML შიგთავსების, ძირითადი ლოგიკისაგან გამოყოფა, განცალკევება. წარმოდგენის ფაილები ინახება resources/views საქაღალდეში. მოვიყვანოთ წარმოდგენის უმარტივესი მაგალითი :
<!-- 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' => 'შამილი']);
წარმოდგენის ფაილის დასახელება არ უნდა შეიცავდეს . სიმბოლოს (წერტილს)
ამ თავში ვისაუბრებთ წარმოდგენებზე, რომლებიც იმუშავებენ შაბლონიზატორ blade-ს გარეშე.

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);

with მეთოდი

ინფორმაციის მიმაგრება შესაძლებელია with მეთოდითაც : 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 მეთოდის დასახელება პრეფიქსად ერთვის ცვლადის დიდი ასოთი დაწყებულ სახელს, შემდეგ კი ფრხილებში ეთითება ცვლადის მნიშვნელობა.

compact მეთოდი

წარმოდგენის ფაილზე ინფორმაციის მიმაგრების კიდევ ერთი, არანაკლებ ეფექტური მეთოდია compact. ეს არის PHP-ს სტანდარტული ფუნქცია, რომელიც ცვლადთა დასახელებებისა და მათი მნიშვნელობებისაგან აკეთებს ასოციაციურ მასივს :
$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>
            

view()->exists()

როგორ გადავამოწმოთ კონტროლერში, არსებობს თუ არა წარმოდგენის ესა თუ ის ფაილი ? როგორც ვიცით view() ფუნქცია ქმნის გლობალური view კლასის ობიექტს, ამ ობიექტს გააჩნია მეთოდი exists(), რომელიც ამოწმებს არსებობს თუ არა წარმოდგენის კონკრეტული ფაილი. მეთოდს პარამეტრად გადეცემა წარმოდგენის ფაილის დასახელება და აბრუნებს true მნიშვნელობას თუ ეს ფაილი არსებობს. კონტროლერში შეგვიძლია ჩავწეროთ ასეთი რამ :
public function index()
{
    if (view()->exists('templates.template'))
    {
        return view('templates.template')->withTitle('Hello World');
    }
}            
            

ინფორმაციის გაზიარება წარმოდგენის ყველა ფაილისათვის

ინფორმაციის გაზიარება წარმოდგენის ყველა ფაილისათვის შესაძლებელია View ფასადის share მეთოდით. როგორც წესი, ეს გაზიარება აღიწერება ხოლმე სერვისპროვაიდერის boot მეთოდში :
namespace App\Providers;

use Illuminate\Support\Facades\View;

class AppServiceProvider extends ServiceProvider
{
    public function register()
    {
        //
    }
    
    public function boot()
    {
        View::share('key', 'value');
    }
}         
            

წარმოდგენის ფაილების ქეშირება

ნაგულისხმეობის პრინციპით ფრეიმვორკი ახდენს წარმოდგენათა ფაილების კომპილაციას. როდესაც სრულდება მოთხოვნა, რომლის შედეგადაც უნდა მოხდეს წარმოდგენის ჩატვირთვა, Laravel-ი ადგენს არსებობს თუ არა საჭირო წარმოდგენის კომპილირებული ვერსია, თუ ეს ვერსია არსებობს ამის შემდეგ სისტემა დაადგენს რომელი ვერსია უფრო გვიან შეიცვალა - კომპილირებული თუ არაკომპილირებული. თუ კომპილირებული ვერსია საერთოდ არ არსებობს ან არსებობს, მაგრამ არაკომპილირებულში მოხდა ცვლილებები მაშინ გაკეთდება წარმოდგენის ფაილის ხელახალი კომპილაცია.

შესაძლებელია, რომ ამ პროცესმა უარყოფითი გავლენა იქონიოს აპლიკაციის სისწრაფეზე, ასე რომ view:cache Arttisan ბრძანებით ეგვიძლია დავქეშოთ წარმოდგენის ფაილები :

php artisan view:cache ქეშირებული შაბლონები შეინახება storage/framework/views საქაღალდეში. ქეშის გასუფთავება კი მოხდება ასე : php artisan view:clear
13. შაბლონიზატორი Blade
ამ თავში ვისაუბრებთ შაბლონიზატორ blade-ზე, რომელიც ჩაშენებულია Laravel-ის ფუნქციონალში. შაბლონიზატორი არის სპეციალური მექანიზმი, რომელიც გამოიყენება შაბლონის კონკრეტულ ადგილებზე ინფორმაციის მისამაგრებლად და გვერდის საბოლოო იერსახის ჩამოსაყალიბებლად. თავის მხრივ წარმოდგენა არის PHP ფაილი, რომელშიც აღწერილია HTML კოდი და აგრეთვე სპეციალური ნიშნულები რომელთა ნაცვლადაც უნდა ჩაჯდეს პროექტის ლოგიკური ნაწილიდან მოსული ინფორმაციები. blade შაბლონიზატორში არ უნდა ეწეროს PHP კოდი. იმდენად რამდენადაც გვერდის გაფორმებისათვის აუცილებელი ყველანაირი ლოგიკა ჩანაცვლებულია სპეციალური ნიშნულებითა და შაბლონიზატორის კონსტრუქციებით, რომელთა გადათარგმნა და ლოგიკის კოდით ჩანაცვლება ხდება კომპილაციის დროს.

კომპილირებული შაბლონები ინახება storage/framework/views საქაღალდეში.

იმისათვის რათა წარმოდგენის ფაილი blade შაბლონიზატორმა დაამუშავოს, ფაილის გაფართოება უნდა იყოს blade.php. გადავარქვათ ჩვენს მიერ შექმნილ ფაილს სახელი:

template.blade.php ამ ფაილს ახლა უკვე ამუშავებს შაბლონიზატორი Blade.

ინფორმაციის გამოტანა

იმისაათვის რათა შაბლონში გამოვიტანოთ მასზე მიმაგრებული ინფორმაცია, უნდა გამოვიყენოთ ორმაგი ფიგურული ბრჭყალები :
Route::get('/', function () {
    return view('templates.template', ['name' => 'შამილი']);
});
            
წარმოდგენის ფაილი : {{ $name }} გამარჯობა ! არ არის აუცილებელი მაინცდამაინც ცვლადის სახით მიმაგრებული ინფორმაცია გამოვიტანოთ ამ გზით, შესაძლებელია PHP-ს ნებისმიერი ფუნქციის შედეგის გამოტანაც : მიმდინარე UNIX დროითი ნიშნული არის {{ time() }}

JSON ფორმატის ინფორმაცია

შეიძლება მოხდეს ისე, რომ დაგვჭირდეს PHP მასივის JSON ფორმტში გადაყვანა და შედეგის JavaScript ცვლადში მოქცევა :
<script>
    var app = <?php echo json_encode($array); ?>;
</script>
            
იგივეს გაკეთება უფრო მარტივადაა შესაძლებელი შაბლონიზატორის @json დირექტივის გამოყენებით :
<script>
    var app = @json($array);
</script>
            

HTML გარემოს გამოტანა

შევქნათ კონტროლერი და აღვწეროთ ამდაგვარი მეთოდი :
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 ოპერატორთან მუშაობა ხდება @if, @elseif, @else და @endif დირექტივების მეშვეობით :
@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 და @guest დირექტივების მეშვეობით უმარტივესად ხდება იმის დადგენა აუტენტიფიცირებულია მომხმარებელი თუ არა :
@auth
    // აუტენტიფიცირებულია
@endauth

@guest
    // არააუტენტიფიცირებულია
@endguest
            

switch ინსტრუქცია

switch ინსტრუქციებთან მუშაობა ხდება @switch, @case, @break, @default და @endswitch დირექტივების მეშვეობით :
@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
            

ციკლის ცვლადი $loop

ციკლებთან მუშაობისას საშუალება გვაქვს მივწვდეთ $loop ცვლადს, რომელიც გვიმარტივებს ამ პროცესს.
@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 კომენტარებისაგან განსხვავებით ბლეიდის კომენტარები არ შედის გენერირებულ HTML კოდში: {{-- ეს კომენტარი არ შევა დაგენერირებულ HTML-ში --}}

წარმოდგენის ფაილის ჩასმა წარმოდგენის ფაილში

შაბლონიზატორის @include დირექტივის მეშვეობით საშუალება გვაქვს წარმოდგენის ფაილში ჩავსვათ სხვა წარმოდგენის ფაილები. მშობელ ფაილზე მიმაგრებული ნებისმიერი ცვლადი ხელმისაწვდომია შვილობილ ფაილშიც :
<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 ბლეიდში

ზოგჯერ საჭიროა, რომ წარმოდგენის ფაილში სტანდატული PHP კოდი შევიტანოთ, ამისათვის გამოიყენება @php დირექტივა :
@php
    $counter = 1;
@endphp
            

მშობელი შაბლონები, წარმოდგენის ფაილების სტუქტურის გამართვა

ხშირად ხდება ისე, რომ საიტის სხვადასხვა გვერდებზე გვხვდება ერთი და იგივე ფრაგმენტები, მაგალითად საიტის ქუდი (header), საიტის ძირი (footer), ნავიგაციური მენიუ და ა.შ. ბუნებრიბვია გაუმართლებელია ამ ფრაგმენტების ყველა გვერდისათვის სათითაოდ გაკეთება და ჯობია თუ მათ ცალკე ფაილებში გავიტანთ, შემდეგ კი სხვადასხვა ადგილებში გამოვიყენებთ საჭიროებისამებრ.

განვიხილოთ მშობელი შაბლონის მარტივი მაგალითი :

კოპირება
<!-- 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
            

ფორმები

CSRF

CSRF იშიფრება, როგორც Cross-site request forgeries - საიტთა შორის ყალბი მოთხოვნების გაცვლა-გამოცვლის პროცესი, რომლის დროსაც ხდება არაავტორიზირებული მოთხოვნების შესრულება აუტენტიფიცირებული მომხმარებლის სახელით.

განვიხილოთ მოქმედებათა ასეთი ჯაჭვი:

  • წარმოვიდგინოთ, რომ რომელიმე ონლაინ-ბანკში (პირობითად www.mybank.com) შესულები ვართ პირად კაბინეტში.
  • ასევე დავუშვათ, რომ თანხის ტრანსფერის განხორციელება შესაძლებელია http://www.mybank.com/transfer?to=<SomeAccountnumber>;amount=<SomeAmount> ბმულზე მიკითხვით (ჩვენი ანგარიშის ნომერი ან რაიმე სხვა იდენტიფიკატორი აღარაა საჭირო რადგან, აუტენტიფიკაცია გავლილი გვაქვს და სისტემა უკვე ისედაც 'გვცნობს').
  • დავუშვათ ახალ ფანჯარაში გავხსენით www.cute-cat-pictures.org ჰაკერული საიტი, მაგრამ არ ვიცით რომ აქ შესვლა სახიფათოა :))
  • თუ ამ საიტის მფლობელმა იცის რომელ ბმულს უნდა მიაკითხოს ტრანზაქციის განსახორციელებლად (ამის გაგება საკმაოდ მარტივია, ბოლოს და ბოლოს თვითონაც იქნება დარეგისტრირებული www.mybank.com საიტზე :))) და ასევე იცის, რომ დროის კონკრეტულ მომენტში თქვენ სისტემაში ხართ შესული, მას შეუძლია თავის საიტზე განათავსოს ასეთი ბმული : http://www.mybank.com/transfer?to=123456;amount=10000 (სადაც 123456 არის მისი ანგარიშის ნომერი, 10000 კი არის თანხა, რომლის გადარიცხვასაც ცდილობს თქვენი ანგარიშიდან.
  • www.cute-cat-pictures.org შესვლისას ჩვენი ბრაუზერი ავტომატურად გააკეთებს ამ მოთხოვნას.
  • საბანკო სისტემა ვერ დაადგენს თუ საიდან წამოვიდა მოთხოვნა, რადგან როგორც ზემოთ ვთქვით სისტემაში შესულები ვართ ანუ ბრაუზერში შენახულია საჭირო Cookie-ბი, სისტემა ჩათვლის, რომ მოთხოვნა ლეგალურია და ჩამოგვეჭრება 10000 ლარი ))

ახლა განვიხილოთ ასეთი შემთხვევა:

  • ტრანსფერის მოთხოვნას დავამატოთ კიდევ ერთი პარამეტრი : http://www.mybank.com/transfer?to=123456;amount=10000;token=31415926535897932384626433832795028841971.
  • მესამე პარამეტი token არის რთულად გამოსაცნობი შემთხვევით სტრიქონი, რომლის გენერირებასაც mybank.com საიტი მოახდენს მისი გვერდების თითოეული ჩატვირთვისას. ეს სტრიქონი განსხვავებული იქნება ნებისმიერი გვერდის ყოველ ახალ ჩატვირთვაზე.
  • თავდამსხმელს არ მიუწვდება ხელი ამ თოქენთან და შესაბამისად არც ის შეუძლია რომ იგი მოთხოვნას გამოაყოლოს, არასწორი თოქენის შემცველი ან საერთოდდ უთოქენოდ გამოგზავნილი მოთხოვნები კი უარყოფილი იქნება www.mybank.com-ის მიერ.

CSRF ველი

ყოველთვის როცა ჩვენს ნებისმიერ აპლიკაციაში აღვწერთ HTML ფორმას, აუცილებლად უნდა განვსაზღვროთ ერთი დამალული ველი (type="hidden") CSRF თოქენისათვის, ფორმით გაგზავნილი მოთხოვნა კი უნდა დაამუშავოს CSRF თავდასხმისაგან დამცავმა შესაბამისმა შუამავალმა შუამავალმა. ამ ველის გენერირება შესაძლებელია @csrf დირექტივის მეშვეობით : კოპირება
<form method="POST" action="/profile">
    @csrf

    ...
</form>
            

შიგთავსის სახელდებული ფრაგმენტები, დასტები

შაბლონიზატორი ბლეიდი საშუალებას გვაძლევს წარმოდგენის ფაილში განვათავსოთ, სადღაც სხვა წარმოდგენის ფაილში აღწერილი შიგთავსის (მარქაფის) სახელდებული ფრაგმენტები. მაგალითად თუ გვაქვს რამოდენიმე შვილობილი შაბლონი, რომლებსაც თავიანთი კონკრეტული Javascript ფაილები ესაჭიროებათ, შეგვიძლია მოვიქცეთ ასე, მშობელი შაბლონი : კოპირება
<!-- 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
            
14. მუშაობა URL-ებთან
Laravel-ში შექმნილია რამოდენიმე დამხმარე ფუნქცია, რომლებიც გვიმარტივებენ URL-ებთან მუშაობას. ამ ფუნქციებთან მუშაობა განსაკუთრებით კომფორტულია, როდესაც გვჭირდება სხვადასხვა ბმულების გენერირება ჩვენს აპლიკაციაში ან კონკრეტული მოთხოვნის ისეთი პასუხს ვაგენერირებთ, რომლის მიხედვითაც აპლიკაციის ერთი ნაწილიდან მეორეში გვიწევს გადამისამართება.

ბმულების გენერირება

ბმულების გენერირებისათვის გამოიყენება url დამხმარე :
$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();               
            

URL-ები სახელდებული მარშრუტებისათვის

სახელდებული მარშრუტებისათვის ბმულების გენერირებაში დაგვეხმარება route ფუნქცია. ასეთი ბმულების შექმნისას, თუ უშუალოდ მარშრუტში აღწერილი ბმული შეიცვლება ჩვენ არ მოგვიწევს არანაირი ცვლილება route ფუნქციის გამოძახებისას, სისტემა ავტომატურად დააგენერირებს მარშრუტის ახალი ინსტრუქციის შესაბამის ბმულს. მაგალითად, დავუშვათ გვაქვს ასეთი მარშრუტი :
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           
            

URL-ები კონტროლერების მეთოდებისათვის

action მეთოდის მეშვეობით შეგსაძლებელია, რომ დავაგენერიროთ ბმული კონკრეტული მარშრუტის კონკრეტული მეთოდისათვის :
use App\Http\Controllers\HomeController;

$url = action([HomeController::class, 'index']);  
            
თუ კონტროლერის მეთოდს გადაეცემა მარშრუტის პარამეტრები, შეგვიძლია ისინი აღვწეროთ ასოციაციურ მასივში და ეს მასივი action ფუნქციას გადავცეთ მეორე პარამეტრად : $url = action([UserController::class, 'profile'], ['id' => 1]);
15. სესიები
სესიათა მექანიზმის პარამეტრები აღწრილია config/session.php ფაილში სადაც ბრუნდება მასივი. განვიხილოთ ძირითადი პარამეტრები : '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-ს. ანუ სესიის ინფორმაციები შეინახება მბ-ში და კერძოდ აქ მითითებულ ცხრილში.

დრაივერი database

როგორც აღვნიშნეთ, 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
...
             

მუშაობა სესიებთან

სესიებთან მუშაობა $request ობიექტით

get()

მოთხოვნის ობიექტს - $request-ს აქვს დამხმარე მეთოდი session, რომელიც გვაძლევს სესიებთან წვდომის საშუალებას. კონკრეტული სესიის მნიშვნელობის მისაღებად session დამხმარე უნდა გამოვიყენოთ get მეთოდთან ერთად, სესიებში ინფორმაციები ინახება წყვილების სახით "გასაღები:მნიშვნელობა", get მეთოდს პირველ პარამეტრად უნდა გადავცეთ სესიის სასურველი გასაღების დასახელება, მეორე პარამეტრად კი შეგვიძლია გადავცეთ ნაგულსხმები მნიშვნელობა იმ შემთხვევისათვის თუ სესიაში ეს გასაღები ვერ მოიძებნება :
public function show(Request $request)
{
    $result = $request->session()->get('key','არ არსებობს');
    dump($result);
}
          
სესიაში უჯრა სახელად key არ არსებობს, ამიტომ ბრაუზერში დაგვიბრუნდება "არ არსებობს".

all()

თუ გვინდა, რომ დავაბრუნოთ სესიაში შენახული ყველა ინფორმაცია უნდა გამოვიყენოთ session დამხმარეს all მეთოდი, რომელიც სესიას აბრუნებს მასივის სახით :
public function show(Request $request)
{
    $result = $request->session()->all();
    dump($result);
}
            

put()

სესიაში ინფორმაციის შეტანა ხდება put მეთოდის მეშვეობით, მას პარამეტრებად უნდა გადაეცეს სესიის უჯრის დასახელება და შესაბამისი მნიშვნელობა :
public function show(Request $request)
{
    $request->session()->put('key','value');
    $result = $request->session()->all();
    dump($result);
}
             
ამ კოდის შედეგი იქნება დაახლოებით ამდაგვარი რამ :

has()

იმის გასაგებად არსებობს თუ არა კონკრეტული დასახელების სესია, გამოიყენება has მეთოდი, რომელსაც პარამეტრად უნდა გადაეცეს საძიებელი სესიის დასახელება :
public function show(Request $request)
{
    if ($request->session()->has('key'))
    {
        dump("1");
    }
    else
    {
        dump("0");
    }
}
            

flash()

ხანდახან საჭოროა, რომ სესიაში ინფორმაცია შევინახოთ მხოლოდ შემდეგი მოთხოვნის დამუშავებამდე, შემდეგ კი წავშალოთ ეს ინფორმაცია სესიიდან. ამისათვის უნდა გამოვიყენოთ flash მეთოდი, რომლის გამოყენებაც საკმაოდ ეფექტურ შედეგს იძლევა დროებითი თვალსაჩინოებისათვის გამოყენებული შეტყობინებების გამოტანისას : $request->session()->flash('status', 'შეტყობინება წარმატებით გაიგზავნა !'); ამ ინფორმაციის ნახვა კი ასე შეგვიძლია წარმოდგენის ფაილში :
@if(Session::has('status'))
    <p>{{ Session::get('status') }}<p>
@endif
            

სესიის მნიშვნელობის გაზრდა და შემცირება

თუ სესიაში შენახული გვაქვს მთელი ტიპის მნიშვნელობა და გვსურს მისი გაზრდა ან შემცირება, უნდა გამოვიყენიოთ increment და decrement მეთოდები :
$request->session()->increment('count');

$request->session()->increment('count', $incrementBy = 2);

$request->session()->decrement('count');

$request->session()->decrement('count', $decrementBy = 2);              
            

Session ფასადი

ზემოთ აღწერილი მეთოდები შეგვიძლია გამოვიყენოთ Session ფასადთანაც. მაგალითად დავბეჭდოთ სესია წარმოდგენის ფაილში : {{ dump(Session::all()) }} ამ ფასადის გამოყენება, რა თქმა უნდა შესაძლებელია კონტროლერშიც.

სესიის კონკრეტული უჯრის წასაშლელად გამოიყენება forget მეთოდი, რომელსაც პარამეტრად უნდა გადავცეთ შესაბამისისი უჯრის დასახელება :

Session::forget('key2'); სესიის მთლიანად წასაშლელად გამოიყენება flush მეთოდი, რომელსაც პარამეტრის გადაცემა არ სჭირდება. Session::flush();

session დამხმარე

სესიებთან წვდომა შესაძლებელია session დამხმარე ფუნქციითაც. მაგალითად დავაბრუნოთ სესიაში შენახული key გასაღების მნიშვნელობა : dump(session('key')); თუ გვსურს, რომ session ფუნქციის მეშვეობით სესიაში შევიტანოთ ახალი მნიშვნელობები : session(['key' => 'value']);
16. მონაცემთა ვალიდაცია
მომხმარებლის მიერ სერვერზე გაგზავნილ ინფორმაციას აუცილებლად სჭირდება გადამოწმება ანუ ვალიდაცია. ამ თავში ვისაუბრებთ სწორედ ამ თემაზე.

პირველ რიგში 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'],
]);
            

ვალიდაციის შეწყვეტა პირველივე დარღვევისას

დავუშვათ ვალიდაციაში აღწერილი გვაქვს რამოდენიმე წესი, მაგრამ გვინდა, რომ ვალიდაცია შეწყდეს წესების პირველივე დარღვევისას, ამისათვის წესების აღწერაში უნდა ჩავსვათ bail წესი : კოპირება
$request->validate([
    'title' => 'bail|required|unique:posts|max:255',
    'body' => 'required',
]);
            
ამ შემთხვევაში თუ title ატრიბუტის unique წესი დაირღვევა, მაშინ max წესის გადამოწმება აღრ მოხდება.

ვალიდაციის შეცდომების გამოტანა

როდესაც ვალიდაციის წესები დაირღვევა, Laravel-ი მომხმარებელს ავტომატურად გადაამისამართებს წინა გვერდზე, დარღვევების შესახებ ინფორმაცია კი, ასევე ავტომატურად შეინახება სესიაში.

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

<!-- სიახლის დასამატებელი ფორმა -->
            

ვალიდაციის შეტყობინებების განსაზღვრა

სისტემაში არსებულ, ვალიდაციის ნებისმიერ წესს, შეესაბამება კონკრეტული შეტყობინება, ეს შეტყობინებები აღწერილია lang/en/validation.php ფაილში და შესაძლებლობა გვაქვს, ნებისმიერი მათგანი გადავაკეთოთ ჩვენი საჭიროებისამებრ.

@error დირექტივა

იმის დასადგენად ფიქსირდება თუ არა შეცდომა კონკრეტული ატრიბუტისათვის (მაგალითად სიახლის სათაურისათვის - title), შეგვიძლია გამოვიყენოთ @error დირექტივა : კოპირება
<!-- /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
            

ფორმის ხელახალი შევსება

როდესაც მომხმარებელი ვალიდაციის წესების დარღვევით შეავსებს ფორმას და გაგზავნის მას, თუ ფორმა მარტივია და შედგება რამოდენიმე ველისაგან, მომხმარებელს უბრალოდ გადავამისამართებთ ისევ ფორმის გვერდზე, მაგრამ თუ ფორმა რთულია და შეიცავს ძალიან ბევრ ველებს, მაშინ მომხმარებელს ამ ველების თავიდან შევსება მოუწევს, რაც არც თუ ისე მოსახერხებელია. ამ პრობლემის გადასაწყვეტად გამოიყენება request ობიექტის flash მეთოდი, რომელიც მოთხოვნის ტანში ჩადებულ ინფორმაციას ინახავს სესიაში : $title = $request->old('title'); ასევე შეგვიძლია გამოვიყენოთ გლობალური დამხმარე old : <input type="text" name="title" value="{{ old('title') }}">

სამომხმარებლო ვალიდატორი

იმისათვის რათა შევქმნათ საკუთარი ვალიდატორი, უნდა მივმართოთ Validator ფასადს და გამოვიყენოთ მისი მეთოდი - make, რომელიც ქმნის ვალიდატორის ობიექტს, ამ მეთოდს პარამეტრებად უნდა გადავცეთ შესამოწმებელი ინფორმაციის შემცველი მასივი და ვალიდაციის წესების შემცველი მასივი. პირველ პარამეტრს ანუ შესამოწმებელ ინფორმაციას, მოგვცემს მოთხოვნის ობიექტის - request-ის all() მეთოდი. კოპირება
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. ფორმა გავაგზავოთ შემდეგი ვარიანტებით :
  • ორივე ველი დავტოვოთ ცარიელი და ისე დავაჭიროთ გაგზავნის ღილაკს
  • სათაურის ველში შევიყვანოთ მნიშვნელობა - '45', ტექსტი დავტოვოთ ცარიელი და ისე დავაჭიროთ გაგზავნის ღილაკს
ამ უკანასკნელ შემთხვევაში ვნახავთ, რომ title ველისათვის დაირღვევა ორი წესი : ის უნდა შეიცავდეს მხოლოდ ანბანის ასოებს და შეყვანილი მნიშვნელობის მიმიმალური სიგრძე უნდა იყოს 5.

იმისათვის რათა, კონკრეტული ველის ყველა შეცდომის შეტყობინება გამოვიტანოთ უნდა გამოვიყენოთ $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
            
17. მიგრაციები, მბ ვერსიათა კონტროლი, ცხრილების მოწყობა მბ-ში
ამ თავში განვიხილავთ მონაცემთა ბაზასთან (მბ) სამუშაო ინსტრუმენტებს.

მბ მიგრაცია

მიგრაცია არის ვერსიათა კონტროლის გამარტივებული სახე მონაცემთა ბაზებთან სამუშაოდ, მისი მეშვეობით ფრეიმვორკის კონსოლიდან შესაძლებელია მბს ცხრილების შექმნა, რედაქტირება და ა.შ. მიგრაციების გამოყენებას განსაკუთრებით დიდი მნიშვნელობა აქვს მაშინ, როდესაც ვმუშაობთ სხვა პროგრამისტებთან ერთად, ანუ გუნდური მუშაობისას. უფრო კონკრეტულად : თუ ჩვენ, ჩვენს ლოკალურ სივრცეში შევიტანთ ცვლილებებს მონაცემთა ბაზაში (დავამატებთ ახალ ცხრილს, წავშლით არსებულს, რომელიმე კონკრეტულ ცხრილში ჩავამატებთ ახალ ველს და ა.შ), ბუნებრივია ეს ცვლილებები შეცდომებს გამოიწვევს გლობალურ, ანუ რეალურ გარემოში ჩვენი კოდის ატვირთვისას ან სხვებისათვის გაზიარებისას, რადგანაც ამ გარემოებში არ იქნება ასახული ჩვენს მიერ გაკეთებული ცვლილებები. მიგრაცია კი არის ერთგვარი 'დოკუმენტაცია', რომელშიც აღწერილია ეს ცვლილებები და მისი დახმარებით, გუნდის სხვა წევრებს მარტივად შეუძლიათ თავიანთ გარემოშიც ასახონ თითოეული სიახლე.

მბ კონფიგურაცია

როგორც ვიცით მბს კონფიგურაციული პარამეტრები ინახება config/database.php ფაილში. ეს ფაილი აბრუნებს მასივს რომელშიც აღწერილია სხვადასხვა პარამეტრები. '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

ცხრილის წაშლა და სახელის შეცვლა

ცხრილის სახელის შესაცვლელად გამოიყენება Schema ფასადის rename მეთოდი :
use Illuminate\Support\Facades\Schema;

Schema::rename($from, $to);
            
ცხრილის წასაშლელად უნდა გამოვიყენოთ Schema ფასადის drop ან dropIfExists მეთოდი :
Schema::drop('users');

Schema::dropIfExists('users');
            

ანონიმური მიგრაციის კლასები

ლარაველის მე-9-ე ვერსიაში აღარ ხდება მიგრაციის კლასთა დასახელების განსაზღვრა, ამის ნაცვლად გამოიყენება ანონიმური კლასები. შევადაროთ მე-8-ე ვერსიის მიგრაციის ფაილი მე-9-ე ვერსიისას :
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');
    }
};
            
ახლა განვმარტოთ თუ რის გამო მოხდა ეს ცვლილება. განვიხილოთ ასეთი სიტუაცია :

  1. დავუშვათ ჩვენი აპლიკაციის 1.0 ვერსიაში გვქონდა 'create_news_table' მიგრაციით შექმნილი 'news' ცხრილი.
  2. აპლიკაციის 1.1 ვერსიაში შევქმენით მიგრაცია, რომელმაც წაშალა 'news' ცხრილი.
  3. აპლიკაციის 1.2 ვერსიაში ისევ გვინდა 'news' ცხრილის შექმნა, ამჯერად უკვე სხვა ველებითა და შინაარსით, ამისათვის ისევ გავაკეთეთ 'create_news_table' მიგრაცია.
ბუნებრივია მე-3-ე ბიჯზე შექმნილ მიგრაციაში აღწერილი კლასის დასახელება დაემთხვევა 1-ელ ბიჯზე შექმნილ მიგრაციაში აღწერილი კლასის დასახელებას. რაც მიგრაციების გაშვებისას, შეცდომას გამოიწვევს ლარაველის მე-8-ე ვერსიაში, მე-9-ე ვერსიაში კი ეს აღრ მოხდება ანონიმური კლასების დახმარებთ.
18. ინფორმაციის შეტანა მბ-ში (Seeders)
ამ თავში განვიხილავთ ფრეიმვორკის მექანიზმს, რომელიც გამოიყენება მბ-ს საწყისი მონაცემებით შევსებისათვის. ეს ნექანიზმი აღწერილია შემდეგ ფაილში : database/seeders/DatabaseSeeder.php (ing: Seed წყარო, საწყისი). გარდა ამ ფაილში აღწერილი DatabaseSeeder კლასისა, შესაძლებელია რომ ჩვენც შევქმნათ ჩვენი საკუთარი კლასები, ფრეიმვორკის კონსოლის გამოყენებით. მაგალითისათვის შევქმნათ კლასი ArticlesSeeder, ამისათვის უნდა ავკრიფოთ ბრძანება : 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
19. მუშაობა მბ-სთან, ფასადი DB

ფასადი DB

ფასადი DB გამოიყენება მბ-სთან სამუშაოდ, იგი აღჭურვილია იმ მეთოდებით, რომლებიც შეიძლება დაგვჭირდეს ნებისმიერი ტიპის მოთხოვნის გასაშვებად მონაცემთა ბაზაში: select, update, insert, delete, statement.

DB::select

DB ფასადის select მეთოდი გამოიყენება მბს ცხრილიდან ინფორმაციის ამოსაღებად. მეთოდს პირველ პარამეტრად უნდა გადაეცეს მოთხოვნის შაბლონი, ტანი. კოპირება
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;
}
            

DB::insert

DB ფასადის insert მეთოდი გამოიყენება მბს ცხრილში ინფორმაციის შესატანად : კოპირება
$insert = DB::insert("INSERT INTO articles (name, text, img) VALUES(?,?,?)", ['Article 4','Article 4 Text','img4.jpg']);
dd($inser);
            

DB::update

DB ფასადის update მეთოდი გამოიყენება მბს ცხრილში ინფორმაციის განახლებისათვის : კოპირება
$update = DB::update("UPDATE articles SET name=? WHERE id > ?", ['Renamed Article', 1]);
dd($update);
            
ეს მეთოდი აბრუნებს ზემოქმედებული ჩანაწერების რაოდენობას.

DB::delete

DB ფასადის delete მეთოდი გამოიყენება მბს ცხრილში ჩანაწერების წასაშლელად : კოპირება
$delete = DB::delete("DELETE FROM articles WHERE id=?", [1]);
dd($delete);        
            
მეთოდი აბრუნებს წაშლილი ჩანაწერების რაოდენობას.

DB::statement

DB ფასადის statement მეთოდი გამოიყენება ისეთი ტიპის ბრძანებების შესასრულებლად, რომლებიც არ მიეკუთვნებიან არც ამორჩევითი ტიპის ბრძანებათა ოჯახს (select) და არც ცვლილებათა ტიპის ბრძანებათა ოჯახს (insert, delete, update). მაგალითად მბ-ცხრილის წასაშლელად ჩვენ დაგვჭირდება სწორედ statement მეთოდი. კოპირება
$statement = DB::statement("DROP TABLE test");
dd($statement);  // true/false     
            

DB::unprepared

თუ გვსურს, რომ ბრძანება გავუშვათ არაწინასწარგანსაზღრული ფორმატით მაშინ უნდა გამოვიყენოთ DB ფასადის unprepared მეთოდი : კოპირება
$unprepared = DB::unprepared('UPDATE articles SET text = "New text" WHERE id = 2');
dd($unprepared); // true/false       
            
გამომდინარე იქიდან, რომ unprepared მეთოდი არ იყენებს წინასწარგანსაზღრულ ფორმატსა და მიმაგრებულ პარამეტრებს, არსებობს SQL ინექციის დიდი საფრთხე. მომხმარებლის მიერ გამოგზავნილი ინფორმაცია ამ მეთოდით არასდროს უნდა შევინახოთ ბაზაში.
20. მბ მოთხოვნათა კონსტრუქტორი
მოთხოვნათა კონსტრუქტორის უკან მოიაზრება სპეციალური კლასი Builder (ფსევდონიმი queryBuilder), რომელსაც გააჩნია კონკრეტული მეთოდები, რომელთაგან თითოეული უზრუნველჰყოფს ბრძანების სრული ტანის კონკრეტული ნაწილის ფორმირებას, ჩვენ აღარ გვიწევს მოთხოვნის ხელით დაწერა. აღნიშნული კლასი აღწერილია vendor/laravel/framework/src/Illuminate/Database/Builder.php ფაილში.

იმისათვის რათა გამოვიყენოთ ეს კლასი, პირველ რიგში უნდა შევქმნათ მბ-ს ცარიელი მოთხოვნის ობიექტი კონკრეტული ცხრილისათვის, ამისათვის უნდა მივმართოთ DB ფასადის table მეთოდს, რომელსაც პარამეტრად უნდა გადავცეთ ცხრილის დასახელება.

get მეთოდი

ეს მეთოდი იღებს ყველანაირ ინფორმაციას კონკრეტული ცხრილიდან და აბრუნებს ობიექტთა მასივს : კოპირება
$articles = DB::table('articles')->get();
dd($articles);          
            
ამ ჩანაწერმა რეალურად შექმნა შემდეგი ბრძანება "SELECT * FROM articles"

first მეთოდი

ეს მეთოდი იღებს პირველ ჩანაწერს კონკრეტული ცხრილიდან და აბრუნებს stdClass კლასის ობიექტის სახით: კოპირება
$article = DB::table('articles')->first();
            
ამ ჩანაწერმა რეალურად შექმნა შემდეგი ბრძანება : "SELECT * FROM articles LIMIT 1"

value მეთოდი

ეს მეთოდი იღებს ინფორმაციას ერთი რომელიმე კონკრეტული ველიდან, პარამეტრად უნდა გადაეცეს ველის დასახელება. მეთოდი აბრუნებს სტრიქონული ტიპის შედეგს, აგრეთვე უნდა აღინიშნოს, რომ ეს მეთოდიც, ისევე როგორც წინა, ინფორმაციას იღებს მხოლოდ პირველი ჩანაწერიდან. კოპირება
$name = DB::table('articles')->value('name');
            
ამ ჩანაწერმა რეალურად შექმნა შემდეგი ბრძანება : "SELECT 'name' FROM articles LIMIT 1"

pluck მეთოდი

ეს მეთოდი იღებს ინფორმაციას ერთი რომელიმე კონკრეტული ველიდან, პარამეტრად უნდა გადაეცეს ველის დასახელება. მეთოდი აბრუნებს ველის მნიშვნელობათა მასივს, აღსანიშნავია რომ, pluck მეთოდი, value მეთოდისაგან განსხვავებით, ინფორმაციას იღებს ყველა ჩანაწერიდან. (ინგ: Pluck - კრეფა, აღება, აკრეფა, შეგროვება). კოპირება
$names = DB::table('articles')->pluck('name');
            
ამ ჩანაწერმა რეალურად შექმნა შემდეგი ბრძანება : "SELECT 'name' FROM articles"

count მეთოდი

ეს მეთოდი თვლის ჩანაწერების რაოდენობას ცხრილში და აბრუნებს რიცხვითი ტიპის მნიშვნელობას : კოპირება
$count = DB::table('articles')->count();
            
ამ ჩანაწერმა რეალურად შექმნა შემდეგი ბრძანება : "SELECT COUNT(*) FROM articles"

max მეთოდი

ეს მეთოდი აბრუნებს მაქსიმალურ მნიშვნელობას განსაზღვრული ველისათვის, პარამეტრად უნდა გადაეცეს ველის დასახელება : კოპირება
$max = DB::table('articles')->max('id');
            
ჩანაწერმა რეალურად შექმნა შემდეგი ბრძანება : "SELECT MAX('id') FROM articles" ასევე არსებობს min, avg და sum მეთოდებიც, რომლებიც ანალოგიურად მუშაობს.

select მეთოდი

ამ მეთოდის გამოყენება ხელსაყრელია მაშინ, როდესაც გვსურს კონკრეტული ველების ამოღება ჩანაწერებიდან და არა ყველა ველისა. ველთა დასახელებები მეთოდს უნდა გადაეცეს პარამეტრებად, შესაძლებელია მასივის სახით გადაცემა ან თითოეული ველის დასახელების გადაცემა ცალკე პარამეტრად : კოპირება
$articles = DB::table('articles')->select('id','name');
            
თუ ამ ბრძანებას გავუშვებთ, შედეგად დაგვიბრუნდება Builder კლასის ობიექტი და არა ის შედეგი რაც გვსურს, იმიტომ რომ select მეთოდს ჯერ არ შეუსრულებია თავისი საქმე, ამისათვის მას უნდა მივაშველოთ get მეთოდი : კოპირება
$articles = DB::table('articles')->select('id','name');
            
ამ ჩანაწერმა რეალურად შექმნა შემდეგი ბრძანება : "SELECT id, name FROM articles"

where მეთოდი

ხშირად საჭიროა ინფორმაციის ამოღება გარკვეული ფილტრების მიხედვით (მაგ: ამოირჩეს ჩანაწერები სადაც id მეტია 2-ზე), ასეთი სახის ბრძანებების შესასრულებლად select მეთოდთან ერთად უნდა გამოვიყენოთ where მეთოდი. როგორც ვიცით where ოპერატორის სინტაქსი სტანდარტულ PHP-ში შემდეგნაირია : 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();   
            

whereBetween მეთოდი

კოპირება
$articles = DB::table('articles')->whereBetween('id',[2,5])->get();   
            
ამ ჩანაწერმა რეალურად შექმნა შემდეგი ბრძანება : "SELECT 'name' FROM articles WHERE id BETWEEN 2 AND 5" ამ მეთოდის შებრუნებული მეთოდია whereNotBetween.

ამ ორი მეთოდიას ანალოგიურია მეთოდები whereIn და whereNotIn ამიტომ მათ მაგალითებს აღარ მოვიყვანთ.

groupBy მეთოდი

ეს მეთოდი გამოიყენება ცხრილის ჩანაწერთა დაჯგუფებისათვის, პარამეტრად უნდა გადაეცეს იმ ველის დასახელება რომლის მიხედვითაც ვაჯგუფებთ : კოპირება
$articles = DB::table('articles')->get()->groupBy('name');
            
ამ ჩანაწერმა რეალურად შექმნა შემდეგი ბრძანება : "SELECT * FROM articles GROUP BY name"

take მეთოდი

ეს მეთოდი დაგვეხმარება მაშინ თუ გვსურს ამოღებული ჩანაწერების რაოდენობის ლიმიტირება : კოპირება
$articles = DB::table('articles')->take(2)->get();
            
ამ ჩანაწერმა რეალურად შექმნა შემდეგი ბრძანება : "SELECT * FROM articles LIMIT 2"

insert მეთოდი

ეს მეთოდი გამოიყენება მბს ცხრილში ინფორმაციის შესატანად, მას პარამეტრად უნდა გადაეცეს ის ინფორმაცია, რომლის შეტანაც გვსურს ცხრილში : კოპირება
$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 მეთოდი

ეს მეთოდი გამოიყენება მბს ცხრილში ინფორმაციის განახლებისათვის. ლოგიკურია, რომ მეთოდის გამოყენება ხდება where მეთოდთან ერთად, რადგან თუ ვუშვებთ ბრძანებას რომელიც სისტემას ეუბნება, რომ ჩაატაროს განახლების ოპერაცია მაშინ ისიც უნდა მივუთითოთ თუ რა უნდა განაახლოს : კოპირება
$update = DB::table('articles')->where('id',2)->update(['name' => 'hello world']);
             
ამ ჩანაწერმა რეალურად შექმნა შემდეგი ბრძანება : "UPDATE articles SET name='hello world' WHERE id=2" მეთოდი აბრუნებს ზემოქმედებული ჩანაწერების რაოდენობას.

delete მეთოდი

ეს მეთოდი გამოიყენება მბს ცხრილში ინფორმაციის წასაშლელად. ამ მეთოდის გამოყენებაც where მეთოდთან ერთად ხდება, რადგან თუ ვუშვებთ წაშლის ბრძანებას მაშინ ისიც უნდა მივუთითოთ თუ რა და სად უნდა წაიშალოს : კოპირება
$delete = DB::table('articles')->where('id',2)->delete();
            
ამ ჩანაწერმა რეალურად შექმნა შემდეგი ბრძანება : "DELETE FROM articles WHERE id=2"

find მეთოდი

თუ გვსურს ერთი ჩანაწერის ამორება id ველის მეშვეობით, უნდა გამოვიყენოთ find მეთოდი : კოპირება
$article = DB::table('articles')->find(3);
            
ამ ჩანაწერმა რეალურად შექმნა შემდეგი ბრძანება : "SELECT * FROM articles WHERE id = 3" მეთოდი აბრუნებს ზემოქმედებული ჩანაწერების რაოდენობას.

join მეთოდი

კოპირება
$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();
            

leftJoin მეთოდი

კოპირება
$users = DB::table('users')->leftJoin('posts', 'users.id', '=', 'posts.user_id')->get();    
            

დაუმუშავებელი განაცხადები

ზოგჯერ შეიძლება დაგვჭირდეს, რომ ბრძანებებში ჩავრთოთ, მოთხოვნათა კონსტრუქტორის ჩვეული სტილისაგან განსხვავებული, არასდანტარტული გამოსახულებები. დავარქვათ მათ დაუმუშავებელი გამოსახულებები. ამისათვის გამოიყენება DB ფასადის raw მეთოდი (ინგ: Raw Data - საწყისი, დაუმუშავებელი მონაცემები). კოპირება
 $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`
            
ეს ყველაფერი მოგვცემდა ასეთ შედეგს :



დაუმუშავებელი გამოსახულებები მოთხოვნაში შედის სტრიქონის სახით და ამიტომ მათი გამოყენებისას უკიდურესად ფრთხილად უნდა ვიყოთ რათა თავი დავიცვათ SQL ინექციებისაგან.

JSON ველები

რომელიმე სატესტო ცხრილში გავაკეთოთ JSON ტიპის ველი 'properties' და ასევე ცხრილში შევიტანოთ რამოდენიმე ჩანაწერი properties ველების შემდეგი მნიშვნელობებით :
{
    "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();
            

WHERE ფილტრის დამატებითი ფუნქციონალი

whereBetween / orWhereBetween

კოპირება
$users = DB::table('users')->whereBetween('id', [1, 100])->get();
            

whereNotBetween / orWhereNotBetween

კოპირება
$users = DB::table('users')->whereNotBetween('id', [1, 100])->get();
            

whereIn / whereNotIn / orWhereIn / orWhereNotIn

კოპირება
$users = DB::table('users')->whereIn('id', [1, 2, 3])->get();

$users = DB::table('users')->whereNotIn('id', [1, 2, 3])->get();
            

whereNull / whereNotNull / orWhereNull / orWhereNotNull

კოპირება
$users = DB::table('users')->whereNull('updated_at')->get();

$users = DB::table('users')->whereNotNull('updated_at')->get();
            

whereDate / whereMonth / whereDay / whereYear / whereTime

კოპირება
$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();
            

whereColumn / orWhereColumn

whereColumn მეთოდის მეშვეობით შეგვიძლია დავადგინოთ ტოლია თუ არა ორი სხვადასხვა ველის მნიშვნელობები : კოპირება
$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();
            
21. მონაცემთა მოდელი
როგორც ვიცით, ფრეიმვორკი laravel-ი დაფუძნებულია შაბლონ mvc-ზე (Model, View, Controller ანუ მოდელი, წარმოდგენა, კონტროლერი). ამ თავში ვისაუბრებთ მოდელებზე. იმის შესახებ თუ რა არის მოდელი და რა ევალება მას, ვისაუბრეთ მე-3-ე თავში, ახლა კი მოვიყვანოთ შემდეგი განმარტება: მოდელი არის მბ-ს ცხრილში შეტანილი კონკრეტული ჩანაწერების აბსტრაქცია, რომელიც მბს ცხრილის ელემენტებს წარმოგვიდგენს ობიექტების სახით, მარტივი სიტყვებით მოდელი არის განსაზღვრული კლასის ობიექტი, რომლის მეთოდებითაც ხდება ცხრილის მონაცემებთან მუშაობა.

მოდელის შსაქმნელად კონსოლში უნდა ავკრიფოთ შემდეგი ბრძანება :

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

თვისება $table

$table თვისება არის მოდელის ობიექტის დახურული თვისება, რომელშეიც უნდა განისაზღვროს მბს იმ ცხრილის სახელი, რომელთანაც ვმუშაობთ მოცემულ მოდელში :
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Article extends Model
{
    protected $table = 'articles';
}
            
უნდა აღინიშნოს, რომ ამ შემთხვევაში $table თვისების განსაზღვრა საჭირო არ არის, რადგანაც ცხრილისა და მოდელის სახელებს შორის გვაქვს ის დამოკიდებულება რაც ზემოთ ვთქვით : მოდელს სახელად აქვს ცხრილის დასახელების მხოლობითი ფორმა. ასეთ შემთხვევაში კი სისტემა ავტომატურად მიხვდება, თუ რომელ ცხრილთან უნდა იმუშავოს.

თვისება $primaryKey

$primaryKey თვისება არის მოდელის ობიექტის დახურული თვისება, რომელშეიც უნდა განისაზღვროს მბს ცხრილის პირველადი გასაღები ველის სახელი, რომელიც გამოიყენება ჩანაწერთა იდენტიფიკაციისათვის, თუ ამ თვისებაში არაფერს განვსაზღვრავთ მაშინ ფრეიმვორკი გაარკვევს შეიცავს თუ არა ცხრილი ველს სახელად id და ჩათვლის, რომ ისაა პირველადი გასაღები და აგრეთვე ავტოგადამთვლელი (auto_increment), ხოლო თუ გვინდა, რომ პირველადი გასაღები იყოს დავუშვათ ველი article_id მაშინ ეს უნდა განვსაზღვვროთ შემდეგნაირად :
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Article extends Model
{
    protected $primaryKey = 'article_id';
}
            

თვისება $incrementing

ეს ღია თვისება გამოიყენება იმის განსასაზღვრავად არის თუ არა რომელიმე ველი ავტოგადამთვლელი :
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Article extends Model
{
    public $incremeting = FALSE;
}
            

თვისება $timestamps

ეს ღია თვისება გამოიყენება იმის განსასაზღვრავად შეივსოს თუ არა ცხრილის created_at და updated_at ველები ავტომატურად ცხრილთან მუშაობისას. როგორც ვიცით, მიგრაციით ცხრილის შექმნისას ფრეიმვორკი ამ ველებს ავტომატურად ამატებს ცხრილში.
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Article extends Model
{
    public $timestamps = FALSE; // ამ შემთხვევაში ეს ველები შეივსება მნიშვნელობა Null-ით
}
            

თვისება $fillable

ეს დახურული თვისება გამოიყენება მბს ცხრილის იმ ველთა განსასაზღვრავად, რომლებშიც შესაძლებელია, რომ მომხმარებელმა შეიტანოს ინფორმაცია :
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Article extends Model
{
    protected $fillable = ['name','text','img'];
}
            

თვისება $guarded

ეს დახურული თვისება გამოიყენება მბს ცხრილის იმ ველთა განსასაზღვრავად, რომლებშიც მომხმარებელს არ შეუძლია ინფორმაციის შეტანა :
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;
}   

... 
            






ამ მიდგომას ეწოდება ORM - Object Relational Mapping და არის ერთგვარი ხიდი რელაციურ ბაზებსა და ოოპ ობიექტებს შორის.

რასაკვირველია მოდელში შესაძლებელია მთხოვნათა კონსტრუქტორის გამოყენებაც :

კოპირება
$a$articles = Article::where('id', '>', 2)->orderBy('name')->take(3)->get();
            

კონკრეტული მოდელის ამოღება

კონკრეტული მოდელის ამოსაღებად გამოიყენება find, first და firstWhere მეთოდები, რომლებიც კოლექციის ნაცვლად აბრუნებენ ერთ ობიექტს. კოპირება
// მოდელის ამოღება პირველადი გასაღების მიხედვით (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 'ვერ მოიძებნა';

});     
            

findOrFail() მეთოდი

ზოგჯერ საჭიროა, რომ იმ შემთხვევაში, თუ მოდელი ვერ მოიძებნება, დავაფიქსიროთ კონკრეტული გამონაკლისი. findOrFail და firstOrFail მეთოდები შეეცდებიან სასურველი მოდელის პოვნას და თუ ეს არ მოხერხდა მაშინ შეგვიძლია :
  • ჩვენს კონტროლერში დავაფიქსიროთ Illuminate\Database\Eloquent\ModelNotFoundException.php ფაილში აღწერილი ინსტრუქციების მიხედვით დაგენერირებული გამონაკლისი : კოპირება
    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
                }
            }
        }
    }    
                        
  • გადავმისამართდეთ 404 გვერდზე (სწორედ ამას აკეთებს სისტემა ავტომატურად თუ პირველ ვარიანტს არ განვიხილავთ).

ინფორმაციის შეტანა მბს ცხრილში

firstOrCreate() მეთოდი

ამ მეთოდის გამოყენება ძალიან ხელსაყრელია მაშინ თუ გვინდა, რომ ცხრილში დავამატოთ უნიკალური ინფორმაცია განსაზღვრული ველისათვის. ამ მეთოდსაც პარამეტრად უნდა გადაეცეს მასივში აღწერილი ველთა დასახელებები და შესაბამისი მნიაშვნელობები : კოპირება
$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', თუ ესეთი ჩანაწერი არსებობს მაშინ მეთოდი დააბრუნებს ამ ჩანაწერის მოდელს. ხოლო თუ არ არსებობს ასეთი ჩანაწერი, მაშინ მეთოდი შექმნის მას და დააბრუნებს ახალშექმნილ მოდელს.

save() მეთოდი

იმისათვის რათა ცხრილში ინფორმაცია შევიტანოთ save მეთოდით, პირველ რიგში უნდა შევქმნათ ცარიელი მოდელის ობიექტი, შემდეგ კი უბრალოდ მივმართოთ და განვუსაზღვროთ მნიშვნელობები შექმნილი ობიექტის იმ თვისებებს, რომელთა დასახელებებიც ემთხვევა ცხრილის ველთა დასახელებებს : კოპირება
$article = new Article;

$article->name = 'ტესტი';
$article->text = 'ტესტი';
$article->img  = 'ტესტი';

$article->save();
            
save მეთოდით ჩანაწერების დამატებისას ავტომატურად ხდება ცხრილის created_at და updated_at ველების შევსება.

create() მეთოდი

ეს მეთოდი გამოიყენება ცხრილში ინფორმაციის შესატანად : კოპირება
$article = Article::create([
    'name' => 'სათაური',
    'text' => 'ტექსტი',
    'img' => 'test.jpg',
]);    
            
create() მეთოდით ჩანაწერების შექმნისას მოდელში აუცილებლად უნდა გვქონდეს აღწერილი მასიურ განსაზღვრებადობასთან დაკავშირებული fillable და guarded თვისებები : კოპირება
protected $fillable = ['name', 'text','img'];  
            
ან : კოპირება
protected $fillable = [];  
            

ინფორმაციის განახლება მბს ცხრილში

ინფორმაციის განახლებისათვის, ჯერ უნდა ამოირჩეს კონკრეტული ჩანაწერი, შემდეგ კი უბრალოდ თავიდან უნდა განვსაზღროთ სასურველი ველების მნიშვნელობები, ბოლოს კი გამოვიძახოთ ისევ მეთოდი save() : კოპირება
$article = Article::find(3);
$article->name = "ახალი სათაური";
$article->save();
            
updated_at ველის მნიშვნელობა ავტომატურად განახლდება.

მასიური განახლებები

შესაძლებელია, რომ ერთ ჯერზე განვაახლოთ არა მარტო კონკრეტული ჩანაწერი, არამედ რამოდენიმე ჩანაწერიც ერთდროულად : კოპირება
Article::where('id','>',1)->update([
    'name' => 'ახალი სათიურები',
    'text' => 'ახალი ტექსტები'
]);
            

Delete() მეთოდი

ეს მეთოდი შლის ჩანაწერებს მბს ცხრილიდან, მაგრამ იმისათვის რათა ცხრილის რომელიმე ჩანაწერი წაიშალოს, ჯერ უნდა ამოირჩეს ეს ჩანაწერი : კოპირება
$article = Article::find(4);
$article->delete();
            

destroy() მეთოდი

ამ მეთოდის მეშვეობით შესაძლებელია ჩანაწერების წაშლა ამოურჩევლად, მას პარამეტრად უნდა გადაეცეს წასაშლელი ჩანაწერის იდენტიფიკატორი (id) კოპირება
  Article::destroy(9);
            
თუ რამოდენიმე ჩანაწერის წაშლა გვსურს ერთდროულად, მაშინ მეთოდს პარამეტრად უნდა გადაეცეს მასივი სადაც შეტანილი იქნება ამ ჩანაწერთა იდენტიფიკატორები.

softDelete

ეს მეთოდი გამოიყენება ჩანაწერთა წასაშლელად, იმ განსხვავებით, რომ იგი რეალურად არ შლის ჩანაწერებს, მისი მუშაობის პრინციპი წააგავს ოპერაციულ სისტემაში ფაილების წაშლის პრინციპს, რის შედეგადაც ეს ფაილები ხვდება სანაგვე ყუთში. ამ მეთოდის გამოსაყენებლად ცხრილს უნდა დავამატოთ ახალი ველი deleted_at, რომელშიც შეინახება ჩანაწერის წაშლის მომენტის შესაბამისი დროის ნიშნული. ამისათვის გავაკეთოთ ახალი მიგრაცია : კოპირება
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();
            
22. მბ-ს ცხრილების ურთიერთდამოკიდებულებები (hasmany, belongsto, ...)
ამ თავში ვისაუბრებთ მბს ცხრილების ურთიერთკავშირებზე. როგორც ვიცით, ცხრილების შექმნისას შესაძლებელია მათი ერთმანეთთან დაკავშირება კონკრეტული ველების გამოყენებით (foreign key).

მაგალითად გვაქვს სტატიების ცხრილი articles და იმ მომხმარებლების ცხრილი - users, რომლებიც ამატებენ ამ სტატიებს. ასეთ შემთხვევაში, როგორც წესი, მომხმარებლის საიდენტიფიკაციო ნომერი (id) იწერება ხოლმე articles ცხრილის user_id ველში. ანუ articles ცხრილის user_id ველი არის users ცხრილთან კავშირის საგარეო გასაღები. პრაქტიკაში ასეთი კავშირები საკმაოდ ხშირია და ამიტომ laravel-ში ჩადგმულია ფუნქციონალი, რომელიც ამარტივებს ამ კავშირებთან მუშაობას.





laravel-ში არსებობს ცხრილთა შორის კავშირის რამოდენიმე ვარიანტი, მათ შორის ძირითადებია :

  • ერთი ერთთან
  • ერთი ბევრთან
  • ბევრი ბევრთან

კავშირი 'ერთი ერთთან', hasOne() მეთოდი

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
            
ვიხილავთ ამდაგვარ შეტყობინებას :



ეს იმიტომ, რომ სისტემამ მიგრაციის ძველ ფაილებსაც მიაკითხა და შესაბამისი შეტყობინებაც დააბრუნა - კონკრეტული ცხრილები უკვე არსებობსო. ამ პრობლემის მოგვარების რამოდენიმე ვარიანტი არსებობს, ყველაზე მარტივია შევქმნათ database/migrations/old საქაღალდე, მასში გადავიტანოთ ყველა ძველი მიგრაცია და ისე გავუშვათ '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
            

კავშირი 'ერთი მრავალთან', hasMany() მეთოდი

ამ მომენტისათვის ჩვენ უკვე გვაქვს მომხმარებლებისა და სიახლეების ცხრილები - users და articles. დავაკავშიროთ ისინი შემდეგნაირად: სიახლეების ცხრილს დავამატოთ ერთი ველი - 'user_id', რომელშიც ჩაიწერება სიახლეების ავტორების იდენტიფიკატორები. კოპირება
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-თან დაკავშირდება კავშირის ტიპით - ერთი მრავალთან.



user მოდელში ჩავამატოთ ახალი მეთოდი 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 ობიექტს :



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;
            

sql ბრძანებათა ოპტიმიზაცია, ე.წ 'ძუნწი ჩატვირთვა'

როგორც ვიცით შემდეგი კოდის გაშვებეისას : კოპირება
$articles = Article::all();
dump($articles);
            
შესრულდება შემდეგი ბრძანება და ბრუნდება მოდელების შემდეგი კოლექცია :


დავუშვათ გვსურს დავბეჭდოთ ავტორის სახელი თითოეული სიახლისათვის : კოპირება
$articles = Article::all();
        
foreach($articles as $article)
{
    echo $article->user->name . '<br>';
}
            
ასეთ შემთხვევაში მივიღებთ შემდეგ სურათს :



როგორც ვხედავთ ციკლის ყოველ იტერაციაზე შესრულდა ერთი და იგივე sql ბრძანება, ეს დიდი პრობლემა არაა, როდესაც ბაზაში სულ რამოდენიმე ჩანაწერი გვაქვს, მაგრამ თუ იქ 10 000 ჩანაწერია, ამ ბრძანების გაშვება საკმაოდ დიდი დატვირთვა იქნება მბ-ს სერვერისათვის, რაც, დიდი ალბათობით, პრობლემებს შექმნის.

ასეთ შემთხვევაში გამოიყენება ე.წ 'ძუნწი ჩატვირთვა' :)) ამ შემთხვევაში დამატებითი ინფორმაციის ჩატვირთვა ხდება კოლექციასთან ერთად. ოპტიმიზირებული ჩატვირთვისას უნდა გამოვიყენოთ მეთოდი with, რომელიც უზრუნველჰყოფს ინფორმაციის ჩატვირთვას ძირითად ცხრილთან დაკავშირებული სხვა ცხრილებიდანაც. მეთოდს პარამეტრად უნდა გადაეცეს იმ მოდელის დასახელება, რომელთან დაკავშირებაც გვსურს :

კოპირება
$articles = Article::with('user')->get();

foreach($articles as $article)
{
    echo $article->user->name . '<br />';
}
            
ასეთ შემთხვევაში მივიღებთ შემდეგ სურათს :



ანუ სულ გაეშვა ორი ბრძანება : პირველმა მოახდინა კოლექციის გენერირება, მეორემ კი ჩატვირთა ამ კოლექციასთან დაკავშირებული საჭირო ინფორმაციები. ბრძანებათა ამდაგვარ ოპტიმიზაციას ეწოდება 'ძუნწი ჩატვირთვა' :))

მეთოდი has()

დავუშვათ გვინდა ამოვარჩიოთ ისეთი მომხმარებლები, რომლებსაც დამატებული აქვთ ერთი სიახლე მაინც, ასეთ შემთხვევაში გამოიყენება has მეთოდი : კოპირება
$users = User::has('articles')->get();

foreach($users as $user)
{
    echo $user->name . '<br />';
}        
            




ანუ : User კლასის has მეთოდს პარამეტრად გადაეცა იმ მოდელის დასახელება, რომლის შესაბამის ცხრილშიც გვსურს User კოლექციის შესაბამისი ჩანაწერების არსებობა/არარსებობის დადგენა.

has მეთოდთან ერთად შესაძლებელია სხვადასხვა პირობითი ოპერატორების გამოყენებაც, დავუშვათ გვინდა ამოვარჩიოთ ის მომხმარებლები, რომლებსაც ორზე მეტი სიახლე აქვთ დამატებული:

კოპირება
$users = User::has('articles','>=', 2)->get();

foreach($users as $user)
{
    echo $user->name . '<br />';
}        
            




კავშირი 'ბევრი ბევრთან', belongsToMany() მეთოდი

ცხრილებს შორის კავშირის ეს ვარიანტი განვიხილოთ მომხმარებლებისა და მათი როლების მაგალითზე. მომხმარებელს შეიძლება გააჩნდეს რამოდენიმე როლი (ადმინისტრატორი, სტუმარი ...), მეორეს მხრივ კი ერთი როლიც შეიძლება დაკავშირებული იყოს რამოდენიმე მომხმარებელთან. ამდაგვარი კავშირის ასაღწერად დაგვჭირდება სამი ცხრილი : მომხმარებლების ცხრილი (users), როლების ცხრილი (roles) და მომხმარებლებისა და როლების საიდენტიფიკაციო ნომრების ცხრილი (role_user). მომხმარებლების ცხრილი უკვე გვაქვს, შევქმნათ დანარჩენი ორის შესაბამისი მიგრაციები : 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' ცხრილთან სამუშაო მოდელის დასახელება.

გავუშვათ ამ მიგრაციების შესრულების ბრძანება და შევქმნათ ცხრილები.







ახლა შევქმნათ "roles" ცხრილის შესაბამისი მოდელი 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) და თუ ასე არ მოვიქცეოდით დაფიქსირდებოდა კონფლიქტი, შეცდომა.

ზუსტად ანალოგიურად მოხდება უკუკავშირის დამყარება : "ერთი როლი უკავშირდება რამოდენიმე მომხმარებელს".

save(), saveMany() და create() მეთოდები

კოპირება
$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' => 'მეოთხე სიახლის ტექსტი'
]);      
            

update() მეთოდი

მეთოდს პარამეტრად უნდა გადაეცეს მბ-ს ცხრილის იმ ველთა დასახელებების შემცველი მასივი, რომელთა შეცვლაც გვსურს : კოპირება
$user = User::find(1);

$user->articles()->where('id', 3)->update([
    'name' => 'მესამე სიახლის ახალი სათაური'
]);
            
ამ ჩანაწერში უნდა გავითვალისწინოთ ერთი რამ : როგორც ვხედავთ ჯერ ვიღებთ მომხმარებლის შესახებ ინფორმაციას (id=1) და შემდეგ ვუშვებთ ინფორმაციის განახლების შესახებ ბრძანებას. თუ სიახლე რომლის შეცვლაც გვსურს (id=3), არ ეკუთვნის ამორჩეულ მომხმარებელს, მაშინ სისტემა არ განაახლებს ამ ჩანაწერს, და ეს ასეც უნდა იყოს.
23. მომხმარებელთა აუტენტიფიკაცია
აპლიკაციების შექმნისას ხშირად აუცილებელია, რომ აპლიკაციის კონკრეტული ნაწილი დაიმალოს საერთო თვალთახედვიდან (მაგალითად ადმინისტრატორის პანელი) და მასზე ხელი მიუწვდებოდეს მხოლოდ რეგისტრირებულ მომხმარებლებს, ამისათვის გამოიყენება მომხმარებელთა აუტენთიფიკაციის სისტემა, რომელიც ჩაშენებულია ფრეიმვორკ ლარაველის სტანდარტულ ფუნქციონალში.

რა არის აუტენტიფიკაცია ?

აუტენტიფიკაცია ეს არის მომხმარებელთა გადამოწმების პროცესი, რომლის დროსაც დარდება მომხმარებლის მიერ აკრეფილი ინფორმაცია (მაგ: სახელი და პაროლი), სისტემაში არსებულ მონაცემებთან. თუ მოხდა ამ ინფორმაციათა დამთხვევა შეიძლება ითქვას, რომ მოხდა მომხმარებელის აუტენტიფიცირება, მაგრამ ეს კიდევ არ ნიშნავს იმას, რომ მას აქვს სისტემაში რაიმეს გაკეთების უფლება. აუტენთიფიკაციის ლოგიკური გაგრძელებაა ავტორიზაცია.

რა არის ავტორიზაცია ?

ავტორიზაცია არის აუტენტიფიცირებული მომხმარებლის უფლებათა გადამოწმების პროცესი.

აუტენთიფიკაციის პარამეტრები განსაზღვრულია config/auth.php ფაილში. მომხმარებლის აუტენთიფიკაციის პროცესი შედგება ორი ძირითადი ელემენტისაგან: პირველი ეს არის ე.წ მცველი - guard, მეორე კი - პროვაიდერი provider.

მცველი განსაზღვრავს თუ რა სახით იქნას შენახული ინფორმაცია იმ მომხმარებლის შესახებ, რომელიც აკეთებს მოთხოვნას, ანუ როგორ შევინახოთ ინფორმაცია იმის შესახებ, რომ მოთხოვნა გააკეთა მაგალითად აუტენთიფიცირებულმა მომხმარებელმა, ეს ინფორმაცია შესაძლებელია შენახულ იქნას სესიაში ან სპეციალურ სტრიქონში სახელად token.

პროვაიდერი განსაზღვრავს თუ როგორ და რა სახით შეიძლება მიიღოს მომხმარებელმა ინფორმაცია მბ-დან ან სხვა წყაროდან.

აუტენტიფიკაციის სისტემასთან მუშაობისთვის საჭიროა წარმოდგენის ფაილები, მარშრუტები და კონტროლერები. არსებობს ამ ყველაფრის შექმნის რამოდენიმე ვარიანტი. განვიხილოთ ერთ-ერთი მათგანი - Laravel Breeze

რა არის Laravel Breeze ?

Laravel Breeze არის პაკეტი, რომელიც საშუალებას გვაძლევს შევქმნათ ყველა აუცილებელი კომპონენტი, აპლიკაციაში აუტენტიფიკაცის სისტემის ჩასაშენებლად. წარმოდგენის ფაილები იქმნება Laravel შაბლონიზატორისა და Tailwind CSS-ის დახმარებით. Tailwind CSS არის CSS ფრეიმვორკი, რომელიც მოიცავს სხვადასხვა კლასებისა და სელექთორების საკმაოდ ფართო ნაკრებს, მასში ასევე აღწერილია ამ კლასების შესაბამისი სტილები და ჩვენ ისღა დაგვრჩენია უბრალოდ მოვარგოთ ეს კლასები ჩვენს აპლიკაციას.

Laravel 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,
    
    ...
];

...
            

Auth ფასადი

user() მეთოდი

აუტენტიფიცირებული მომხმარებლის შესახებ ინფორმაციის მისაღებად უნდა მივმართოთ Auth ფასადს : კოპირება
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());
    }
}
            

check() მეთოდი

იმის დასადგენათ არის თუ არა აუტენტიფიცირებული მომხმარებელი, რომელიც აკეთებს HTTP მოთხოვნას, გამოიყენება Auth ფასადის check მეთოდი : კოპირება
use Illuminate\Support\Facades\Auth;

if(Auth::check())
{
    echo 'აუტენტიფიცირებულია';
}
else
{
    return redirect()->route('login');
}
            
მიუხედავად იმისა, რომ ამ მეთოდით შესაძლებელია მომხმარებლის აუტენტიფიცირება/არააუტენტიფიცირების გადამოწმება, უკეთესი ვარიანტია თუ საჭირო მარშრუტებს შუამავლების მეშვეობით დავიცავთ ხოლმე და თუ მომხმარებელი ამ შუამავალს გაივლის, შესაბამისად აღარც იმის გადამოწმება დაგვჭირდება არის თუ არა იგი აუტენტიფიცირებული.

დაცული მარშრუტები

რიგ შემთხვევებში საჭიროა, რომ კონკრეტულ მარშრუტებთან წვდომა შეეძლოთ მხოლოდ აუტენტიფიცირებულ მომხმარებლებს. როგორც ზემოთ აღვნიშნეთ, ამისათვის გამოიყენება auth შუამავალი : კოპირება
Route::get('/profile', function () {

    // მხოლოდ აუტენტიფიცირებული მომხმარებლები

})->middleware('auth');
            

არააუტენტიფიცირებული მომხმარებლების გადამისამართება

როდესაც auth შუამავალი დაადგენს, რომ მომხმარებელი არააუტენტიფიცირებულია, იგი მას გადაამისამართებს მარშრუტზე სახელად - auth. შეგვიძლია შევცვალოთ ეს მიდგომა, თუ ჩავერევით app/Http/Middleware/Authenticate.php შუამავლის redirectTo მეთოდში :
protected function redirectTo($request)
{
    return route('somewhere');
}
            

აუტენტიფიკაციის სამომხმარებლო სისტემა

როგორც ადრე აღვნიშნეთ, არსებობს აუტენტიფიკაციის სისტემის შექმნის რამოდენიმე ვარიანტი და განვიხილეთ ერთ-ერთი მათგანი - Laravel Breeze. რა თქმა უნდა შესაძლებელია, რომ შევქმნათ ჩვენი საკუთარი სისტემაც. ამაში დაგვეხმარება, ისევ და ისევ, Auth ფასადი : კოპირება
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])) 
{
    // წარმატებული აუტენტიფიკაცია
}
            

მომხმარებლების დამახსოვრება

ალბათ ხშირად შეგვხვედრია აუტენტიფიკაციის ფორმები, რომლებსაც თან ახლავს 'დამიმახსოვრე' ღილაკი (remember me). იმისათვის რათა აპლიკაციაში გამოვიყენოთ ეს ფუნქციონალი attempt მეთოდს უნდა გადავცეთ ლოგიკური ტიპის დამატებითი პარამეტრი. true მნიშვნელობის შემთხვევაში სისტემა მომხმარებლის შესახებ ინფორმაციას სესიაში შეინახავს მანამ, სანამ თავად მომხმარებელი არ დააჭერს სისტემიდან გამოსვლის ღილაკს (logout). users ცხრილს თუ დავაკვირდებით, ვნახავთ, რომ იგი შეიცავს remember_token ველს. სწორედ ამ ველში შეინახება დამახსოვრებული მომხმარებლის შესაბამისი უნიკალური სტრიქონი. კოპირება
use Illuminate\Support\Facades\Auth;

$remember = $request->has('remember');

if (Auth::attempt(['email' => $email, 'password' => $password], $remember)) 
{
    // მომხმარებელი დამახსოვრებულია ...
}
            
როდესაც მომხმარებელი აუტენტიფიკაციისას აწვება 'დამიმახსოვრე' ღილაკს, იკვრება მოქმედებათა შემდეგი ჯაჭვი : გენერირდება უნიკალური ჰეშირებული სტრიქონი, რომელიც ინახება როგორც ბრაუზერში (Cookie-ს სახით), ასევე სერვერზე ('users' ცხრილის 'remember_token' ველში), ამის შემდეგ თუ მომხმარებელი დახურავს და ისევ გახსნის ბრაუზერს, სისტემა გადაამოწმებს არსებობს თუ არა დამახსოვრებული ჰეშ-სტრიქონი Cookie-ში, თუ კი - მაშინ შეამოწმებს შეესაბამება თუ არა ეს სტრიქონი ბაზაში არსებულ რომელიმე ჩანაწერს და თუ ეს ასეა მაშინ მომხმარებელი ავტომატურად ხდება აუტენტიფიცირებული. Cookie-ში შენახული ინფორმაცია იარსებებს მანამ, სანამ მომხმარებელი არ გამოვა სისტემიდან (logout), უბრალოდ ბრაუზერის დახურვით ეს ინფორმაცია არ იშლება.

კონკრეტული მომხმარებლის ობიექტის აუტენტიფიკაცია

დავუშვათ გვაქვს app/Models/User კლასის კონკრეტული ობიექტი და გვსურს, რომ ამ ობიექტის შესაბამისი მომხმარებელი გახდეს აუტენტიფიცირებული სისტემაში. ამისათვის უნდა მივმართოთ Auth ფასადის login მეთოდს : კოპირება
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 მეთოდს : კოპირება
Auth::loginUsingId(1, $remember = true);
            

სისტემიდან გამოსვლა

იმისათვის რათა სესიიდან წაიშალოს ინფორმაცია აუტენტიფიცირებული მომხმარებლის შესახებ და შესაბამისად მომხმარებელიც გამოვიდეს სისტემიდან, უნდა მივმართოთ Auth ფასადის logout მეთოდს. რეკომენდებულია, რომ სისტემიოდან გამოსვლის შემდეგ მოვახდინოთ სესიის გასუფთავება და CSRF თოქენის ხელახალი გენერირება : კოპირება
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

public function logout(Request $request)
{
    Auth::logout();

    $request->session()->invalidate();

    $request->session()->regenerateToken();

    return redirect('/');
}
            
24. მომხმარებელთა ავტორიზაცია
როგორც უკვე აღვნიშნეთ, მომხმარებელთა ავტორიზაცია არის პროცესი, რომლის დროსაც ხდება მომხმარებლის ხელმისაწვდომობის, უფლების ქონა/არქონის გადამოწმება ამა თუ იმ მოქმედებაზე. Laravel-ში შესაძლებელია შეიქმნას კონკრეტული ინსტრუმენტები, რომლებიც განსაზღვრავენ ამ ხელმისაწვდომობას. ამ ინსტრუმენტებს პირობითად დავარქვათ 'კონტროლიორები'.

როგორც წესი, კონტროლიორების განსაზღვრა ხდება ხოლმე 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)) 
{
    // მომხმარებელს არ შეუძლია სიახლის არც წაშლა და არც რედაქტირება ... 
}
            
25. ლოკალიზაცია (localization, lang)
ფრეიმვორკ laravel-ში გვხვდება, ტექსტების სხვადასხვა ენაზე გადათრგმნისათვის საჭირო, საკმაოდ მოსახერხებელი ფუნქციები. მათი დახმარებით მარტივად ხდება ჩვენი აპლიკაციის გამართვა რამოდენიმე ენაზე ერთდროულად. ენობრივი სტრიქონები (სათარგმნი სტრიქონები) ინახება lang საქაღალდეში. აქვე უნდა შევქმნათ ქვე-საქაღალდეები თითოეული იმ ენისათვის, რომელზეც გვსურს ჩვენი პროექტის გადათარგმნა:
/lang
    /en
        messages.php
    /ka
        messages.php
            
ფრეიმვორკის დაინსტალირების შემდეგ ამ საქაღალდეში, ნაგულისხმეობის პრინციპით, იქმნება ლოკალიზაციის ერთადერთი საქაღალდე ინგლისური ენისათვის lang/en.

როგორც წესი, თითოეული ლოკალიზაცია ინახება საქაღალდეში, რომლის დასახელებაც ემთხვევა შესაბამისი ენის კოდს (მაგ: ინგლისური - en, ქართული - ka და ა.შ). ლოკალიზაციის ქვე-საქაღალდეებში ინახება ე.წ ფაილი ლექსიკონები, მათში აღწერილია კონკრეტული სტრიქონების თარგმანები კონკრეტულ ენაზე. ამ სტრიქონებს ეწოდებათ ენობრივი კონსტანტები. ფაილ ლექსიკონებში ბრუნდება უბრალო ასოციაციური მასივები, რომლის გასაღებებიც გადასათარგმნი სტრიქონებია, ხოლო ამ გასაღებთა მნიშვნელობები - ამ სტრიქონების თარგმანები შესაბამის ენაზე.

თითოეული ფაილი ლექსიკონი განკუთვნილია, პროექტის კონკრეტული ელემენტისათვის, მაგალითად ვალიდაციის ელემენტს აქვს თავისი ფაილი (validation.php), გვერდების გადანომრვის ელემენტს - თავისი (pagination.php), აუთენტიფიკაციის გვერდს - თავისი (auth.php) და ა.შ.

ლოკალიზაციისა და ფაილ-ლექსიკონის შექმნა

lang საქაღალდეში შევქმნათ ახალი ლოკალიზაცია ქართული ენისათვის : lang/ka. ამ ლოკალიზაციაში უნდა ჩავაკოპიროთ ის სტანდარტული ფაილ-ლექსიკონები, რომლებსაც ფრეიმვორკი ავტომატურად ქმნის ინსტალაციისას lang/en ლოკალიზაციაში, ეს ფაილებია : validation.php, pagination.php, auth.php და passwords.php. ამის გაკეთება აუცილებელია, იმდენად რამდენადაც, აოლიკაციის მუშაობისას, დიდი ალბათობით დაგვჭირდება იმ ელემენტებთან მუშაობა, რომლებთანაც ეს ლექსიკონებია დაკავშირებული.

ამის შემდეგ უბრალოდ უნდა გავხსნათ ახლადშექმნილ ლოკალიზაციაში არსებული ლექსიკონ-ფაილები და მათში აღწერილ მასივებში, გასაღებების მნიშვნელობები მივუთითოთ ქართულად.

ეს რაც შეეხებოდა უკვე არსებულ ლექსიკონ-ფაილებს. ახლა შევქმნათ საკუთარი ლექსიკონ-ფაილი და ვნახოთ თუ როგორ ხდება მისი გამოყენება, lang/ka საქაღალდეში შევქმნათ ფაილი - messages.php : კოპირება
return [
    'welcome' => 'კეთილი იყოს თქვენი მობრძანება',
    'hello' => 'მოგესალმებით'
];
            
ლოკალიზაციის შექმნის შემდეგ საჭიროა config/app.php ფაილში აღწერილი ასოციაციური მასივის locale გასაღების მნიშვნელობის ჩასწორება : 'locale' => 'ka' ამავე მასივის fallback_locale გასაღებში მითითებულია ალტერნატიული ლოკალიზაციის დასახელება : 'fallback_locale' => 'en' ანუ თუ locale გასაღებში მითითებული ლოკალიზაცია რაიმე მიზეზის გამო მიუწვდომელი იქნება სისტემისათვის, მაშინ ფრეიმვორკი შეეცდება გამოიყენოს ალტერნატიული ლოკალიზაცია.

Lang ფასადი

Lang ფასადი შექმნილია ლოკალიზაციებთან და ფაილ-ლექსიკონებთან სამუშაოდ. ფასადს აქვს მეთოდი get, რომლის მეშვეობითაც ხდება საჭირო ენობრივი კონსტანტის თარგმანის მიღება :
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' => 'ვასო'));
}
            

მიმდინარე ლოკალიზაციის განსაზღვრა

მიმდინარე ლოკალიზაციის დასადგენად გამოიყენება App ფასადის currentLocale და isLocale მეთოდები : კოპირება
use Illuminate\Support\Facades\App;

$locale = App::currentLocale();

if (App::isLocale('ka')) 
{
    //
}
            


პრაქტიკა



1. პროლოგი
ცნობარის პრაქტიკულ ნაწილში შევქმნით ორენოვან ბლოგს. ადმინისტრატორს შესაძლებლობა ექნება დაამატოს, წაშალოს, დაარედაქტიროს და გამოაქვეყნოს სიახლეები, ასევე დაადასტუროს და გამოაქვეყნოს, მომხმარებლების მიერ კონკრეტულ სიახლეებზე დატოვებული კომენტარტები. რეგისტრირებულ მომხმარებლებს შეეძლებათ დატოვონ კომენტარები კონკრეტულ სიახლეებზე, დაარედაქტირონ პირადი მონაცემები და ა.შ.

ასევე განვიხილავთ ისეთ სისტემებსა და ინსტრუმენტებს, რომლებთანაც აქამდე არ გვქონია შეხება. მაგალითად მუშაობა მოგვიწევს ფაილებთან, ქეშირებასთან, ჰეშირებასთან, ასევე ვისწავლით Ajax-თან მუშაობას, დავაინსტალირებთ სხვადასხვა პაკეტებს და ა.შ ...

მომავალი პროექტის ადმინისტრატორის მხარისთვის გამოვიყენოთ Bootstrap ფრეიმვორკის მარტივი შაბლონი, რომლის გადმოწერაც შეგიძლიათ ამ ბმულიდან.

მომხმარებლის მხარისათვის კი გამოვიყენოთ ეს შაბლონი.

მზა პროექტის გადმოსაწერად მიჰყევით ამ ბმულს.

2. პროექტის ინსტალაცია და მონაცემთა ბაზის დაგეგმარება
პირველ რიგში დავაინსტალიროთ ახალი პროექტი : კოპირება
composer create-project laravel/laravel blog
                
ახლა ავამუშავოთ პროექტი : კოპირება
cd path/to/project

php artisan serve
                
ასევე დავაინსტალიროთ საკმაოდ მოსახერხებელი პაკეტი, რომლის მეშვეობითაც შესაძლებელია თვალი ვადევნოთ გაშვებულ SQL ბრძანებებს, მიმდინარე მარშრუტებს, კონფიგურაციულ პარამეტრებს და ა.შ. კოპირება
composer require barryvdh/laravel-debugbar --dev
                
აპლიკაციის კეთების პროცესში სასურველია, რომ .env ფაილში ჩართული გვქონდეს შეცდომების ხილვის რეჟიმი (debugger) : APP_DEBUG=true ასეთ შემთხვევაში, ზემოთხსენებული პაკეტის ინსტალაციის შემდეგ, ბრაუზერში ვიხილავთ ამდაგვარ პანელს :



მონაცემთა ბაზა

მომხმარებლების ცხრილი

როგორც ვიცით, Laravel-ის ინსტალაციის შემდეგ ავტომატურად იქმნება მიგრაციის რამოდენიმე ფაილი. ერთ-ერთი მათგანია მომხმარებლებთან სამუშაო ცხრილის მიგრაციის ფაილი, რომელსაც აქვს შემდეგი სტრუქტურა : კოპირება
public function up()
{
    Schema::create('users', function (Blueprint $table) {
        $table->id();
        $table->string('name');
        $table->string('email')->unique();
        $table->timestamp('email_verified_at')->nullable();
        $table->string('password');
        $table->rememberToken();
        $table->timestamps();
    });
}
                
ასე რომ, მომხმარებლების ცხრილის მიგრაციის ფაილი უკვე გვაქვს.

ადმინისტრატორების ცხრილი

საკმაოდ ხშირი პრაქტიკაა, როდესაც ადმინისტრატორებისთვისაც users ცხრილს იყენებენ და შემდეგ როლის მიხედვით ანსხვავებენ ხოლმე სტანდარტულ მომხმარებლებასა და ადმინსტრატორებს, გააჩნია პროექტის შინაარს, მასშტაბურობას, მიზნებს და ა.შ. ამჯერად ადმინისტრატორებისათვის შევქმნათ სრულიად დამოუკიდებელი მოდული ცხრილითურთ. შევქმნათ მიგრაციის ფაილი : კოპირება
php artisan make:migration create_admins_table
                
კოპირება
public function up()
{
    Schema::create('admins', function (Blueprint $table) {
        $table->id();
        $table->string('name');
        $table->string('email')->unique();
        $table->string('password');
        $table->timestamps();
    });
}
                

სიახლეების ცხრილი

როგორც აღვნიშნეთ, ბლოგი იქნება ორენოვანი, შესაბამისად სიახლეებთან სამუშაოდაც დაგვჭირდება ორი ცხრილი - ძირითადი და სათარგმნი. შევქმნათ მიგრაციის ფაილები : კოპირება
php artisan make:migration create_articles_table
                
კოპირება
public function up()
{
    Schema::create('articles', function (Blueprint $table) {
        $table->id(); 
        $table->string('image', 255); 
        $table->timestamps();
    });
}
                
კოპირება
php artisan make:migration create_articles_translates_table
                
სათარგმნი ცხრილის თითოეული ჩანაწერი დაკავშირებული უნდა იყოს ძირითადი ცხრილის კონკრეტულ ჩანაწერთან : კოპირება
public function up()
{
    Schema::create('articles_translates', function (Blueprint $table) {
        $table->id(); 
        $table->foreignId('article_id')->constrained()->onDelete('cascade');
        $table->string('title', 100); 
        $table->string('description', 255); 
        $table->text('text'); 
        $table->string('lang', 2); // ენის ინდექსი : ka, en ...
        $table->timestamps();
    });
}
                

კომენტარების ცხრილი

ბუნებრივია თითოეული კომენტარი დაკავშირებული იქნება კონკრეტულ სიახლესთან, კომენტარს ასევე ეყოლება ავტორი, შევქმნათ მიგრაციის ფაილი და განვსაზღვროთ შესაბამისი დამოკიდებულებები : კოპირება
php artisan make:migration create_comments_table
                
კოპირება
public function up()
{
    Schema::create('comments', function (Blueprint $table) {
        $table->id(); 
        $table->foreignId('user_id')->constrained()->onDelete('cascade');
        $table->foreignId('article_id')->constrained()->onDelete('cascade');
        $table->string('comment', 255); 
        $table->timestamps();
    });
}
                

საკონტაქტო ინფორმაციის ცხრილი

შევქმნათ მიგრაციის ფაილი : კოპირება
php artisan make:migration create_contacts_table
                
კოპირება
public function up()
{
    Schema::create('contacts', function (Blueprint $table) {
        $table->id();
        $table->string('phone', 100); 
        $table->string('email', 100); 
        $table->timestamps();
    });
}
                

მონაცემთა ბაზის კონფიგურაციული პარამეტრები

ახლა .env ფაილში განვსაზღვროთ მონაცემთა ბაზის კონფიგურაციული პარამეტრები : კოპირება
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=blog
DB_USERNAME=user
DB_PASSWORD=password
                
***
უკვე შეგვიძლია გავუშვათ მიგრაციების შესრულების ბრძანება : კოპირება
php artisan migrate
                

dbdiagram.io

არსებობოს მშვენიერი ხელსაწყო dbdiagram.io, რომელიც გვიმარტივებს მონაცემთა ბაზის დაგეგმარების პროცესს და ასევე გვაძლევს საშუალებას თვალი ვადევნოთ ბაზაში შემავალი ცხრილების ურთიერთდამოკიდებულებებს და ა.შ. შეგვიძლია უკვე არსებული ბაზის dbdiagram.io-ში დაიმპორტება და პირიქითაც - dbdiagram.io-ზე შექმნილი ბაზის დაექსპორტებაც.

ჩვენი პროექტის ბაზას dbdiagram.io-ზე ექნება შემდეგი სახე :

კოპირება
Table "admins" {
  "id" bigint(20) [not null]
  "name" varchar(255) [not null]
  "email" varchar(255) [not null]
  "password" varchar(255) [not null]
  "created_at" timestamp [default: NULL]
  "updated_at" timestamp [default: NULL]
}

Table "articles" {
  "id" bigint(20) [not null]
  "image" varchar(255) [not null]
  "created_at" timestamp [default: NULL]
  "updated_at" timestamp [default: NULL]
}

Table "articles_translates" {
  "id" bigint(20) [not null]
  "article_id" bigint(20) [not null, ref: > articles.id]
  "title" varchar(100) [not null]
  "description" varchar(255) [not null]
  "text" text [not null]
  "lang" varchar(2) [not null]
  "created_at" timestamp [default: NULL]
  "updated_at" timestamp [default: NULL]
}

Table "comments" {
  "id" bigint(20) [not null]
  "user_id" bigint(20) [not null, ref: > users.id]
  "article_id" bigint(20) [not null, ref: > articles.id]
  "comment" varchar(255) [not null]
  "confirmed" int(1) [not null, default: 0]
  "created_at" timestamp [default: NULL]
  "updated_at" timestamp [default: NULL]
}

Table "contacts" {
  "id" bigint(20) [not null]
  "phone" varchar(100) [not null]
  "email" varchar(100) [not null]
  "created_at" timestamp [default: NULL]
  "updated_at" timestamp [default: NULL]
}

Table "failed_jobs" {
  "id" bigint(20) [not null]
  "uuid" varchar(255) [not null]
  "connection" text [not null]
  "queue" text [not null]
  "payload" longtext [not null]
  "exception" longtext [not null]
  "failed_at" timestamp [not null, default: `current_timestamp()`]
}

Table "migrations" {
  "id" int(10) [not null]
  "migration" varchar(255) [not null]
  "batch" int(11) [not null]
}

Table "password_resets" {
  "email" varchar(255) [not null]
  "token" varchar(255) [not null]
  "created_at" timestamp [default: NULL]
}

Table "personal_access_tokens" {
  "id" bigint(20) [not null]
  "tokenable_type" varchar(255) [not null]
  "tokenable_id" bigint(20) [not null]
  "name" varchar(255) [not null]
  "token" varchar(64) [not null]
  "abilities" text [default: NULL]
  "last_used_at" timestamp [default: NULL]
  "created_at" timestamp [default: NULL]
  "updated_at" timestamp [default: NULL]
}

Table "users" {
  "id" bigint(20) [not null]
  "name" varchar(255) [not null]
  "email" varchar(255) [not null]
  "email_verified_at" timestamp [default: NULL]
  "password" varchar(255) [not null]
  "remember_token" varchar(100) [default: NULL]
  "created_at" timestamp [default: NULL]
  "updated_at" timestamp [default: NULL]
}
                




3. ადმინისტრატორების მოდული

Admin მოდელი

შევქმნათ ადმინისტრატორების ცხრილთან და მოდულთან სამუშაო მოდელი - Admin : კოპირება
php artisan make:model Admin
                
კოპირება
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Admin extends Model 
{
    //    
}
                

საწყისი ინფორმაციის შეტანა ადმინისტრატორების ცხრილში

ადმინსტრატორების ცხრილში საწყისი ინფორმაციის შესატანად გამოვიყენოთ ე.წ 'სიდერების' (seeders) მექანიზმი : კოპირება
php artisan make:seeder AdminSeeder
                
სიდერში მივმართოთ DB ფასადს და განვსაზღვროთ ცხრილში შესატანი ინფორმაცია : კოპირება
namespace Database\Seeders;

use DB;
use Hash;
use Illuminate\Database\Seeder;

class AdminSeeder extends Seeder
{
    public function run()
    {
        DB::table('admins')->insert([
            [
                'name' => 'ვასო',
                'email' => 'admin@admin.com',
                'password' => Hash::make('admin123')
            ]
        ]);
    }
}
                
როგორც ვხედავთ, პაროლის ჰეშირებისათვის გამოვიყენეთ Hash ფასადის make მეთოდი, რომელსაც პარამეტრტად გადავეცით სასურველი პაროლი - 'admin123'. ჰეშირებული პაროლის გადამოწმებას ვისწავლით ადმინისტრატორის ავტორიზაციის მეთოდის დაწერისას.

ახლა გავუშვათ ინფორმაციის შეტანის ბრძანება :

კოპირება
php artisan db:seed --class=AdminSeeder
                

BaseController

ადმინისტრატორის მხარის კონტროლერებისათვის შევქმნათ app/Http/Controllers/Admin საქაღალდე, მასში კი BaseController კონტროლერი, რომლის მემკვიდრეც იქნება ადმინისტრატორის მხარის ყველა კონტროლერი : კოპირება
php artisan make:controller Admin/BaseController
                
კოპირება
namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;

class BaseController extends Controller
{
    //
}
                

AdminsController

ახლა შევქმნათ ადმინისტრატორების მოდულთან სამუშო კონტროლერი - AdminsController, რომელიც, როგორც ვთქვით, იქნება Admin/BaseController კონტროლერის მემკვიდრე : კოპირება
php artisan make:controller Admin/AdminsController --resource
                
კოპირება
namespace App\Http\Controllers\Admin;

use Illuminate\Http\Request;

class AdminsController extends BaseController
{
    // ჩამონათვალის გვერდი
    public function index()
    {
        //
    }

    // ადმინსტრატორის დამატების ფორმის გვერდი
    public function create()
    {
        //
    }

    // ადმინსტრატორის შენახვა მბ-ში
    public function store(Request $request)
    {
        //
    }

    // ადმინსტრატორის რედაქტირების ფორმის გვერდი
    public function edit($id)
    {
        //
    }

    // ადმინსტრატორის განახლება მბ-ში
    public function update(Request $request, $id)
    {
        //
    }

    // ადმინსტრატორის წაშლა მბ-ში
    public function destroy($id)
    {
        //
    }
}
                
4. ადმინისტრატორის ავტორიზაცია, წარმოდგენის ფაილების დაგეგმარება
პირველ რიგში განვსაზღვროთ ადმინისტრატორის ავტორიზაციის მარშრუტი, routes/web.php : კოპირება
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Admin\LoginController;

Route::get('/admin/login', [LoginController::class, 'showLogin'])->name('ShowLogin');
                
ახლა შევქმნათ შესაბამისი კონტროლერი : კოპირება
php artisan make:controller Admin/LoginController
                
აღვწეროთ მისი showLogin მეთოდი, რომელიც უზრუნველჰყოფს ავტორიზაციის ფორმის გამოტანას ბრაუზერში : კოპირება
namespace App\Http\Controllers\Admin;

use Illuminate\Http\Request;

class LoginController extends BaseController
{
    public function showLogin(Request $request) 
    {
        /* 
            თუ სესიაში უკვე შენახულია ავტორიზებული ადმინისტრატორის შესაბამისი 
            მოდელი გადავიდეთ მთავარ გვერდზე, წინააღმდეგ შემთხვევაში ჩავტვირთოთ
            ავტორიზაციის ფორმა (AdminMainPage მარშრუტს შევქმნით ოდნავ ქვემოთ)
        */

        return $request->session()->has('admin') ? redirect()->route('AdminMainPage') : view('admin.login');        
    }    
}
                

წარმოდგენის ფაილები

ადმინისტრატორის მხარის წარმოდგენის ფაილებისათვის შევქმნათ საქაღალდე resources/views/admin, მასში კი ფაილი - login.blade.php, რომელშიც ჩავაკოპირებთ გადმოწერილი Bootstrap შაბლონის login.html ფაილის მაქსიმალურად გამარტივებულ კოდს : კოპირება
<!DOCTYPE html>
<html lang="en">
    <head>
        <title>ავტორიზაცია</title>
        <link href="{{ asset('assets/admin/css/styles.css') }}" rel="stylesheet" />
    </head>
    <body class="bg-primary">
        <div id="layoutAuthentication">
            <div id="layoutAuthentication_content">
                <div class="container">
                    <div class="row justify-content-center">
                        <div class="col-lg-5">
                            <div class="card shadow-lg border-0 rounded-lg mt-5">
                                <div class="card-body">
                                    <form>
                                        <div class="form-floating mb-3">
                                            <input class="form-control" type="email"/>
                                            <label>ელ-ფოსტა</label>
                                        </div>
                                        <div class="form-floating mb-3">
                                            <input class="form-control" type="password"/>
                                            <label>Password</label>
                                        </div>
                                        <div class="d-flex align-items-center justify-content-between mt-4 mb-0">
                                            <button type="submit" class="btn btn-primary">ავტორიზაცია</button>
                                        </div>
                                    </form>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </body>
</html>
                
ყურადღება მივაქციოთ წითლად მონიშნულ ჩანაწერს, ისეთი დამხმარე ფაილებისათვის როგორებიცაა css, js და ა.შ, შევქმნათ public/assets საქაღალდე და იქ განვათავსოთ ისინი. ადმინისტრატორის მხარის დამხმარე ფაილებისათვის შევქმნათ, კიდევ ერთი - public/assets/admin საქაღალდე. გადმოწერილი Bootstrap შაბლონიდან დავაკოპიროთ მხოლოდ css საქაღალდე და ჩავაგდოთ ახლადშექმნილ public/assets/admin საქაღალდეში.

ახლა დავუბრუნდეთ asset ფუნქციას, იგი მიმდინარე HTTP/HTTPS მოთხოვნიდან გამომდინარე აგენერირებს ბმულს, public საქაღალდეში არსებულ დამხმარე ფაილებამდე. მაგალითად ამ შემთხვევაში დააგენერირებდა შემდეგ ბმულს :

http://127.0.0.1:8000/assets/admin/css/styles.css
                
თუ შევალთ http://127.0.0.1:8000/admin/login მისამართზე, ვიხილავთ ავტორიზაციის ფორმას.

ავტორიზაცია

ავტორიზაციისათვის განვსაზღვროთ post ტიპის AdminLogin მარშრუტი, routes/web.php : კოპირება
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Admin\LoginController;

Route::get('/admin/login', [LoginController::class, 'showLogin'])->name('ShowLogin');
Route::post('/admin/signin', [LoginController::class, 'login'])->name('AdminLogin');
                
login.blade.php ფაილში არსებულ ფორმაში შევიტანოთ ცვლილებები: კოპირება
<form method="post" action="{{ route('AdminLogin') }}">
    
    @if($errors->any())
        <div class="alert alert-danger">
            <ul>
                @foreach ($errors->all() as $error)
                    <li>{{ $error }}</li>
                @endforeach
            </ul>
        </div>
    @endif
    @if(Session::has('login_failed'))
        <div class="alert alert-danger">
            არასწორი მონაცემები
        </div>
    @endif
    

    @csrf

    <div class="form-floating mb-3">
        <input class="form-control" type="email" name="email" value="{{ old('email') }}" />
        <label>ელ_ფოსტა</label>
    </div>
    <div class="form-floating mb-3">
        <input class="form-control" type="password" name="password" />
        <label>პაროლი</label>
    </div>
    <div class="d-flex align-items-center justify-content-between mt-4 mb-0">
        <button type="submit" class="btn btn-primary">ავტორიზაცია</button>
    </div>
</form>
                
ახლა აღვწეროთ LoginController კონტროლერის login მეთოდი : კოპირება
namespace App\Http\Controllers\Admin;

use Hash;
use App\Models\Admin;
use Illuminate\Http\Request;

class LoginController extends BaseController
{
    public function showLogin(Request $request) 
    {
        /* 
            თუ სესიაში უკვე შენახულია ავტორიზებული ადმინისტრატორის შესაბამისი 
            მოდელი გადავიდეთ მთავარ გვერდზე, წინააღმდეგ შემთხვევაში ჩავტვირთოთ
            ავტორიზაციის ფორმა (AdminMainPage მარშრუტს შევქმნით ოდნავ ქვემოთ)
        */

        return $request->session()->has('admin') ? redirect()->route('AdminMainPage') : view('admin.login');      
    }    

    public function login(Request $request)
    {
        // ვალიდაცია
    	$this->validate( $request , [
            'password' => 'required',
            'email' => 'required|email'
    	]);

        // ვიპოვოთ ადმინისტრატორი მითითებული ელ-ფოსტით
    	$admin = Admin::where('email', $request->email)->first();
        
        // თუ ადმინისტრატრორი ვერ მოიძებნა ან მოიძებნა, მაგრამ არ ემთხვევა პაროლი
    	if(!$admin || ($admin && !Hash::check($request->password, $admin->password)))
        {
            return redirect()->back()->with('login_failed', true);
    	}

        // შევინახოთ ადმინისტრატორის მოდელი სესიაში
    	$request->session()->put('admin' , $admin);
        
        return redirect()->route('AdminMainPage');       
    }
}
                
როგორც ვხედავთ ჰეშირებული პაროლის გადასამოწმებლად გამოვიყენეთ Hash ფასადის check მეთოდი, რომელსაც პირველ პარამეტრად გადავეცით მომხმარებლის მიერ აკრეფილი პაროლი, მეორე პარამეტრად კი ელ-ფოსტის მიხედვით ამოღებული მოდელის password ველის მნიშვნელობა, რომელშიც, თავის დროზე, 'admin123' პაროლი შევინახეთ ჰეშირებული სახით. თუ გადაცემული პარამეტრები ერთმანეთს დაემთხვევა, check მეთოდი აბრუნებს მნიშვნელობას - true, წინააღმდეგ შემთხვევაში ბრუნდება მნიშვნელობა false.

ადმინისტრატორის განყოფილების მთავარი გვერდი

შევქმნათ მარშრუტი ადმინისტრატორის განყოფილების მთავარი გვერდისათვის. routes/web.php : კოპირება

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Admin\LoginController;

Route::get('/admin/login', [LoginController::class, 'showLogin'])->name('ShowLogin');
Route::post('/admin/signin', [LoginController::class, 'login'])->name('AdminLogin');


Route::prefix('admin')->group(function () {
       
    // ადმინისტრატორის პანელის მთავარი გვერდი 
    Route::get('/', function () {
        return view('admin.index');
    })->name('AdminMainPage');

});

                
როგორც ვხედავთ ადმინისტრატორის განყოფილების ბმულები გავაერთიანეთ ჯგუფში პრეფიქსით - 'admin', შესაბამისად, მთავარ გვერდზე მოვხვდებით თუ შევალთ ამ მისამართზე : კოპირება
http://127.0.0.1:8000/admin
                
შევქმნათ resources/views/admin/index.blade.php ფაილი და შევიტანოთ მასში შემდეგი კოდი : კოპირება
<a href="{{ route('AdminLogout') }}">გასვლა</a>
                
ასევე შევქმნათ შესაბამისი მარშრუტი სისტემიდან გამოსასვლელად : კოპირება
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Admin\LoginController;

Route::get('/admin/login', [LoginController::class, 'showLogin'])->name('ShowLogin');
Route::post('/admin/signin', [LoginController::class, 'login'])->name('AdminLogin');
Route::get('admin/logout', [LoginController::class, 'logout'])->name('AdminLogout');           

Route::prefix('admin')->group(function () {
       
    // ადმინისტრატორის პანელის მთავარი გვერდი 
    Route::get('/', function () {
        return view('admin.index');
    })->name('AdminMainPage');

});
                
როგორც ვხედავთ, ამ ეტაპზე სისტემიდან გამოსვლისათვის შევქმენით get ტიპის მარშრუტი. მომავალში ამ მარშრუტს ჩავანაცვლებთ post ტიპის მარშრუტით და ასევე ვისაუბრებთ ამ ცვლილების მიზეზებზეც.
აღვწეროთ logout მეთოდი LoginController კონტროლერში : კოპირება
public function logout(Request $request) 
{
    $request->session()->forget('admin');

    return redirect()->route('ShowLogin');
}
                
თუ ახლა ავტორიზაციას გავივლით, ვიხილავთ resources/views/admin/index.blade.php ფაილს, რომელშიც განთავსებულია სისტემიდან გასვლის ბმული. დავაწვეთ ამ ბმულს, სესიიდან წაიშლება ავტორიზებული ადმინისტრატორის შესაბამისი მოდელი და გადავმისამართდებით ავტორიზაციის გვერდზე.



თუმცა ! ახლა ისევ ვცადოთ შემდეგ მისამართზე შესვლა : კოპირება
http://127.0.0.1:8000/admin
                
ისევ ვიხილავთ resources/views/admin/index.blade.php ფაილს, ანუ ადმინისტრატორის განყოფილების მთავარ გვერდზე მოვხვდებით ისე, რომ ავტორიზაცია არ გვექნება გავლილი ...



შუამავალი Admin

ამ პრობლემის მოგვარებაში დაგვეხმარება შუამავალი, რომელიც გადაამოწმებს, შეანახულია თუ არა სესიაში, ავტორიზებული ადმინისტრატორის შესაბამისი მოდელი, თუ კი - მაშინ შეგვიშვებს ადმინისტრატორის განყოფილებაში, წინააღმდეგ შემთხვევაში გადაგვამისამართებს ავტორიზაციის გვერდზე. კოპირება
php artisan make:middleware Admin
                
შევიტანოთ შესაბამისი ცვლილება app/Http/Kernel.php ფაილში : კოპირება
...

protected $routeMiddleware = [
        
    ...
    
    'admin' => \App\Http\Middleware\Admin::class
];

...
                
თავად შუამავალში კი გვექნება ამდაგვარი სიტუაცია : კოპირება
namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;

class Admin
{
    public function handle(Request $request, Closure $next)
    {
        if( !$request->session()->has('admin'))
        {
            return redirect()->route('ShowLogin');
        }
        
        return $next($request);
    }
}
                
ახლადშექმნილი შუამავალი დავაკავშიროთ ადმინისტრატორის განყოფილების იმ მარშრუტებს, რომლებზე მოხვედრაც არ შეიძლება თუ ადმინსტრატორს ავტორიზაცია არ აქვს გავლილი, routes/web.php ფაილი : კოპირება
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Admin\LoginController;

Route::get('/admin/login', [LoginController::class, 'showLogin'])->name('ShowLogin');
Route::post('/admin/signin', [LoginController::class, 'login'])->name('AdminLogin');
Route::get('admin/logout', [LoginController::class, 'logout'])->name('AdminLogout');

Route::group(['middleware' => ['admin'], 'prefix' => 'admin'], function () {
    
    // ადმინისტრატორის პანელის მთავარი გვერდი 
    Route::get('/', function () {
        return view('admin.index');
    })->name('AdminMainPage');
    
});                
                
ოდნავ დავხვეწოთ ეს კოდი : კოპირება
use Illuminate\Support\Facades\Route;

use App\Http\Middleware\Admin;
use App\Http\Controllers\Admin\LoginController;

Route::group(['middleware' => ['admin'], 'prefix' => 'admin'], function () {
    
    Route::get('/login', [LoginController::class, 'showLogin'])->withoutMiddleware([Admin::class])->name('ShowLogin');
    Route::post('/signin', [LoginController::class, 'login'])->withoutMiddleware([Admin::class])->name('AdminLogin');
    Route::get('/logout', [LoginController::class, 'logout'])->name('AdminLogout');
    
    // ადმინისტრატორის პანელის მთავარი გვერდი 
    Route::get('/', function () {
        return view('admin.index');
    })->name('AdminMainPage');
    
});
                
5. ადმინისტრატორის მხარის მთავარი შაბლონი
შევქმნათ ფაილი resources/views/admin/layout.blade.php და შევიტანოთ მასში შემდეგი კოდი : კოპირება
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
        <title>@yield('title')</title>
        @yield('style')
        <link href="{{ asset('assets/admin/css/styles.css') }}" rel="stylesheet" />
        <script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/js/all.min.js" crossorigin="anonymous"></script>
    </head>
    <body class="sb-nav-fixed">
        
        <nav class="sb-topnav navbar navbar-expand navbar-dark bg-dark">
            
            <!-- ბრენდის ადგილი -->
            <a class="navbar-brand ps-3" href="{{ route('AdminMainPage') }}">Laravel</a>
            
            <!-- მენიუს გადამრთველი -->
            <button class="btn btn-link btn-sm order-1 order-lg-0 me-4 me-lg-0" id="sidebarToggle" href="#!">
                <i class="fas fa-bars"></i>
            </button>
            
            <!-- ზედა მენიუ -->
            <ul class="navbar-nav d-md-inline-block ms-auto me-0 me-md-3 my-2 my-md-0">
                <li class="nav-item dropdown">
                    <a class="nav-link dropdown-toggle" id="navbarDropdown" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
                        <i class="fas fa-user fa-fw"></i>
                    </a>
                    <ul class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
                        <li><a class="dropdown-item" href="{{ route('AdminMainPage') }}">მთავარი</a></li>
                        <li><hr class="dropdown-divider" /></li>
                        <li><a class="dropdown-item" href="{{ route('AdminLogout') }}">გასვლა</a></li>
                    </ul>
                </li>
            </ul>
            
        </nav>
        
        <div id="layoutSidenav">
            
             <div id="layoutSidenav_nav">
                <nav class="sb-sidenav accordion sb-sidenav-dark" id="sidenavAccordion">
                    <div class="sb-sidenav-menu">
                        
                        <!-- გვერდითი მენიუ -->
                        <div class="nav">
                            <a class="nav-link" href="{{ route('AdminMainPage') }}">
                                <div class="sb-nav-link-icon"><i class="fas fa-tachometer-alt"></i></div>
                                მთავარი
                            </a>
                        </div>
                        
                    </div>
                    
                    <div class="sb-sidenav-footer">
                        <div class="small">გამარჯობა {{ Session::get('admin')->name }}</div>
                    </div>
                    
                </nav>
            </div>
            
            <div id="layoutSidenav_content">
                
                <main>
                    
                    <!-- ძირითადი შიგთავსი -->
                    @yield('content')
                    
                </main>
                
                <!-- ძირი -->
                <footer class="py-4 bg-light mt-auto">
                    <div class="container-fluid px-4">
                        <div class="d-flex align-items-center justify-content-between small">
                            <div class="text-muted">© 2021</div>
                            <div>
                                <a href="{{ route('AdminMainPage') }}">ადმინსტრატორის პანელი</a>
                            </div>
                        </div>
                    </div>
                </footer>
                
            </div>
            
        </div>
        
        <script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4="
  crossorigin="anonymous"></script>
        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/js/bootstrap.bundle.min.js" crossorigin="anonymous"></script>
        
        @yield('script')
        
    </body>
</html>
                

ახლა კი resources/views/admin/index.blade.php ფაილში წავშალოთ არსებული კოდი და შევიტანოთ შემდეგი :

კოპირება
@extends('admin.layout')
@section('title','მთავარი')
@section('content')
<div class="container-fluid px-4">
    <h1 class="mt-4">მთავარი</h1>
    <ol class="breadcrumb mb-4">
        <li class="breadcrumb-item active">მთავარი</li>
    </ol>
    <div class="row">

        <!-- აქ განთავსდება მოდულების ბმულები -->

    </div>
</div>
@endsection
                
წითელი ფერით მონიშნულია ის ადგილები სადაც შაბლონიზატორის სინტაქსით დაგვჭირდა ჩარევა, ვფიქრობ ყველაფერი გასაგებია და ახალი არაფერია ამ სინტაქსში, შესაბამისად დაკონკრეტებაზე აღარ დავკარგოთ დრო.

თუ ახლა შევალთ ადმინისტრატორის პანელის მთავარ გვერდზე, ვიხილავთ ამდაგვარ სურათს :





შევნიშნოთ, რომ ამჯერად უკვე ავტორიზაციის გავლა მოგვიწევს ამ გვერდზე მოსახვედრად (ელ_ფოსტა : admin@admin.com, პაროლი : admin123).
6. ადმინისტრატორების დამატება, რედაქტირება, წაშლა და ჩამონათვალის გვერდი

მარშრუტები

პირველ რიგში შევქმნათ მარშრუტები ადმინისტრატორების მოდულისათვის : კოპირება
use App\Http\Controllers\Admin\AdminsController;

Route::resource('admins', AdminsController::class);
                
როგორც ვიცით, Route ფასადის resource მეთოდი დააგენერირებს მარშრუტებს, შემდეგი მახასიათებლების:
  • ტიპი
  • ბმული
  • სახელი
  • კონტროლერის მეთოდი
შემდეგი მნიშვნელობებით :



რესურსის დამატების შაბლონი

შევქმნათ resources/views/admin/admins საქაღალდე, მასში კი create.blade.php ფაილი შემდეგი კოდით : კოპირება
@extends('admin.layout')
@section('title','ადმინსტრატორის დამატება')
@section('content')
<div class="container-fluid px-4">
    <h1 class="mt-4">ადმინსტრატორის დამატება</h1>
    <ol class="breadcrumb mb-4">
        <li class="breadcrumb-item active">ადმინსტრატორის დამატება</li>
    </ol>
    
    @if($errors->any())
        <div class="row">
            <div class="col-md-5 offset-4">
                <div class="alert alert-danger">
                    <ul>
                        @foreach ($errors->all() as $error)
                            <li>{{ $error }}</li>
                        @endforeach
                    </ul>
                </div>
            </div>
        </div>
    @endif
    
    <div class="row">
        <div class="col-md-6 offset-3">
            <form method="post" action="{{ route('admins.store') }}">
                @csrf
                <div class="form-group row">
                    <label class="col-sm-2 col-form-label">სახელი</label>
                    <div class="col-sm-10">
                        <input type="text" name="name" value="{{ old('name') }}" class="form-control">
                    </div>
                </div>
                <div class="form-group row mt-4">
                    <label class="col-sm-2 col-form-label">ელ_ფოსტა</label>
                    <div class="col-sm-10">
                        <input type="email" name="email" value="{{ old('email') }}" class="form-control">
                    </div>
                </div>
                <div class="form-group row mt-4">
                    <label class="col-sm-2 col-form-label">პაროლი</label>
                    <div class="col-sm-10">
                        <input type="password" name="password" class="form-control">
                    </div>
                </div>
                <div class="form-group row mt-4">
                    <label class="col-sm-2 col-form-label"></label>
                    <div class="col-sm-10">
                        <button type="submit" class="btn btn-success">დამატება</button>
                    </div>
                </div>
            </form>
        </div>
    </div>
</div>
@endsection
                

რესურსის დამატების მეთოდები კონტროლერში

გადავინაცვლოთ AdminsController-ში და შევიტანოთ შესაბამისი ცვლილებები : კოპირება
namespace App\Http\Controllers\Admin;

use App\Models\Admin;
use Illuminate\Http\Request;

class AdminsController extends BaseController
{
    // ჩამონათვალის გვერდი
    public function index()
    {
        //
    }

    // ადმინსტრატორის დამატების ფორმის გვერდი
    public function create()
    {
        return view('admin.admins.create');
    }

    // ადმინსტრატორის შენახვა მბ-ში
    public function store(Request $request)
    {
        // ვალიდაცია
        $this->validate($request, [
            'name' => 'required|string|max:255',
            'password' => 'required|string|min:6|max:255',
            'email' => 'required|email|max:255|unique:admins',
        ]);

        $store = Admin::store($request); // true ან false
        
        $request->session()->flash('result', $store);
        
        return redirect()->route('admins.index');
    }

    // ადმინსტრატორის რედაქტირების ფორმის გვერდი
    public function edit($id)
    {
        //
    }

    // ადმინსტრატორის განახლება მბ-ში
    public function update(Request $request, $id)
    {
        //
    }

    // ადმინსტრატორის წაშლა მბ-ში
    public function destroy($id)
    {
        //
    }
}
                
თუ ახლა შევალთ http://127.0.0.1:8000/admin/admins/create მისამართზე, ვიხილავთ ადმინისტრატორის დამატების ფორმას.

store() მეთოდი

განვიხილოთ store მეთოდი. ვალიდაციის მონაკვეთში ჩვენთვის ახალი ერთადერთი ჩანაწერია : 'email' => 'required|email|max:255|unique:admins', unique:admins ჩანაწერი აღნიშნავს, რომ admins ცხრილში email ველის მნიშვნელობა უნდა იყოს უნიკალური თითოეული ჩანაწერისათვის.

როგორც ვიცით, MVC შაბლონის მიხედვით მონაცემთა ბაზებთან სამუშოდ გამოიყენება მოდელები, ამიტომ ინფორმაციის ბაზაში შენახვის ფუნქციონალი აღვწეროთ მოდელში :

$store = Admin::store($request); // true ან false Admin მოდელის store მეთოდი კი გამოიყურება ასე : კოპირება
namespace App\Models;

use Hash;
use Illuminate\Database\Eloquent\Model;

class Admin extends Model 
{
    protected $fillable = ['name','password'];
    
    public static function store($request)
    {
        $item = new Admin();
        
        $item->name = $request->name;
        $item->email = $request->email;
        $item->password = Hash::make($request->password);

        return $item->save(); // true/false;         
    }    
}

                
ამის შემდეგ მოდელის მეთოდის მიერ დაბრუნებულ ლოგიკურ შედეგს ვინახავთ სესიაში და გადავდივართ ადმინისტრატორების ჩამონათვალის გვერდზე : $request->session()->flash('result', $store);

return redirect()->route('admins.index');

ჩამონათვალის გვერდი

ჩამონათვალის გვერდის გამოსატანი მეთოდი იქნება ასეთი : კოპირება
public function index()
{
    $items = Admin::all(); // ყველა ჩანაწერის ამოღება admins ცხრილიდან
    return view('admin.admins.index', compact('items')); // მივამაგროთ ინფორმაცია და დავაბრუნოთ წარმოდგენის ფაილი
}
                

resources/views/admin/admins საქაღალდეში შევქმნათ ფაილი index.blade.php :

კოპირება
@extends('admin.layout')
@section('title','ადმინსტრატორები')
@section('content')
<div class="container-fluid px-4">
    <h1 class="mt-4">ადმინსტრატორები</h1>
    <ol class="breadcrumb mb-4">
        <li class="breadcrumb-item active">
            <a href="{{ route('admins.create') }}" class="btn btn-sm btn-success">დამატება</a>
        </li>
    </ol>
    <div class="row">
        
        @if(Session::has('result'))
        <div class="col-md-12">
            <div class="alert alert-{{ Session::get('result') ? 'success' : 'danger'}}">
                ოპერაცია {{ Session::get('result') ? 'წარმატებით' : 'წარუმატებლად'}} დასრულდა
            </div>
        </div>
        @endif
        
        <div class="col-md-12">
            <table class="table">
                <thead>
                    <tr>
                        <th scope="col">#</th>
                        <th scope="col">სახელი</th>
                        <th scope="col">ელ_ფოსტა</th>
                        <th scope="col"></th>
                    </tr>
                </thead>
                <tbody>
                    @foreach($items as $key => $item)
                        <tr>
                            <th scope="row">{{ ++$key }}</th>
                            <td>{{ $item->name }}</td>
                            <td>{{ $item->email }}</td>
                            <td>
                                <a href="{{ route('admins.edit', $item->id) }}" class="btn btn-sm btn-primary" style="float: left; margin-right: 5px">
                                    <i class="fa fa-edit"></i> 
                                </a>
                                
                                @if($item->id != 1)
                                    <form action="{{ route('admins.destroy', $item->id) }}" method="post">
                                        @csrf
                                        <input type="hidden" name="_method" value="delete">
                                        <a href="#!" class="btn btn-sm btn-danger btn-destroy">
                                            <i class="fa fa-trash"></i> 
                                        </a>
                                    </form>
                                @endif
                                
                            </td>
                        </tr>
                    @endforeach
                </tbody>
            </table>
        </div>
    </div>
</div>
@endsection

@section('script')
<script>
    
    $('.btn-destroy').on('click', function(){
        
        if(confirm('დარწმუნებული ხართ ?'))
        {
            $(this).parent('form').submit();         
        }
        
    });
    
</script>
@endsection

                

ჩანაწერის წაშლა

როგორც ვიცით Route ფასადის resource მეთოდი ჩანაწერების წაშლისათვის აგენერირებს DELETE ტიპის მარშრუტს. HTML ფორმები კი მხარს არ უჭერენ PUT, PATCH და DELETE მეთოდებს. ასეთ შემთხვევებში უნდა შევქმნათ ფორმები, რომლებსაც ექნებათ hidden ტიპის _method სახელიანი და ასევე csrf ველები : კოპირება
<form action="/example" method="POST">
    <input type="hidden" name="_method" value="delete">
    <input type="hidden" name="_token" value="{{ csrf_token() }}">
</form>
                
იგივეს ჩაწერა შეიძლება დირექტივების მეშვეობითაც : კოპირება
<form action="/example" method="POST">
    @method('delete')
    @csrf
</form>
                
სწორედ ეს გავაკეთეთ index.blade.php ფაილის შემდეგ ფრაგმენტში : კოპირება
@if($item->id != 1)
    <form action="{{ route('admins.destroy', $item->id) }}" method="post">
        @csrf
        <input type="hidden" name="_method" value="delete">
        <a href="#!" class="btn btn-sm btn-danger btn-destroy">
            <i class="fa fa-trash"></i> 
        </a>
    </form>
@endif
                
რაც შეეხება პირობით ოპერატორს :
@if($item->id != 1)

...

@endif
                
ბუნებრივია მომხმარებელს არ უნდა მივცეთ საშუალება, რომ წაშალოს admins ცხრილის ყველა ჩანაწერი, რადგან ამ შემთხვევაში სისტემიდან გამოსვლის შემდეგ იგი ხელახლა ვეღარ გაივლის ავტორიზაციას ვინაიდან ბაზაში აღარ მოიძებნება აღარცერთი ადმინსტრატორი :)))

წაშლის სისტემას ასევე მიმაგრებული აქვს მცირედი javascript-იც :

კოპირება
@section('script')
<script>
    
    $('.btn-destroy').on('click', function(){
        
        if(confirm('დარწმუნებული ხართ ?'))
        {
            $(this).parent('form').submit();         
        }
        
    });
    
</script>
@endsection
                
ანუ წაშლის ღილაკზე დაჭერისას მომხმარებელს ვეკითხებით ნამდვილად სურს თუ არა ჩანაწერის წაშლა და დასტურის შემთხვევაში ვახდენთ იმ ფორმის გაგზავნას, რომელიც წაშლის მარშრუტთან არის დაკავშირებული.

ჩანაწერის წაშლის მეთოდი AdminsController კონტროლერში გამოიყურება ასე :

კოპირება
public function destroy(Request $request, $id)
{
    if($id == 1)
    {
        return redirect()->back();  
    }

    $delete = Admin::find($id)->delete();
    $request->session()->flash('result', $delete);

    return redirect()->back();      
}
                

ჩანაწერის რედაქტირება

ჩანაწერების რედაქტირების გვერდზე გადასასვლელი ბმული შემდეგნაირად გენერირდება ჩამონათვალის გვერდზე : კოპირება
...

<a href="{{ route('admins.edit', $item->id) }}" class="btn btn-sm btn-primary" style="float: left; margin-right: 5px">
    <i class="fa fa-edit"></i> 
</a>

...
                
კონტროლერის მეთოდი კი, რომელიც რედაქტირების ფორმის ჩატვირთვას უზრუნველჰყოფს, ასეთია : კოპირება
public function edit($id)
{
    $item = Admin::findOrFail($id);

    return view('admin.admins.edit', compact('item'));
}
                
resources/views/admin/admins საქაღალდეში შევქმნათ edit.blade.php ფაილი შემდეგი კოდით : კოპირება
@extends('admin.layout')
@section('title','ადმინსტრატორის რედაქტირება')
@section('content')
<div class="container-fluid px-4">
    <h1 class="mt-4">მთავარი</h1>
    <ol class="breadcrumb mb-4">
        <li class="breadcrumb-item active">ადმინსტრატორის რედაქტირება</li>
    </ol>
    
    @if($errors->any())
        <div class="row">
            <div class="col-md-5 offset-4">
                <div class="alert alert-danger">
                    <ul>
                        @foreach ($errors->all() as $error)
                            <li>{{ $error }}</li>
                        @endforeach
                    </ul>
                </div>
            </div>
        </div>
    @endif
    @if(Session::has('result'))
        <div class="col-md-12">
            <div class="alert alert-{{ Session::get('result') ? 'success' : 'danger'}}">
                ოპერაცია {{ Session::get('result') ? 'წარმატებით' : 'წარუმატებლად'}} დასრულდა
            </div>
        </div>
    @endif
    
    <div class="row">
        <div class="col-md-6 offset-3">
            <form method="post" action="{{ route('admins.update', $item->id) }}">
                @csrf
                @method('put')
                <div class="form-group row">
                    <label class="col-sm-2 col-form-label">სახელი</label>
                    <div class="col-sm-10">
                        <input type="text" name="name" value="{{ $item->name }}" class="form-control">
                    </div>
                </div>
                <div class="form-group row mt-4">
                    <label class="col-sm-2 col-form-label">ელ_ფოსტა</label>
                    <div class="col-sm-10">
                        <input type="email" name="email" value="{{ $item->email }}" class="form-control">
                    </div>
                </div>
                <div class="form-group row mt-4">
                    <label class="col-sm-2 col-form-label">პაროლი</label>
                    <div class="col-sm-10">
                        <input type="password" name="password" class="form-control">
                    </div>
                </div>
                <div class="form-group row mt-4">
                    <label class="col-sm-2 col-form-label"></label>
                    <div class="col-sm-10">
                        <button type="submit" class="btn btn-success">განახლება</button>
                    </div>
                </div>
            </form>
        </div>
    </div>
</div>
@endsection                
                

update() მეთოდი

კოპირება
namespace App\Http\Controllers\Admin;

use App\Models\Admin;
use Illuminate\Http\Request;

class AdminsController extends BaseController
{
    // ჩამონათვალის გვერდი
    public function index()
    {
        $items = Admin::all();
        return view('admin.admins.index', compact('items'));
    }

    // ადმინსტრატორის დამატების ფორმის გვერდი
    public function create()
    {
        return view('admin.admins.create');
    }

    // ადმინსტრატორის შენახვა მბ-ში
    public function store(Request $request)
    {
        // ვალიდაცია
        $this->validate($request, [
            'name' => 'required|string|max:255',
            'password' => 'required|string|max:255',
            'email' => 'required|email|max:255|unique:admins',
        ]);

        $store = Admin::store($request); // true ან false
        
        $request->session()->flash('result', $store);
        
        return redirect()->route('admins.index');
    }

    // ადმინსტრატორის რედაქტირების ფორმის გვერდი
    public function edit($id)
    {
        $item = Admin::findOrFail($id);
        
        return view('admin.admins.edit', compact('item'));
    }
    
    // ადმინსტრატორის განახლება მბ-ში
    public function update(Request $request, $id)
    {
        // ვალიდაცია
        $this->validate($request, [
            'name' => 'required|string|max:255',
            'email' => 'required|email|max:255|unique:admins,email,' . $id,
        ]);
        
        $item = Admin::findOrFail($id);
        $update = Admin::updateItem($request, $item); // true ან false
        $request->session()->flash('result', $update);
        
        return redirect()->back();      
    }

    // ადმინსტრატორის წაშლა მბ-ში
    public function destroy(Request $request, $id)
    {
        if($id == 1)
        {
            return redirect()->back();  
        }
        
        $delete = Admin::find($id)->delete();
        $request->session()->flash('result', $delete);
        
        return redirect()->back();      
    }
}
                
განვიხილოთ კონტროლერის update მეთოდი, კერძოდ კი ვალიდაციის ეს ფრაგმენტი : 'email' => 'required|email|max:255|unique:admins,email,' . $id, როგორც ვიცით, unique:admins ჩანაწერი აღნიშნავს, რომ admins ცხრილში email ველის მნიშვნელობა უნდა იყოს უნიკალური თითოეული ჩანაწერისათვის. ,' . $id ჩანაწერი კი აღნიშნავს, რომ მომხმარებლის მიერ აკრეფილი ელ_ფოსტა უნდა გააჩნდეს მხოლოდ ამ id-ის მქონე ჩანაწერს.

შემდეგ ისევ იგივე სქემით მეორდება ყველაფერი რაც ჩანაწერის დამატების მეთოდში გვქონდა, უბრალოდ ამჯერად Admin მოდელის updateItem მეთოდს მივმართავთ, რომელიც გამოიყურება ასე :

კოპირება
namespace App\Models;

use Hash;
use Illuminate\Database\Eloquent\Model;

class Admin extends Model 
{
    protected $fillable = ['name','password'];
    
    public static function store($request) 
    {
        $item = new Admin();
        
        $item->name = $request->name;
        $item->email = $request->email;
        $item->password = Hash::make($request->password);

        return $item->save();         
    }
    
    public static function updateItem($request, $item)
    {
        if ($request->password) 
        {
            $update = $item->update([
                'name' => $request->name,
                'email' => $request->email,
                'password' => Hash::make($request->password)
            ]);
        } 
        else 
        {
            $update = $item->update([
                'name' => $request->name,
                'email' => $request->email
            ]);
        }
        
        return $update;           
    }
}
                
ბოლოს ისღა დაგვრჩენია, რომ მოდულის ბმულები ჩავამატოთ resources/views/admin/layout.blade.php და resources/views/admin/index.blade.php ფაილებში, ანუ გვერდით მენიუში და ადმინისტრატორის განყოფილების მთავარ გვერდზე : კოპირება
...

<!-- გვერდითი მენიუ -->
<div class="nav">
    <a class="nav-link" href="{{ route('AdminMainPage') }}">
        <div class="sb-nav-link-icon"><i class="fas fa-tachometer-alt"></i></div>
        მთავარი
    </a>
    <a class="nav-link" href="{{ route('admins.index') }}">
        <div class="sb-nav-link-icon"><i class="fas fa-user"></i></div>
        ადმინისტრატორები
    </a>
</div>

...
                
კოპირება
...

<div class="container-fluid px-4">
    <h1 class="mt-4">მთავარი</h1>
    <ol class="breadcrumb mb-4">
        <li class="breadcrumb-item active">მთავარი</li>
    </ol>
    <div class="row">
        <div class="col-xl-3 col-md-6">
            <div class="card bg-primary text-white mb-4">
                <div class="card-body">ადმინსტრატორები</div>
                <div class="card-footer d-flex align-items-center justify-content-between">
                    <a class="small text-white stretched-link" href="{{ route('admins.index') }}">სრულად</a>
                    <div class="small text-white"><i class="fas fa-angle-right"></i></div>
                </div>
            </div>
        </div>
    </div>
</div>

...
                
7. საკონტაქტო ინფორმაციის მოდული
როგორც გვახსოვს, საკონტაქტო ინფორმაციისათვის შევქმენით contacts ცხრილი. ბუნებრივია ეს ინფორმაცია არც ისე ხშირად შეიცვლება და არც ჩანაწერების დამატებისა და ჩამონათვალის გვერდები გვექნება, რადგან ცხრილში სულ ერთ ჩანაწერს შევიტანთ და შემდეგ დავარედაქტირებთ ხოლმე მას.

საწყისი ინფორმაციის შეტანა საკონტაქტო ინფორმაციის ცხრილში

კოპირება
php artisan make:seeder ContactSeeder
                
სიდერში მივმართოთ DB ფასადს და განვსაზღვროთ ცხრილში შესატანი ინფორმაცია : კოპირება
namespace Database\Seeders;

use DB;
use Illuminate\Database\Seeder;

class ContactSeeder extends Seeder
{
    public function run()
    {
        DB::table('contacts')->insert([
            [
                'phone' => '557 34 43 05',
                'email' => 'vasil.nadiradze@gmail.com'
            ]
        ]);
    }
}
                
გავუშვათ ინფორმაციის შეტანის ბრძანება : კოპირება
php artisan db:seed --class=ContactSeeder
                

მარშრუტები

კოპირება
use Illuminate\Support\Facades\Route;

use App\Http\Middleware\Admin;
use App\Http\Controllers\Admin\AdminsController;
use App\Http\Controllers\Admin\ContactsController;
use App\Http\Controllers\Admin\LoginController;

Route::get('/', function () {
    return view('welcome');
});

Route::group(['middleware' => ['admin'], 'prefix' => 'admin'], function () {
    
    // ავტორიზაცია და სისტემიდან გასვლა
    Route::get('/login', [LoginController::class, 'showLogin'])->withoutMiddleware([Admin::class])->name('ShowLogin');
    Route::post('/signin', [LoginController::class, 'login'])->withoutMiddleware([Admin::class])->name('AdminLogin');
    Route::get('/logout', [LoginController::class, 'logout'])->name('AdminLogout');
    
    // ადმინისტრატორის პანელის მთავარი გვერდი 
    Route::get('/', function () {
        return view('admin.index');
    })->name('AdminMainPage');
    
    // ადმინისტრატორები
    Route::resource('admins', AdminsController::class);
    
    // საკონტაქტო ინფორმაციის გვერდი
    Route::resource('contacts', ContactsController::class, ['only' => ['edit','update']]);
    
});
                
როგორც ვხედავთ მარშრუტები განვსაზღვრეთ, მხოლოდ რედაქტირების გვერდისა და უშუალოდ ინფორმაციის განახლების მეთოდებისათვის. საკონტაქტო ინფორმაციის რედაქტირებისათვის უნდა მივმართოთ შემდეგ ბმულს: კოპირება
http://127.0.0.1:8000/admin/contacts/1/edit
                
contacts ცხრილში ინფორმაცია სიდერის მეშვეობით შევიტანეთ, შესაბამისად პირველი ჩანაწერის id იქნება 1.

კონტროლერი

როგორც ადრე აღვნიშნეთ, კარგი პრაქტიკაა მბ-სთან სამუშო ფუნქციონალის მოდელებში აღწერა, მაგრამ დროის ეკონომიის მიზნით და ასევე ამ მოდულის სიმარტივის გათვალისწინებით, ამჯერად კონტროლერშივე მივმართოთ DB ფასადს : კოპირება
php artisan make:controller Admin/ContactsController --resource
                
კოპირება
namespace App\Http\Controllers\Admin;

use DB;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;

class ContactsController extends Controller
{
    public function edit($id)
    {
        $item = DB::table('contacts')->first();
        
        return view('admin.contact.edit', compact('item'));
    }
    
    public function update(Request $request, $id)
    {
        // ვალიდაცია
        $this->validate($request, [
            'phone' => 'required|string|max:255',
            'email' => 'required|email|max:255'
        ]);
        
        $update = $update = DB::table('contacts')->where('id',$id)->update([
            'phone' => $request->phone,
            'email' => $request->email
        ]);
        
        $request->session()->flash('result', true);
        
        return redirect()->back();  
    }
}                
                

ინფორმაციის რედაქტირების გვერდი

შევქმნათ საქაღალდე resources/views/admin/contact, მასში კი ფაილი edit.blade.php შემდეგი კოდით : კოპირება
@extends('admin.layout')
@section('title','საკონტაქტო ინფორმაციის რედაქტირება')
@section('content')
<div class="container-fluid px-4">
    <h1 class="mt-4">მთავარი</h1>
    <ol class="breadcrumb mb-4">
        <li class="breadcrumb-item active">
            
            <a href="" class="btn btn-sm btn-success">
                {{ Cache::has('contacts') ? 'ქეშის გასუფთავება' : 'ქეშირება' }}
            </a>
            
        </li>
    </ol>
    
    @if($errors->any())
        <div class="row">
            <div class="col-md-5 offset-4">
                <div class="alert alert-danger">
                    <ul>
                        @foreach ($errors->all() as $error)
                            <li>{{ $error }}</li>
                        @endforeach
                    </ul>
                </div>
            </div>
        </div>
    @endif
    @if(Session::has('result'))
        <div class="col-md-12">
            <div class="alert alert-{{ Session::get('result') ? 'success' : 'danger'}}">
                ოპერაცია {{ Session::get('result') ? 'წარმატებით' : 'წარუმატებლად'}} დასრულდა
            </div>
        </div>
    @endif
    
    <div class="row">
        <div class="col-md-6 offset-3">
            <form method="post" action="{{ route('contacts.update', $item->id) }}">
                @csrf
                @method('put')
                <div class="form-group row">
                    <label class="col-sm-2 col-form-label">ტელ</label>
                    <div class="col-sm-10">
                        <input type="text" name="phone" value="{{ $item->phone }}" class="form-control">
                    </div>
                </div>
                <div class="form-group row mt-4">
                    <label class="col-sm-2 col-form-label">ელ_ფოსტა</label>
                    <div class="col-sm-10">
                        <input type="email"  name="email" value="{{ $item->email }}" class="form-control">
                    </div>
                </div>
                <div class="form-group row mt-4">
                    <label class="col-sm-2 col-form-label"></label>
                    <div class="col-sm-10">
                        <button type="submit" class="btn btn-success">განახლება</button>
                    </div>
                </div>
            </form>
        </div>
    </div>
</div>
@endsection                
                

ინფორმაციის ქეშირება

როგორც ვიცით, კონკრეტული ინფორმაციის მონაცემთა ბაზიდან ამოღებას სჭირდება დამატებითი დროითი რესურსი, რაც, რა თქმა უნდა, გავლენას ახდენს აპლიკაციის ჩატვირთვის სისწრაფეზე, რაც უფრო დიდი და კომპლექსურია ინფორმაცია, მით მეტი დრო სჭირდება მის ამოღებას ბაზიდან. ამ პრობლემის მოსაგვარებლად შეგვიძლია გამოვიყენოთ ქეშირება.

ქეშირებების კონფიგურაციული პარამეტრები აღწერილია config/cache.php ფაილში, დავაკვირდეთ მის შემდეგ ფრაგმენტს :

'default' => env('CACHE_DRIVER', 'file'),
                
ეს ჩანაწერი ნიშნავს, რომ ქეშირებული ინფორმაციები შეინახება ფაილებში.

ქეშირებებთან სამუშაოდ გამოიყენება Cache ფასადი :

კოპირება
namespace App\Http\Controllers;

use Illuminate\Support\Facades\Cache;

class SomeController extends Controller
{
    public function index()
    {
        $value = Cache::get('key');
    }
}
                
შეგვიძლია, რომ Cache ფასადის get მეთოდს გადავცეთ დამატებითი პარამეტრიც, რომელსაც სისტემა იმ შემთხვევაში გამოიყენებს თუ ქეშში ვერ მოიძებნება მითითებული გასაღების შესაბამისი მნიშვნელობა :
$value = Cache::get('key', 'default');
                

ინფორმაციის შენახვა ქეშში

ქეშში ინფორმაციის შენახვის სინტაქსი შემდეგნაირია : კოპირება
$value = Cache::remember('users', $seconds, function () {
    return DB::table('users')->get();
});
                
ანუ მივმართეთ Cache ფასადის remember მეთოდს, რომელსაც პარამეტრებად გადავეცით: გასაღების დასახელება, რომელსაც მიემაგრება კონკრეტული ქეშირებული ინფორმაცია ('უჯრის დასახერლება', რომელშიც მომხმარებლების შესახებ ამოღებული და ქეშირებული ინფორმაცია შეინახება), ასევე ქეშირების ხანგრძლივობა წამებში და ფუნქცია-დამმუშავებელი, რომელშიც, უშუალოდ დასაქეში ინფორმაციის დაფიქსირება ხდება.

ინფორმაციის წაშლა ქეშში

ქეშში ინფორმაციის წასაშლელად უნდა მივმართოთ Cache ფასადის forget მეთოდს :
Cache::forget('key');
                

ინფორმაციის არსებობა/არარსებობის გადამოწმება ქეშში

იმის გასაგებად, არსებობს თუ არა ქეშში კონკრეტული გასაღების შესაბამისი ინფორმაცია, უნდა მივმართოთ Cache ფასადის has მეთოდს :
Cache::has('key'); // true/false
                

საკონტაქტო ინფორმაციის შენახვა ქეშში

მარშრუტი : კოპირება
use Illuminate\Support\Facades\Route;

use App\Http\Middleware\Admin;
use App\Http\Controllers\Admin\AdminsController;
use App\Http\Controllers\Admin\ContactsController;
use App\Http\Controllers\Admin\LoginController;

Route::get('/', function () {
    return view('welcome');
});

Route::group(['middleware' => ['admin'], 'prefix' => 'admin'], function () {
    
    // ავტორიზაცია და სისტემიდან გასვლა
    Route::get('/login', [LoginController::class, 'showLogin'])->withoutMiddleware([Admin::class])->name('ShowLogin');
    Route::post('/signin', [LoginController::class, 'login'])->withoutMiddleware([Admin::class])->name('AdminLogin');
    Route::get('/logout', [LoginController::class, 'logout'])->name('AdminLogout');
    
    // ადმინისტრატორის პანელის მთავარი გვერდი 
    Route::get('/', function () {
        return view('admin.index');
    })->name('AdminMainPage');
    
    // ადმინისტრატორები
    Route::resource('admins', AdminsController::class);
    
    // საკონტაქტო ინფორმაციის გვერდი
    Route::resource('contacts', ContactsController::class, ['only' => ['edit','update']]);
    Route::get('/contacts/cache', [ContactsController::class, 'cache'])->name('contacts.cache');

});
                
resources/views/admin/contact/edit.blade.php შაბლონში არსებული ქეშირების ბმული დავაკავშიროთ ახლადშექმნილ მარშრუტთან : კოპირება
...

<ol class="breadcrumb mb-4">
    <li class="breadcrumb-item active">
        
        <a href="{{ route('contacts.cache') }}" class="btn btn-sm btn-success">
            {{ Cache::has('contacts') ? 'ქეშის გასუფთავება' : 'ქეშირება' }}
        </a>
        
    </li>
</ol>

...    
                
ახლა შევქმნათ შესაბამისი მეთოდი : კოპირება
namespace App\Http\Controllers\Admin;

use DB;
use Cache;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;

class ContactsController extends Controller
{
    public function edit($id)
    {
        $item = DB::table('contacts')->first();
        
        return view('admin.contact.edit', compact('item'));
    }
    
    public function update(Request $request, $id)
    {
        // ვალიდაცია
        $this->validate($request, [
            'phone' => 'required|string|max:255',
            'email' => 'required|email|max:255'
        ]);
        
        $update = $update = DB::table('contacts')->where('id',$id)->update([
            'phone' => $request->phone,
            'email' => $request->email
        ]);
        
        $request->session()->flash('result', true);
        
        return redirect()->back();  
    }
    
    public function cache(Request $request)
    {
        // თუ საკონტაქტო ინფორმაცია უკვე შენახულია ქეშში
        if(Cache::has('contacts'))
        {
            // წავშალოთ იგი
            Cache::forget('contacts');
        }
        else // თუ არადა 
        {
            // შევინახოთ 
            Cache::remember('contacts', 3600, function () {
                return DB::table('contacts')->first();
            });
        }
        
        $request->session()->flash('result', true);
        
        return redirect()->back();  
    }
}
                
ქეშირებული ინფორმაციები ინახება storage/framework/cache/data საქაღალდეში. თუ საკონტაქტო ინფორმაციის ქეშში შენახვის შემდეგ, კონტროლერის მეთოდში ამდაგვარ ჩანაწერს შევიტანთ : კოპირება
public function edit($id)
{
    
    echo '<pre>';
    print_r(Cache::get('contacts'));
    echo '</pre>';
    die;
    

    $item = DB::table('contacts')->first();

    return view('admin.contact.edit', compact('item'));
}

                
ვიხილავთ შემდეგ ინფორმაციას :
stdClass Object
(
    [id] => 1
    [phone] => 557 34 43 05476
    [email] => vasil.nadiradze@gmail.com
    [created_at] => 
    [updated_at] => 
)

                
ქეშში შენახულ საკონტაქტო ინფორმაციას გამოვიყენებთ მომხმარებლის მხარის სამუშაოებისას.

ახლა ტრადიციულად, საკონტაქტო ინფორმაციის მოდულის ბმულები ჩავამატოთ resources/views/admin/layout.blade.php და resources/views/admin/index.blade.php ფაილებში, ანუ გვერდით მენიუში და ადმინისტრატორის განყოფილების მთავარ გვერდზე :

კოპირება
...

<!-- გვერდითი მენიუ -->
<div class="nav">
    <a class="nav-link" href="{{ route('AdminMainPage') }}">
        <div class="sb-nav-link-icon"><i class="fas fa-tachometer-alt"></i></div>
        მთავარი
    </a>
    <a class="nav-link" href="{{ route('admins.index') }}">
        <div class="sb-nav-link-icon"><i class="fas fa-user"></i></div>
        ადმინისტრატორები
    </a>
    <a class="nav-link" href="{{ route('contacts.edit', 1) }}">
        <div class="sb-nav-link-icon"><i class="fas fa-phone"></i></div>
        საკონტაქტო ინფორმაცია
    </a>
</div>

...
                
კოპირება
...

<div class="container-fluid px-4">
    <h1 class="mt-4">მთავარი</h1>
    <ol class="breadcrumb mb-4">
        <li class="breadcrumb-item active">მთავარი</li>
    </ol>
    <div class="row">
        <div class="col-xl-3 col-md-6">
            <div class="card bg-primary text-white mb-4">
                <div class="card-body">ადმინსტრატორები</div>
                <div class="card-footer d-flex align-items-center justify-content-between">
                    <a class="small text-white stretched-link" href="{{ route('admins.index') }}">სრულად</a>
                    <div class="small text-white"><i class="fas fa-angle-right"></i></div>
                </div>
            </div>
        </div>
        <div class="col-xl-3 col-md-6">
            <div class="card bg-success text-white mb-4">
                <div class="card-body">საკონტაქტო ინფორმაცია</div>
                <div class="card-footer d-flex align-items-center justify-content-between">
                    <a class="small text-white stretched-link" href="{{ route('contacts.edit', 1) }}">სრულად</a>
                    <div class="small text-white"><i class="fas fa-angle-right"></i></div>
                </div>
            </div>
        </div>
    </div>
</div>

...
                
8. მრავალენოვანი სისტემის გამართვა
მრავალენოვანი სისტემის გასამართად გამოვიყენოთ ეს პაკეტი : კოპირება
composer require mcamara/laravel-localization
                
იმისათვის რათა უფრო თვალსაჩინოდ ვნახოთ თუ რა კონფიგურაციულ პარამეტრებს მოიცავს აღნიშნული პაკეტი, გავუშვათ შემდეგი ბრძანება : კოპირება
php artisan vendor:publish --provider="Mcamara\LaravelLocalization\LaravelLocalizationServiceProvider"
                
ბრძანების შედეგად შეიქმნება ფაილი config/laravellocalization.php, რომელშიც ბრუნდება ჩვეულებრივი მასივი, რომლის პირველი გასაღებიცაა - supportedLocales, სადაც შენახულია, პაკეტის ინსტალაციის შემდეგ ხელმისაწვდომი ენების საკმაოდ გრძელი სია. განვაკომენტაროთ ჩვენთვის სასურველი ენების (ქართული, ინგლისური) შესაბამისი ჩანაწერები, დანარჩენი ყველა კი უნდა იყოს დაკომენტარებული : კოპირება
... 

'supportedLocales' => [
    
    ... 

    'ka' => ['name' => 'Georgian', 'script' => 'Geor', 'native' => 'ქართული', 'regional' => 'ka_GE'],
    'en' => ['name' => 'English','script' => 'Latn', 'native' => 'English', 'regional' => 'en_GB'],

    ...

],

                
ამავე ფაილში აღწერილი useAcceptLanguageHeader პარამეტრის მნიშვნელობად მივუთითოთ false : კოპირება
'useAcceptLanguageHeader' => false,
                

ძირითადი ენის განსაზღვრა

დავუშვათ გვინდა, რომ აპლიკაციის ძირითადი ენა იყოს ქართული, ამისათვის config/app.php ფაილში აღწერილი locale პარამეტრის მნიშვნელობად უნდა მივუთითოთ ქართული ენის ინდექსი : კოპირება
...

'locale' => 'ka',

...
                

შუამავლები

გადამისამართებები დაგვჭირდება სხვა შემთხვევებშიც, მაგალითად თუ მომხმარებელს უნდა შესვლა http://127.0.0.1:8000/test მისამართზე, სისტემამ ავტომატურად უნდა დააგენერიროს ძირითადი ენის შესაბამისი ბმული (http://127.0.0.1:8000/ka/test), ენების გათვალისწინება დაგვჭირდება სესიებთან მუშაობისასაც და ა.შ, ამიტომ app/Http/Kernel.php ფაილში აღწერილ $routeMiddleware თვისებაში უნდა ჩავამატოთ შემდეგი შუამავლები : კოპირება
protected $routeMiddleware = [

    ... 

    'localize' => \Mcamara\LaravelLocalization\Middleware\LaravelLocalizationRoutes::class,
    'localizationRedirect' => \Mcamara\LaravelLocalization\Middleware\LaravelLocalizationRedirectFilter::class,
    'localeSessionRedirect' => \Mcamara\LaravelLocalization\Middleware\LocaleSessionRedirect::class,
    'localeViewPath' => \Mcamara\LaravelLocalization\Middleware\LaravelLocalizationViewPath::class

];
                

მარშრუტები

ამ ეტაპზე მრავალენოვანი სისტემა გამოვიყენოთ მხოლოდ მომხმარებლის მხარისათვის, routes/web.php ფაილი : კოპირება
use Illuminate\Support\Facades\Route;

use App\Http\Middleware\Admin;
use App\Http\Controllers\Admin\AdminsController;
use App\Http\Controllers\Admin\ContactsController;
use App\Http\Controllers\Admin\LoginController;


Route::group(['prefix' => LaravelLocalization::setLocale(),'middleware' => [ 'localeSessionRedirect', 'localizationRedirect', 'localeViewPath' ]], function(){
   
    Route::get('/', function () {
        return view('welcome');
    });
    
});


Route::group(['middleware' => ['admin'], 'prefix' => 'admin'], function () {
    
    // ავტორიზაცია და სისტემიდან გასვლა
    Route::get('/login', [LoginController::class, 'showLogin'])->withoutMiddleware([Admin::class])->name('ShowLogin');
    Route::post('/signin', [LoginController::class, 'login'])->withoutMiddleware([Admin::class])->name('AdminLogin');
    Route::get('/logout', [LoginController::class, 'logout'])->name('AdminLogout');
    
    // ადმინისტრატორის პანელის მთავარი გვერდი 
    Route::get('/', function () {
        return view('admin.index');
    })->name('AdminMainPage');
    
    // ადმინისტრატორები
    Route::resource('admins', AdminsController::class);
    
    // საკონტაქტო ინფორმაციის გვერდი
    Route::resource('contacts', ContactsController::class, ['only' => ['edit','update']]);
    Route::get('/contacts/cache', [ContactsController::class, 'cache'])->name('contacts.cache');

});      
                
თუ ახლა შევალთ შემდეგ მისამართზე : კოპირება
http://127.0.0.1:8000
                
სისტემა ავტომატურად გადაგვამისამართებს, ძირითად ენად მითითებული ენის (ქართულის) შესაბამის ბმულზე : კოპირება
http://127.0.0.1:8000/ka
                
9. სიახლეების მოდული

Article მოდელი

შევქმნათ სიახლეების ძირითად ცხრილთან (articles) და მოდულთან სამუშაო მოდელი - Article : კოპირება
php artisan make:model Article
                
კოპირება
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Article extends Model
{
    // 
}
                

ArticlesTranslate მოდელი

როგორც ვიცით სიახლეებთან სამუშოდ გვაქვს ორი - ძირითადი (articles) და სათარგმნი (articles_translates) ცხრილები. შევქმნათ სიხლეების თარგმანებთან სამუშო მოდელი : კოპირება
php artisan make:model ArticlesTranslate
                
კოპირება
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class ArticlesTranslate extends Model
{
    //
}
                

ურთიერთკავშირი ცხრილებს შორის

გამომდინარე იქიდან, რომ ჩვენი საიტი ორენოვანია, სიახლიეების ძირითადი ძირითადი ცხრილის თითოეულ ჩანაწერს, სათარგმნ ცხრილში 'ეყოლება' ორი 'შვილობილი' ჩანაწერი. აღვწეროთ ეს დამოკიდებულება Article და ArticlesTranslate მოდელებში : კოპირება
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Article extends Model
{
    public function translates()
    {
        return $this->hasMany(ArticlesTranslate::class);
    } 
}
                
კოპირება
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class ArticlesTranslate extends Model
{
    public function article()
    {
        return $this->belongsTo(Article::class);
    }
}
                

ArticlesController

ახლა შევქმნათ სიახლეების მოდულთან სამუშო კონტროლერი - ArticlesController, რომელიც, ასევე Admin/BaseController კონტროლერის მემკვიდრე იქნება : კოპირება
php artisan make:controller Admin/ArticlesController --resource
                
კოპირება
namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;

class ArticlesController extends BaseController
{
    public function index()
    {
        //
    }

    public function create()
    {
        //
    }

    public function store(Request $request)
    {
        //
    }

    public function show($id)
    {
        //
    }

    public function edit($id)
    {
        //
    }

    public function update(Request $request, $id)
    {
        //
    }

    public function destroy($id)
    {
        //
    }
}
                

ხელმისაწვდომი ენების გაზიარება წარმოდგენის ფაილებში

სისტემა არის ორენოვანი, ეს იმას ნიშნავს, რომ სიახლეების დამატებისა და რედაქტირების წარმოდგენის ფაილებში, ისეთი ველებისათვის, როგორებიცაა - სათაური, აღწერა და სრული ტექსტი, დაგვჭირდება ცალ-ცალკე ველები. ბუნებრივია ამ ფაილებს საიდანღაც უნდა მივაწოდოთ ინფორმაცია ხელმისაწვდომი ენების შესახებ. ეს გავაკეთოთ BaseController-ში : კოპირება
namespace App\Http\Controllers\Admin;

use View;
use LaravelLocalization;
use App\Http\Controllers\Controller;

class BaseController extends Controller
{
    
    public function __construct() 
    {
        View::share('locales', LaravelLocalization::getSupportedLocales()); 
    }  
    
}
                
როგორც ვიცით, LaravelLocalization ფასადი ხელმისაწვდომია მრავალენოვან სისტემასთან სამუშო პაკეტის ინსტალაციის შემდეგ, მისი getSupportedLocales() მეთოდი კი ხელმისაწვდომ ენებს გვიბრუნებს შემდეგი სახით :



$locales ცვლადის მეშვეობით ახლა უკვე შეგვიძლია წარმოდგენის ფაილებში ვიმუშავოთ ენებთან.

მარშრუტები

შევქმნათ მარშრუტები სიახლეების მოდულისათვის : კოპირება
use App\Http\Controllers\Admin\ArticlesController;

Route::resource('articles', ArticlesController::class);
                

რესურსის დამატების შაბლონი

შევქმნათ resources/views/admin/articles საქაღალდე, მასში კი create.blade.php ფაილი შემდეგი კოდით : კოპირება
@extends('admin.layout')
@section('title','სიახლის დამატება')
@section('content')
<div class="container-fluid px-4">
    <h1 class="mt-4">მთავარი</h1>
    <ol class="breadcrumb mb-4">
        <li class="breadcrumb-item active">სიახლის დამატება</li>
    </ol>
    
    @if($errors->any())
        <div class="row">
            <div class="col-md-5 offset-4">
                <div class="alert alert-danger">
                    <ul>
                        @foreach ($errors->all() as $error)
                            <li>{{ $error }}</li>
                        @endforeach
                    </ul>
                </div>
            </div>
        </div>
    @endif
    
    <div class="row">
        <div class="col-md-6 offset-3">
            <form method="post" action="{{ route('articles.store') }}" enctype="multipart/form-data">
                @csrf
                @foreach($locales as $key => $locale)
                    <div class="form-group row mt-4">
                        <label class="col-sm-2 col-form-label">სათაური ({{ $key }})</label>
                        <div class="col-sm-10">
                            <input type="text" name="translates[{{ $key }}][title]" value="{{ old('translates.'.$key.'.title') }}" class="form-control">
                        </div>
                    </div>
                    <div class="form-group row mt-4">
                        <label class="col-sm-2 col-form-label">აღწერა ({{ $key }})</label>
                        <div class="col-sm-10">
                            <textarea rows="5" name="translates[{{ $key }}][description]" class="form-control">{{ old('translates.'.$key.'.description') }}</textarea>
                        </div>
                    </div>
                    <div class="form-group row mt-4">
                        <label class="col-sm-2 col-form-label">ტექსტი ({{ $key }})</label>
                        <div class="col-sm-10">
                            <textarea rows="10" name="translates[{{ $key }}][text]" class="form-control">{{ old('translates.'.$key.'.text') }}</textarea>
                        </div>
                    </div>
                @endforeach
                <div class="form-group row mt-4">
                    <label class="col-sm-2 col-form-label">ფოტო</label>
                    <div class="col-sm-10">
                        <input type="file" name="image" class="form-control">
                    </div>
                </div>
                <div class="form-group row mt-4">
                    <label class="col-sm-2 col-form-label"></label>
                    <div class="col-sm-10">
                        <button type="submit" class="btn btn-success">დამატება</button>
                    </div>
                </div>
            </form>
        </div>
    </div>
</div>
@endsection
                
ამ ფორმის მეშვეობით გაიგზავნება შემდეგი შინაარსის მოთხოვნა :



რესურსის დამატების მეთოდები კონტროლერში

გადავინაცვლოთ ArticlesController-ში და შევიტანოთ შესაბამისი ცვლილებები : კოპირება
namespace App\Http\Controllers\Admin;

use App\Models\Article;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;

class ArticlesController extends BaseController
{
    public function index()
    {
        //
    }

    public function create()
    {
        return view('admin.articles.create');
    }

    public function store(Request $request)
    {
        // სათარგმნი ველების ქართულ ენაზე შევსება აუცულებელია
        $this->validate($request,[
            'translates.ka.title' => 'required|max:100',
            'translates.ka.description' => 'required|max:255',
            'translates.ka.text' => 'required',
            'image' => 'required|mimes:jpeg,jpg,png',
        ]);  
        
        $store = Article::store($request); // true ან false
        
        $request->session()->flash('result', $store);
        
        return redirect()->route('articles.index');
    }

    public function show($id)
    {
        //
    }

    public function edit($id)
    {
        //
    }

    public function update(Request $request, $id)
    {
        //
    }

    public function destroy($id)
    {
        //
    }
}
                
თუ ახლა შევალთ http://127.0.0.1:8000/admin/articles/create მისამართზე, ვიხილავთ სიახლის დამატების ფორმას.

store() მეთოდი

განვიხილოთ store მეთოდი.

როგორც ვიცით, MVC შაბლონის მიხედვით მონაცემთა ბაზებთან სამუშოდ გამოიყენება მოდელები, ამიტომ ინფორმაციის ბაზაში შენახვის ფუნქციონალი აღვწეროთ მოდელში :

$store = Article::store($request); // true ან false Article მოდელის store მეთოდი კი გამოიყურება ასე : კოპირება

namespace App\Models;

use App\Models\ArticlesTranslate;
use Illuminate\Database\Eloquent\Model;

class Article extends Model
{
    public function translates()
    {
        return $this->hasMany(ArticlesTranslate::class);
    } 
    
    public static function store($request)
    {
        $item = new Article;

        // აქ ისე ვერ მოვხვდებით, რომ ფოტო არჩეული არ იყოს, მაგრამ მაინც გადავამოწმოთ :))
        if ($request->hasFile('image')) 
        {
            $destination = 'uploads/articles'; // სად ვტვირთავთ ფოტოს

            $extension = $request->file('image')->getClientOriginalExtension(); // ატვირთული ფაილის გაფართოება : jpeg,jpg,png

            $file_name = mt_rand(11111, 99999) . time() . '.' . $extension; // მაგ: 564564564564.jpg

            $file_src = '/' . $destination . '/'. $file_name; // მაგ: /uploads/articles/564564564564.jpg

            // თუ სამიზნე საქაღალდე არ არსებობს
            if (!file_exists($destination)) 
            {
                mkdir($destination, 0777, true); // შეიქმნება public/uploads/articles საქაღალდე
            }   

            $request->file('image')->move($destination, $file_name); // ფაილის ატვირთვა სამიზნე საქაღალდეში

            $item->image = $file_src; // image ველის განსაზღვრა მოდელის ობიექტისათვის
        }

        /* 
            სათარგმნი ინფორმაციების დამუშავების სქემა რომ უფრო მარტივი აღსაქმელი იყოს, 
            აქვე მოვიყვანოთ მაგალითი თუ რა სახით შედის ეს ინფორმაციები მოთხოვნის ტანში : 

            [translates] => Array
            (
                [ka] => Array
                    (
                        [title] => სატესტო სიახლის სათაური
                        [description] => სატესტო სიახლის აღწერა
                        [text] => სატესტო სიახლის სრული ტექსტი
                    )

                [en] => Array
                    (
                        [title] => Test article title
                        [description] => Test article description
                        [text] => Test article full text
                    )

            )
        */
        
        // თუ ჩანაწრი წარმატებით შეინახება articles ცხრილში
        if ($item->save()) 
        {
            // თარგმანების შემცველი ასცოციაციური მასივი ინდექსებით ka,en
            $translates = $request->translates;

            foreach ($translates as $lang => $translation_data) 
            {
                // სათარგმნი მოდელის ეგზემპლიარი თითოეული ენისათვის 
                $item_translate = new ArticlesTranslate;

                /*
                 *  უშუალოდ თარგმანების მასივი [ველის_დასახელება => თარგმანი_შესაბამის_ენაზე]
                 *  $k : ველის დასახელება, მაგ. 'title'
                 *  $v : თარგმანი შესაბამის ენაზე, მაგ. 'სათური'
                 */
                foreach($translation_data as $k => $v)
                {
                    /* 
                        თუ რომელიმე სათარგმნი ველი არ შეიყვანა ქართული ენის გარდა რომელიმე სხვა ენაზე
                        არაკრეფილის მნიშვნელობად ჩაჯდეს ქართული ენის შესაბამისი მნიშვნელობა, ქართულად 
                        ყველა შემთხვევაში აკრეფილი იქნება ინფორმაცია, რადგან ეს ვალიდაციაში გვაქვს მოთხოვნილი
                    */
                    if(!$v)
                    {
                        
                        $item_translate->$k = $translates['ka'][$k];
                    }
                    else
                    {
                        $item_translate->$k = $v;
                    }                   
                }                

                $item_translate->lang = $lang;
                $item_translate->article_id = $item->id;
                
                $item_translate->save(); // ჩანაწრის შენახვა articles_translates ცხრილში
            }

            return true;            
        }
        
        return false;
    }
}
                
ამის შემდეგ მოდელის მეთოდის მიერ დაბრუნებულ ლოგიკურ შედეგს ვინახავთ სესიაში და გადავდივართ სიახლეების ჩამონათვალის გვერდზე : $request->session()->flash('result', $store);

return redirect()->route('articles.index');

ჩამონათვალის გვერდი

ჩამონათვალის გვერდის გამოსატანი მეთოდი კონტროლერში იქნება ასეთი : კოპირება
public function index()
{
    $items = Article::all('ka');

    return view('admin.articles.index', compact('items')); // მივამაგროთ ინფორმაცია და დავაბრუნოთ წარმოდგენის ფაილი
}
                
Article მოდელის all() მეთოდი კი იქნება შემდეგნაირი : კოპირება
public static function all($local = null) 
{
    return Article::join('articles_translates', 'articles.id', '=', 'articles_translates.article_id')
            ->where('articles_translates.lang', $local)
            ->select('articles.*', 'articles_translates.title')
            ->orderBy('id', 'desc')
            ->get();
}
                
ეს ჩანაწერი დააგენერირებდა შემდეგ ბრძანებას :
SELECT articles.*, articles_translates.title
FROM articles 
INNER JOIN articles_translates 
ON articles.id = articles_translates.article_id 
WHERE articles_translates.lang = 'ka'
ORDER BY id desc
                

resources/views/admin/articles საქაღალდეში შევქმნათ ფაილი index.blade.php :

კოპირება
@extends('admin.layout')
@section('title','სიახლეები')
@section('content')
<div class="container-fluid px-4">
    <h1 class="mt-4">სიახლეები</h1>
    <ol class="breadcrumb mb-4">
        <li class="breadcrumb-item active">
            <a href="{{ route('articles.create') }}" class="btn btn-sm btn-success">დამატება</a>
        </li>
    </ol>
    <div class="row">
        
        @if(Session::has('result'))
        <div class="col-md-12">
            <div class="alert alert-{{ Session::get('result') ? 'success' : 'danger'}}">
                ოპერაცია {{ Session::get('result') ? 'წარმატებით' : 'წარუმატებლად'}} დასრულდა
            </div>
        </div>
        @endif
        
        <div class="col-md-12">
            <table class="table">
                <thead>
                    <tr>
                        <th scope="col">#</th>
                        <th scope="col">სათაური</th>
                        <th scope="col">ფოტო</th>
                        <th scope="col"></th>
                    </tr>
                </thead>
                <tbody>
                    @foreach($items as $key => $item)
                        <tr>
                            <th scope="row">{{ ++$key }}</th>
                            <td>{{ $item->title }}</td>
                            <td>
                                <img src="{{ $item->image }}" style="width: 50px; height: 50px;">                                
                            </td>
                            <td>
                                <a href="{{ route('articles.edit', $item->id) }}" class="btn btn-sm btn-primary" style="float: left; margin-right: 5px">
                                    <i class="fa fa-edit"></i> 
                                </a>
                                <form action="{{ route('articles.destroy', $item->id) }}" method="post">
                                    @csrf
                                    <input type="hidden" name="_method" value="delete">
                                    <a href="#!" class="btn btn-sm btn-danger btn-destroy">
                                        <i class="fa fa-trash"></i> 
                                    </a>
                                </form>
                            </td>
                        </tr>
                    @endforeach
                </tbody>
            </table>
        </div>
    </div>
</div>
@endsection

@section('script')
<script>
    
    $('.btn-destroy').on('click', function(){
        
        if(confirm('დარწმუნებული ხართ ?'))
        {
            $(this).parent('form').submit();         
        }
        
    });
    
</script>
@endsection

                
კონტროლერის მეთოდი გვაქვს, მარშრუტიც - თავისთავად, წარმოდგენის ფაილიც შევქმენით. ისღა დაგვრჩენია სიახლეების ჩამონათვალის გვერდის სანახავად შევიდეთ შემდეგ მისამართზე : კოპირება
http://127.0.0.1:8000/admin/articles
                

ჩანაწერის წაშლა

Route ფასადის resource მეთოდის მიერ დაგენერირებული delete ტიპის მარშრუტის შესახებ უკვე ვისაუბრეთ ადმინისტრატორების მოდულში, ასევე მოვიყვანეთ და ავხსენით ამ მეთოდით ჩანაწერების წაშლის მექანიზმი, აქაც ზუსტად იგივე სიტუაციაა, ამიტომ აღარ დავკონკრეტდებით.

ჩანაწერის წაშლის მეთოდი კი ArticlesController კონტროლერში გამოიყურება ასე :

კოპირება
public function destroy(Request $request, $id)
{
    $delete = Article::find($id)->delete();
    $request->session()->flash('result', $delete);

    return redirect()->back();      
}
                

ჩანაწერის რედაქტირება

ჩანაწერების რედაქტირების გვერდზე გადასასვლელი ბმული შემდეგნაირად გენერირდება ჩამონათვალის გვერდზე : კოპირება
...

<a href="{{ route('articles.edit', $item->id) }}" class="btn btn-sm btn-primary" style="float: left; margin-right: 5px">
    <i class="fa fa-edit"></i> 
</a>

...
                
კონტროლერის მეთოდი კი, რომელიც რედაქტირების ფორმის ჩატვირთვას უზრუნველჰყოფს, ასეთია : კოპირება
public function edit($id)
{
    $items_with_translates = Article::itemByIdWithTranslates($id);
        
    if($items_with_translates->count() != 2)
    {
        redirect()->route('articles.index');
    }

    return view('admin.articles.edit', compact('items_with_translates')) ;
}
                
Article მოდელის itemByIdWithTranslates() მეთოდი იქნება ასეთი: კოპირება
public static function itemByIdWithTranslates($id = null) 
{
    return Article::join('articles_translates', 'articles.id', '=', 'articles_translates.article_id')
            ->select('articles.*', 'articles_translates.title', 'articles_translates.description', 'articles_translates.text','articles_translates.lang')
            ->where('articles.id', $id)
            ->get();
}
                
მეთოდში არსებული ჩანაწერის შედეგად დაგენერირდება შემდეგი ბრძანება :
SELECT articles.*, articles_translates.title, articles_translates.description, articles_translates.text, articles_translates.lang 
FROM articles 
INNER JOIN articles_translates 
ON articles.id = articles_translates.article_id 
WHERE articles.id = '2'
                
itemByIdWithTranslates() მეთოდი დააბრუნებს ჩანაწერების კოლექციას, რომლის სიგრძეც, წესით უნდა იყოს 2, რადგანაც სისტემაში ორი ხელმისაწვდომი ენა გვაქვს. შესაბამისად - articles ცხრილის ერთ ჩანაწერსაც უნდა შეესაბამებოდეს articles_translates ცხრილის 2 ჩანაწერი : კოპირება
public function edit($id)
{
    $items_with_translates = Article::itemsWithTranslates($id);

    echo '<pre>';
    print_r($items_with_translates>toArray());
    echo '</pre>';
    die;

    ...
}
                
შედეგი იქნება ამდაგვარი :
Array
(
    [0] => Array
        (
            [id] => 2
            [image] => /uploads/articles/999131625661813.jpg
            [created_at] => 2021-07-07T12:43:33.000000Z
            [updated_at] => 2021-07-07T12:43:33.000000Z
            [title] => მესამე
            [description] => xdfgd
            [text] => fgdfgdf
            [lang] => ka
        )

    [1] => Array
        (
            [id] => 2
            [image] => /uploads/articles/999131625661813.jpg
            [created_at] => 2021-07-07T12:43:33.000000Z
            [updated_at] => 2021-07-07T12:43:33.000000Z
            [title] => gdfg
            [description] => dfgdfg
            [text] => fgdfg
            [lang] => en
        )

)
                
ბუნებრივია, რომ სათარგმნ ცხრილებთან მუშაობისას შეგვეძლო ცხრილებს შორის ურთიერთკავშირების გამოყენებაც, Article და ArticlesTranslate მოდელებში უკვე აღწერილი გვაქვს ეს ურთიერთკავშირები :
public function translates()
{
    return $this->hasMany(ArticlesTranslate::class);
} 
                
public function article()
{
    return $this->belongsTo(Article::class);
}
                
...

$items_with_translates = ArticlesTranslate::with('article')->get();

...
                
თუმცა, როგორც ვხედავთ, გამოვიყენეთ Join ფუნქცია. ამას აქვს ორი მიზეზი : პირველი ის, რომ უფრო გავუშინაურდეთ ამ ფუნქციას და მეორე - ამ შემთხვევაში ერთ SQL ბრძანებაში ჩავეტიეთ თარგმანების მისაღებად. თქვენ შეგიძლიათ მოიქცეთ თქვენი შეხედულებისამებრ. resources/views/admin/articles საქაღალდეში შევქმნათ edit.blade.php ფაილი შემდეგი კოდით : კოპირება
@extends('admin.layout')
@section('title','სიახლის რედაქტირება')
@section('content')
<div class="container-fluid px-4">
    <h1 class="mt-4">მთავარი</h1>
    <ol class="breadcrumb mb-4">
        <li class="breadcrumb-item active">სიახლის დამატება</li>
    </ol>
    
    
    @if($errors->any())
        <div class="row">
            <div class="col-md-5 offset-4">
                <div class="alert alert-danger">
                    <ul>
                        @foreach ($errors->all() as $error)
                            <li>{{ $error }}</li>
                        @endforeach
                    </ul>
                </div>
            </div>
        </div>
    @endif
    

    <div class="row">
        
        
        @if(Session::has('result'))
        <div class="col-md-12">
            <div class="alert alert-{{ Session::get('result') ? 'success' : 'danger'}}">
                ოპერაცია {{ Session::get('result') ? 'წარმატებით' : 'წარუმატებლად'}} დასრულდა
            </div>
        </div>
        @endif
        
        
        <div class="col-md-6 offset-3">
            <form method="post" action="{{ route('articles.update',$items_with_translates->first()->id) }}" enctype="multipart/form-data">
                @csrf
                @method('put')
                @foreach($locales as $key => $locale)
                    @php
                        $current_locale_item = $items_with_translates->firstWhere('lang',$key);
                    @endphp
                    <div class="form-group row mt-4">
                        <label class="col-sm-2 col-form-label">სათაური ({{ $key }})</label>
                        <div class="col-sm-10">
                            <input type="text" name="translates[{{ $key }}][title]" value="{{ $current_locale_item->title }}" class="form-control">
                        </div>
                    </div>
                    <div class="form-group row mt-4">
                        <label class="col-sm-2 col-form-label">აღწერა ({{ $key }})</label>
                        <div class="col-sm-10">
                            <textarea rows="5" name="translates[{{ $key }}][description]" class="form-control">{{ $current_locale_item->description }}</textarea>
                        </div>
                    </div>
                    <div class="form-group row mt-4">
                        <label class="col-sm-2 col-form-label">ტექსტი ({{ $key }})</label>
                        <div class="col-sm-10">
                            <textarea rows="10" name="translates[{{ $key }}][text]" class="form-control">{{ $current_locale_item->text }}</textarea>
                        </div>
                    </div>
                @endforeach
                <div class="form-group row mt-4">
                    <label class="col-sm-2 col-form-label">ფოტო</label>
                    <div class="col-sm-10">
                        <input type="file" name="image" class="form-control">
                        <img src="{{ $items_with_translates->first()->image }}" width="100%">
                    </div>
                </div>
                <div class="form-group row mt-4">
                    <label class="col-sm-2 col-form-label"></label>
                    <div class="col-sm-10">
                        <button type="submit" class="btn btn-success">განახლება</button>
                    </div>
                </div>
            </form>
        </div>
    </div>
</div>
@endsection                    
                
განვიხილოთ ამ კოდის ზოგიერთი ფრაგმენტი. დავაკვირდეთ ფორმის action ატრიბუტს :
action="{{ route('articles.update',$items_with_translates->first()->id) }}"
                
როგორც ვიცით, განახლების მარშრუტს აუცილებელ პარამეტრად უნდა გადაეცეს შესაბამისი ჩანაწერის იდენტიფიკატორი. $items_with_translates ცვლადში კი შენახული გვაქვს ორელემენტიანი კოლექცია (კოლექცია მასივადაა დაფორმატებული და ისეა დაბეჭდილი):
Array
(
    [0] => Array
        (
            [id] => 2
            [image] => /uploads/articles/999131625661813.jpg
            [created_at] => 2021-07-07T12:43:33.000000Z
            [updated_at] => 2021-07-07T12:43:33.000000Z
            [title] => მესამე
            [description] => xdfgd
            [text] => fgdfgdf
            [lang] => ka
        )

    [1] => Array
        (
            [id] => 2
            [image] => /uploads/articles/999131625661813.jpg
            [created_at] => 2021-07-07T12:43:33.000000Z
            [updated_at] => 2021-07-07T12:43:33.000000Z
            [title] => gdfg
            [description] => dfgdfg
            [text] => fgdfg
            [lang] => en
        )

)
                
როგორც ადრეც ვთქვით, ამ კოლექციაში შენახულია, ერთად მოქცეული სათარგმნი და არასათარგმნი ინფორმაციები ხელმისაწვდომი ენების მიხედვით. არასათარგმნი ველებია : id, image, created_at, updated_at და მათი მნიშვნელობები კოლექციის ორივე ელემენტისათვის, რა თქმა უნდა, ერთნაირია. ამიტომ სულ ერთია რომელი მათგანიდან ავიღებთ ჩვენთვის საჭირო მნიშვნელობებს (ამ შემთცვევაში ჩვენ გვჭირდება id). ამიტომ $items_with_translates->first() ჩანაწერით მივწვდით კოლექციის პირველ ელემენტს და იქიდან ავიღეთ საჭირო ინფორმაცია.

ახლა დავაკვირდეთ შემდეგ ფრაგმენტს :

@php
    $current_locale_item = $items_with_translates->firstWhere('lang',$key);
@endphp
                
ამ ჩანაწერით ხდება foreach ციკლში გატარებული ხელმისაწვდომი ენებიდან, ციკლის მიმინარე იტერაციის შესაბამისი ინფორმაციის ამოღება ზემოთნახსენები ორელემენტიანი კოლექციიდან.

კონტროლერის update() მეთოდი

კოპირება
public function update(Request $request, $id)
{
    // სათარგმნი ველების ქართულ ენაზე შევსება აუცულებელია
    $this->validate($request,[
        'translates.ka.title' => 'required|max:100',
        'translates.ka.description' => 'required|max:255',
        'translates.ka.text' => 'required',
        'image' => 'mimes:jpeg,jpg,png', // ფოტოს არჩევა აღარაა აუცილებელი, თუმცა თუ აირჩევს ფორმატი უნდა გადამოწმდეს
    ]);  

    $item = Article::findOrFail($id);
    $update = Article::updateItem($request, $item); // true ან false
    $request->session()->flash('result', $update);

    return redirect()->back();            
}
                

შემდეგ ისევ იგივე სქემით მეორდება ყველაფერი რაც ჩანაწერის დამატების მეთოდში გვქონდა, უბრალოდ ამჯერად Article მოდელის updateItem მეთოდს მივმართავთ, რომელიც გამოიყურება ასე :

კოპირება
public static function updateItem($request, $item)
{
    if ($request->hasFile('image')) 
    {
        $destination = 'uploads/articles'; // სად ვტვირთავთ ფოტოს

        $extension = $request->file('image')->getClientOriginalExtension(); // ატვირთული ფაილის გაფართოება : jpeg,jpg,png

        $file_name = mt_rand(11111, 99999) . time() . '.' . $extension; // მაგ: 564564564564.jpg

        $file_src = '/' . $destination . '/'. $file_name; // მაგ: /uploads/articles/564564564564.jpg

        // თუ სამიზნე საქაღალდე არ არსებობს
        if (!file_exists($destination)) 
        {
            mkdir($destination, 0777, true); // შეიქმნება public/uploads/articles საქაღალდე
        }   

        $request->file('image')->move($destination, $file_name); // ფაილის ატვირთვა სამიზნე საქაღალდეში

        $item->image = $file_src; // image ველის განსაზღვრა მოდელის ობიექტისათვის
    }

    // თუ ძირითადი ცხრილის ჩანაწერი განახლდა
    if ($item->update()) 
    {
        $translates = $request->translates;

        foreach ($translates as $lang => $translation_data) 
        {
            // სათარგმნი მოდელის, მიმდინარე ენის შესაბამისი ეგზემპლიარი
            $item_translate = ArticlesTranslate::where('article_id', $item->id)->where('lang', $lang)->first();

            foreach($translation_data as $k => $v)
            {
                if(!$v)
                {

                    $item_translate->$k = $translates['ka'][$k];
                }
                else
                {
                    $item_translate->$k = $v;
                }                   
            }                

            $item_translate->update(); // ჩანაწრის განახლება articles_translates ცხრილში
        }

        return true;            
    }

    return false;  
}
                
საბოლოოდ Article მოდელი მიიღებს შემდეგ სახეს : კოპირება
namespace App\Models;

use App\Models\ArticlesTranslate;
use Illuminate\Database\Eloquent\Model;

class Article extends Model
{
    protected $guarded = [];
    
    public function translates()
    {
        return $this->hasMany(ArticlesTranslate::class);
    } 
    
    public static function store($request)
    {
        $item = new Article;

        // აქ ისე ვერ მოვხვდებით, რომ ფოტო არჩეული არ იყოს, მაგრამ მაინც გადავამოწმოთ :))
        if ($request->hasFile('image')) 
        {
            $destination = 'uploads/articles'; // სად ვტვირთავთ ფოტოს

            $extension = $request->file('image')->getClientOriginalExtension(); // ატვირთული ფაილის გაფართოება : jpeg,jpg,png

            $file_name = mt_rand(11111, 99999) . time() . '.' . $extension; // მაგ: 564564564564.jpg

            $file_src = '/' . $destination . '/'. $file_name; // მაგ: /uploads/articles/564564564564.jpg

            // თუ სამიზნე საქაღალდე არ არსებობს
            if (!file_exists($destination)) 
            {
                mkdir($destination, 0777, true); // შეიქმნება public/uploads/articles საქაღალდე
            }   

            $request->file('image')->move($destination, $file_name); // ფაილის ატვირთვა სამიზნე საქაღალდეში

            $item->image = $file_src; // image ველის განსაზღვრა მოდელის ობიექტისათვის
        }

        /* 
            სათარგმნი ინფორმაციების დამუშავების სქემა რომ უფრო მარტივი აღსაქმელი იყოს, 
            აქვე მოვიყვანოთ მაგალითი თუ რა სახით შედის ეს ინფორმაციები მოთხოვნის ტანში : 

            [translates] => Array
            (
                [ka] => Array
                    (
                        [title] => სატესტო სიახლის სათაური
                        [description] => სატესტო სიახლის აღწერა
                        [text] => სატესტო სიახლის სრული ტექსტი
                    )

                [en] => Array
                    (
                        [title] => Test article title
                        [description] => Test article description
                        [text] => Test article full text
                    )

            )
        */
        
        // თუ ჩანაწრი წარმატებით შეინახება articles ცხრილში
        if ($item->save()) 
        {
            // თარგმანების შემცველი ასცოციაციური მასივი ინდექსებით ka,en
            $translates = $request->translates;

            foreach ($translates as $lang => $translation_data) 
            {
                // სათარგმნი მოდელის ეგზემპლიარი თითოეული ენისათვის 
                $item_translate = new ArticlesTranslate;

                /*
                 *  უშუალოდ თარგმანების მასივი [ველის_დასახელება => თარგმანი_შესაბამის_ენაზე]
                 *  $k : ველის დასახელება, მაგ. 'title'
                 *  $v : თარგმანი შესაბამის ენაზე, მაგ. 'სათური'
                 */
                foreach($translation_data as $k => $v)
                {
                    /* 
                        თუ რომელიმე სათარგმნი ველი არ შეიყვანა ქართული ენის გარდა რომელიმე სხვა ენაზე
                        არაკრეფილის მნიშვნელობად ჩაჯდეს ქართული ენის შესაბამისი მნიშვნელობა, ქართულად 
                        ყველა შემთხვევაში აკრეფილი იქნება ინფორმაცია, რადგან ეს ვალიდაციაში გვაქვს მოთხოვნილი
                    */
                    if(!$v)
                    {
                        
                        $item_translate->$k = $translates['ka'][$k];
                    }
                    else
                    {
                        $item_translate->$k = $v;
                    }                   
                }                

                $item_translate->lang = $lang;
                $item_translate->article_id = $item->id;
                
                $item_translate->save(); // ჩანაწრის შენახვა articles_translates ცხრილში
            }

            return true;            
        }
        
        return false;
    }
    
    public static function all($local = null) 
    {
        return Article::join('articles_translates', 'articles.id', '=', 'articles_translates.article_id')
                ->where('articles_translates.lang', $local)
                ->select('articles.*', 'articles_translates.title')
                ->orderBy('id', 'desc')
                ->get();
    }
    
    public static function itemByIdWithTranslates($id = null) 
    {
        return Article::join('articles_translates', 'articles.id', '=', 'articles_translates.article_id')
                ->select('articles.*', 'articles_translates.title', 'articles_translates.description', 'articles_translates.text','articles_translates.lang')
                ->where('articles.id', $id)
                ->get();
    }
    
    public static function itemsByIdWithTranslates($local = null) 
    {
        return Article::join('articles_translates', 'articles.id', '=', 'articles_translates.article_id')
                ->where('articles_translates.lang', $local)
                ->select('articles.*', 'articles_translates.title', 'articles_translates.description', 'articles_translates.text')
                ->orderBy('id', 'desc')
                ->get();
    }
    
    public static function updateItem($request, $item)
    {
        if ($request->hasFile('image')) 
        {
            $destination = 'uploads/articles'; // სად ვტვირთავთ ფოტოს

            $extension = $request->file('image')->getClientOriginalExtension(); // ატვირთული ფაილის გაფართოება : jpeg,jpg,png

            $file_name = mt_rand(11111, 99999) . time() . '.' . $extension; // მაგ: 564564564564.jpg

            $file_src = '/' . $destination . '/'. $file_name; // მაგ: /uploads/articles/564564564564.jpg

            // თუ სამიზნე საქაღალდე არ არსებობს
            if (!file_exists($destination)) 
            {
                mkdir($destination, 0777, true); // შეიქმნება public/uploads/articles საქაღალდე
            }   

            $request->file('image')->move($destination, $file_name); // ფაილის ატვირთვა სამიზნე საქაღალდეში

            $item->image = $file_src; // image ველის განსაზღვრა მოდელის ობიექტისათვის
        }
        
        if ($item->update()) 
        {
            $translates = $request->translates;
            
            foreach ($translates as $lang => $translation_data) 
            {
                $item_translate = ArticlesTranslate::where('article_id', $item->id)->where('lang', $lang)->first();

                foreach($translation_data as $k => $v)
                {
                    if(!$v)
                    {
                        
                        $item_translate->$k = $translates['ka'][$k];
                    }
                    else
                    {
                        $item_translate->$k = $v;
                    }                   
                }                
                
                $item_translate->update(); // ჩანაწრის შენახვა articles_translates ცხრილში
            }

            return true;            
        }
        
        return false;  
    }
}            
                
ArticlesController კონტროლერი კი შემდეგს : კოპირება
namespace App\Http\Controllers\Admin;

use App\Models\Article;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;

class ArticlesController extends BaseController
{
    public function index()
    {
        $items = Article::all('ka');

        return view('admin.articles.index', compact('items')); // მივამაგროთ ინფორმაცია და დავაბრუნოთ წარმოდგენის ფაილი
    }

    public function create()
    {
        return view('admin.articles.create');
    }

    public function store(Request $request)
    {
        // სათარგმნი ველების ქართულ ენაზე შევსება აუცულებელია
        $this->validate($request,[
            'translates.ka.title' => 'required|max:100',
            'translates.ka.description' => 'required|max:255',
            'translates.ka.text' => 'required',
            'image' => 'required|mimes:jpeg,jpg,png',
        ]);  
        
        $store = Article::store($request); // true ან false
        
        $request->session()->flash('result', $store);
        
        return redirect()->route('articles.index');
    }

    public function show($id)
    {
        //
    }

    public function edit($id)
    {
        $items_with_translates = Article::itemByIdWithTranslates($id);

        if($items_with_translates->count() != 2)
        {
            redirect()->route('articles.index');
        }
        
        return view('admin.articles.edit', compact('items_with_translates')) ;
    }                         

    public function update(Request $request, $id)
    {
        // სათარგმნი ველების ქართულ ენაზე შევსება აუცულებელია
        $this->validate($request,[
            'translates.ka.title' => 'required|max:100',
            'translates.ka.description' => 'required|max:255',
            'translates.ka.text' => 'required',
            'image' => 'mimes:jpeg,jpg,png', // ფოტოს არჩევა აღარაა აუცილებელი, თუმცა თუ აირჩევს ფორმატი უნდა გადამოწმდეს
        ]);  
        
        $item = Article::findOrFail($id);
        $update = Article::updateItem($request, $item); // true ან false
        $request->session()->flash('result', $update);
        
        return redirect()->back();            
    }

    public function destroy(Request $request, $id)
    {
        $delete = Article::find($id)->delete();
        $request->session()->flash('result', $delete);

        return redirect()->back();      
    }                
}                
                
ბოლოს ისღა დაგვრჩენია, რომ სიახლეების მოდულის ბმულები ჩავამატოთ resources/views/admin/layout.blade.php და resources/views/admin/index.blade.php ფაილებში, ანუ გვერდით მენიუში და ადმინისტრატორის განყოფილების მთავარ გვერდზე : კოპირება
...

<!-- გვერდითი მენიუ -->
<div class="nav">
    <a class="nav-link" href="{{ route('AdminMainPage') }}">
        <div class="sb-nav-link-icon"><i class="fas fa-tachometer-alt"></i></div>
        მთავარი
    </a>
    <a class="nav-link" href="{{ route('admins.index') }}">
        <div class="sb-nav-link-icon"><i class="fas fa-user"></i></div>
        ადმინისტრატორები
    </a>
    <a class="nav-link" href="{{ route('contacts.edit', 1) }}">
        <div class="sb-nav-link-icon"><i class="fas fa-phone"></i></div>
        საკონტაქტო ინფორმაცია
    </a>
    <a class="nav-link" href="{{ route('articles.index') }}">
        <div class="sb-nav-link-icon"><i class="fas fa-newspaper"></i></div>
        სიახლეები
    </a>
</div>

...            
                
კოპირება
...

<div class="container-fluid px-4">
    <h1 class="mt-4">მთავარი</h1>
    <ol class="breadcrumb mb-4">
        <li class="breadcrumb-item active">მთავარი</li>
    </ol>
    <div class="row">
        <div class="col-xl-3 col-md-6">
            <div class="card bg-primary text-white mb-4">
                <div class="card-body">ადმინსტრატორები</div>
                <div class="card-footer d-flex align-items-center justify-content-between">
                    <a class="small text-white stretched-link" href="{{ route('admins.index') }}">სრულად</a>
                    <div class="small text-white"><i class="fas fa-angle-right"></i></div>
                </div>
            </div>
        </div>
        <div class="col-xl-3 col-md-6">
            <div class="card bg-success text-white mb-4">
                <div class="card-body">საკონტაქტო ინფორმაცია</div>
                <div class="card-footer d-flex align-items-center justify-content-between">
                    <a class="small text-white stretched-link" href="{{ route('contacts.edit', 1) }}">სრულად</a>
                    <div class="small text-white"><i class="fas fa-angle-right"></i></div>
                </div>
            </div>
        </div>
        <div class="col-xl-3 col-md-6">
            <div class="card bg-warning text-white mb-4">
                <div class="card-body">სიახლეები</div>
                <div class="card-footer d-flex align-items-center justify-content-between">
                    <a class="small text-white stretched-link" href="{{ route('articles.index') }}">სრულად</a>
                    <div class="small text-white"><i class="fas fa-angle-right"></i></div>
                </div>
            </div>
        </div>
    </div>
</div>

...
                
10. მომხმარებლის მხარის ძირითადი კონტროლერი
შევქმნათ საქაღალდე app/Http/Controllers/Front, მასში კი მომხმარებლის მხარის ძირითადი კონტროლერი IndexController: კოპირება
php artisan make:controller Front/IndexController  
                
კოპირება
namespace App\Http\Controllers\Front;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;

class IndexController extends Controller
{
    //
}  
                
კონტროლერში შევქმნათ index მეთოდი, რომელიც მომხმარებლის მხარის მთავარი გვერდის ჩატვირთვას უზრუნველჰყოფს : კოპირება
namespace App\Http\Controllers\Front;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;

class IndexController extends Controller
{
    public function index()
    {
        return 'მთავარი გვერდი';
    }
}
                
შევიტანოთ ცვლილებები routes/web.php ფაილში : კოპირება
use Illuminate\Support\Facades\Route;

use App\Http\Middleware\Admin;
use App\Http\Controllers\Admin\AdminsController;
use App\Http\Controllers\Admin\ContactsController;
use App\Http\Controllers\Admin\LoginController;
use App\Http\Controllers\Admin\ArticlesController;

use App\Http\Controllers\Front\IndexController;

Route::group(['prefix' => LaravelLocalization::setLocale(),'middleware' => [ 'localeSessionRedirect', 'localizationRedirect', 'localeViewPath' ]], function(){
   
    // მთავარი გვერდი
    Route::get('/', [IndexController::class, 'index'])->name('index');
    
});

Route::group(['middleware' => ['admin'], 'prefix' => 'admin'], function () {
    
    // ავტორიზაცია და სისტემიდან გასვლა
    Route::get('/login', [LoginController::class, 'showLogin'])->withoutMiddleware([Admin::class])->name('ShowLogin');
    Route::post('/signin', [LoginController::class, 'login'])->withoutMiddleware([Admin::class])->name('AdminLogin');
    Route::get('/logout', [LoginController::class, 'logout'])->name('AdminLogout');
    
    // ადმინისტრატორის პანელის მთავარი გვერდი 
    Route::get('/', function () {
        return view('admin.index');
    })->name('AdminMainPage');
    
    // ადმინისტრატორები
    Route::resource('admins', AdminsController::class);
    
    // საკონტაქტო ინფორმაციის გვერდი
    Route::resource('contacts', ContactsController::class, ['only' => ['edit','update']]);
    Route::get('/contacts/cache', [ContactsController::class, 'cache'])->name('contacts.cache');
    
    // სიახლეები
    Route::resource('articles', ArticlesController::class);

});    
                
თუ ახლა შევალთ პროექტის მთავარ გვერდზე, ვიხილავთ ტექსტს - 'მთავარი გვერდი'.
11. მომხმარებლის მხარის შაბლონების გამართვა
შევქმნათ საქაღალდე resources/views/front, მასში კი მომხმარებლის მხარის ძირითადი შაბლონი layout.blade.php, რომელშიც ამ ეტაპზე განვსაზღვრავთ ერთადერთ სექციას სახელად - content: კოპირება
@yield('content')  
                
ამ შაბლონის მემკვიდრე იქნება კლიენტის მხარის ყველა სხვა შაბლონი.

resources/views/front საქაღლდეშივე შევქმნათ მთავარი გვერდის შაბლონი index.blade.php შემდეგი კოდით :

კოპირება
@extends('front.layout')
@section('content')

მთავარი გვერდი

@endsection  
                
ახლა ეს შაბლონი დავაბრუნებინოთ კონტროლერის მეთოდს : კოპირება
namespace App\Http\Controllers\Front;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;

class IndexController extends Controller
{
    public function index()
    {
        return view('front.index');
    }
} 
                
როგორც ადრეც ვთქვით, მომხმარების მხარისათვის გამოვიყენებთ ამ შაბლონს.

შევქმნათ საქაღალდე public/assets/front და მასში ჩავაკოპიროთ ამ ბმულიდან გადმოწერილ საქაღალდეში არსებიული assets, js და css საქაღალდეები.

ახლა კი resources/views/front/layout.blade.php და resources/views/front/index.blade.php ფაილებში შევიტანოთ შემდეგი კოდები : კოპირება
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
        <title>@yield('title')</title>
        <!-- Font Awesome icons (free version)-->
        <script src="https://use.fontawesome.com/releases/v5.15.3/js/all.js" crossorigin="anonymous"></script>
        <!-- Google fonts-->
        <link href="https://fonts.googleapis.com/css?family=Lora:400,700,400italic,700italic" rel="stylesheet" type="text/css" />
        <link href="https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800" rel="stylesheet" type="text/css" />
        <!-- Core theme CSS (includes Bootstrap)-->
        <link href="{{ asset('assets/front/css/styles.css') }}" rel="stylesheet" />
    </head>
    <body>
        
        <!-- მენიუ -->
        <nav class="navbar navbar-expand-lg navbar-light" id="mainNav">
            <div class="container px-4 px-lg-5">
                <a class="navbar-brand" href="{{ route('index') }}">Start Bootstrap</a>
                <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation">
                    Menu
                    <i class="fas fa-bars"></i>
                </button>
                <div class="collapse navbar-collapse" id="navbarResponsive">
                    <ul class="navbar-nav ms-auto py-4 py-lg-0">
                        <li class="nav-item">
                            <a class="nav-link px-lg-3 py-3 py-lg-4" href="
                                {{ route('index') }}">@lang('menu.index')
                            </a>
                        </li>
                        <li class="nav-item">
                            <a class="nav-link px-lg-3 py-3 py-lg-4" href="contact.html">
                                @lang('menu.contact')
                            </a>
                        </li>
                        <!-- ენების გადამრთველი -->
                        @foreach(LaravelLocalization::getSupportedLocales() as $localeCode => $properties)
                            <li class="nav-item">
                                <a  href="{{ LaravelLocalization::getLocalizedURL($localeCode, null, [], true) }}" class="nav-link px-lg-3 py-3 py-lg-4">
                                    {{ strtoupper(mb_substr($properties['name'], 0, 2)) }} 
                                </a>
                            </li>
                        @endforeach
                    </ul>
                </div>
            </div>
        </nav>
        <!-- /მენიუ -->
        
        <!-- საიტის ქუდი -->
        <header class="masthead" style="background-image: url('{{ asset('assets/front/assets/img/home-bg.jpg') }}')">
            <div class="container position-relative px-4 px-lg-5">
                <div class="row gx-4 gx-lg-5 justify-content-center">
                    <div class="col-md-10 col-lg-8 col-xl-7">
                        <div class="site-heading">
                            <h1>Clean Blog</h1>
                            <span class="subheading">A Blog Theme by Start Bootstrap</span>
                        </div>
                    </div>
                </div>
            </div>
        </header>
        <!-- /საიტის ქუდი -->
        
        <!-- ძირითადი შიგთავსი -->
        @yield('content')
        <!-- /ძირითადი შიგთავსი -->
        
        <!-- საიტის ძირი -->
        <footer class="border-top">
            <div class="container px-4 px-lg-5">
                <div class="row gx-4 gx-lg-5 justify-content-center">
                    <div class="col-md-10 col-lg-8 col-xl-7">
                        <ul class="list-inline text-center">
                            <li class="list-inline-item">
                                <a href="#!">
                                    <span class="fa-stack fa-lg">
                                        <i class="fas fa-circle fa-stack-2x"></i>
                                        <i class="fab fa-twitter fa-stack-1x fa-inverse"></i>
                                    </span>
                                </a>
                            </li>
                            <li class="list-inline-item">
                                <a href="#!">
                                    <span class="fa-stack fa-lg">
                                        <i class="fas fa-circle fa-stack-2x"></i>
                                        <i class="fab fa-facebook-f fa-stack-1x fa-inverse"></i>
                                    </span>
                                </a>
                            </li>
                            <li class="list-inline-item">
                                <a href="#!">
                                    <span class="fa-stack fa-lg">
                                        <i class="fas fa-circle fa-stack-2x"></i>
                                        <i class="fab fa-github fa-stack-1x fa-inverse"></i>
                                    </span>
                                </a>
                            </li>
                        </ul>
                        <div class="small text-center text-muted fst-italic">Copyright © Your Website 2021</div>
                    </div>
                </div>
            </div>
        </footer>
        <!-- საიტის ძირი -->
        
        <!-- Bootstrap core JS-->
        <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.0/dist/js/bootstrap.bundle.min.js"></script>
        <!-- Core theme JS-->
        <script src="{{ asset('assets/front/js/scripts.js') }}"></script>
        
    </body>
</html>
                
კოპირება
@extends('front.layout')
@section('title', trans('menu.index'))
@section('content')
<div class="container px-4 px-lg-5">
    <div class="row gx-4 gx-lg-5 justify-content-center">
        <div class="col-md-10 col-lg-8 col-xl-7">

            <!-- Post preview-->
            <div class="post-preview">
                <a href="post.html">
                    <h2 class="post-title">Man must explore, and this is exploration at its greatest</h2>
                    <h3 class="post-subtitle">Problems look mighty small from 150 miles up</h3>
                </a>
                <p class="post-meta">
                    Posted by
                    <a href="#!">Start Bootstrap</a>
                    on September 24, 2021
                </p>
            </div>
            <!-- Divider-->
            <hr class="my-4" />
            
        </div>
    </div>
</div>
@endsection
                

სათარგმნი ფაილები

პროექტის ინსტალაციის შემდეგ lang საქაღალდეში შეიქმნებოდა ერთადერთი საქაღალდე - en ინგლისური ენისათვის. დავაკოპიროთ იგი და ასლს სახელად დავარქვათ ka. ახლა კი ორივე მათგანში შევქმნათ ფაილი menu.php შემდეგი კოდებით : კოპირება
<?php

return [
    'index' => 'Home',
    'contact' => 'Contact',
];
                
კოპირება
<?php

return [
    'index' => 'მთავარი',
    'contact' => 'კონტაქტი',
];
                
12. სიახლეების გამოტანა მთავარ გვერდზე
კონტროლერის index მეთოდში მოვახდინოთ სიახლეების ამოღება ბაზიდან, შემდეგ კი ეს ინფორმაცია გადავცეთ წარმოდგენის ფაილს : კოპირება
namespace App\Http\Controllers\Front;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;

use App;
use App\Models\Article;

class IndexController extends Controller
{
    public function index()
    {
        $articles = Article::all(App::getLocale()); // App::getLocale() მიმდინარე ენა
        
        return view('front.index', compact('articles'));
    }
}
                
როგორც ვხედავთ, მივმართეთ Article მოდელის all მეთოდს, რომელიც გამოიყურება შემდეგნაირად : კოპირება
public static function all($local = null) 
{
    return Article::join('articles_translates', 'articles.id', '=', 'articles_translates.article_id')
            ->where('articles_translates.lang', $local)
            ->select('articles.*', 'articles_translates.title', 'articles_translates.description')
            ->orderBy('id', 'desc')
            ->get();
}
                
მეთოდში ბრძანებათა კონსტრუქტორის დახმარებით აღწერილი ჩანაწერი დააგენერირებს შემდეგ SQL ბრძანებას : კოპირება
select articles.*, articles_translates.title, articles_translates.description
from articles 
inner join articles_translates 
on articles.id = articles_translates.article_id 
where articles_translates.lang = 'ka' 
order by id desc
                
ახლა ეს ინფორმაცია გამოვიტანოთ resources/views/front/index.blade.php ფაილში : კოპირება
@extends('front.layout')
@section('title', trans('menu.index'))
@section('content')
<div class="container px-4 px-lg-5">
    <div class="row gx-4 gx-lg-5 justify-content-center">
        <div class="col-md-10 col-lg-8 col-xl-7">
            @forelse($articles as $article)
                <div class="post-preview">
                    <a href="post.html">
                        <h2 class="post-title">{{ $article->title }}</h2>
                        <h3 class="post-subtitle">{{ $article->description }}</h3>
                    </a>
                    <p class="post-meta">
                        @lang('site.author') 
                        <a href="#!">Start Bootstrap</a>
                        @lang('site.date') : {{ $article->created_at }}
                    </p>
                </div>
                @if(!$loop->last)
                    <!-- დიზაინში არსებული გამყოფი ხაზი აღარაა საჭირო ბოლო სიახლის შემდეგ -->
                    <hr class="my-4" />
                @endif  
            @empty
                <div class="alert alert-danger">@lang('site.no_data')</div>
            @endforelse           
        </div>
    </div>
</div>
@endsection
                
სათარგმნი ფაილის (site.php) შექმნა უკვე ვიცით და ამიტომ აღარ დავკონკრეტდებით.
13. კონკრეტული სიახლის გვერდი
პირველ რიგში შევქმნათ კონტროლერის მეთოდი : კოპირება
namespace App\Http\Controllers\Front;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;

use App;
use App\Models\Article;

class IndexController extends Controller
{
    public function index()
    {
        $articles = Article::all(App::getLocale());
        
        return view('front.index', compact('articles'));
    }
    
    public function article($id)
    {
        $article = Article::item(App::getLocale(), $id); 

        if(!$article)
        {
            return redirect()->back();
        }
        
        return view('front.article', compact('article'));
    }
}
                
როგორც ვხედავთ მივმართავთ Article მოდელის item მეთოდს, რომელსაც გადაეცემა ორი პარამეტრი - სასურველი ენა და სასურველი ჩანაწერის იდენტიფიკატორი. ჩავამატოთ ეს მეთოდი მოდულში : კოპირება
public static function item($local = null, $id = null) 
{
    return Article::join('articles_translates', 'articles.id', '=', 'articles_translates.article_id')
            ->where('articles.id', $id)
            ->where('articles_translates.lang', $local)
            ->select('articles.*', 'articles_translates.title','articles_translates.description','articles_translates.text')
            ->first();
}
                
მოთხოვნათა კონსტრუქტორის მეშვეობით დაგენერირდება შემდეგი SQL ბრძანება : კოპირება
select articles.*, articles_translates.title, articles_translates.description, articles_translates.text 
from articles 
inner join articles_translates 
on articles.id = articles_translates.article_id 
where articles.id = 3 and articles_translates.lang = 'en' 
limit 1
                
შემდეგ მიღებულ ინფორმაციას გადავცემთ წარმოდგენის ფაილს. შევქმნათ ფაილი resources/views/front/article.blade.php : კოპირება
@extends('front.layout')
@section('title', $article->title)
@section('content')
<article class="mb-4">
    <div class="container px-4 px-lg-5">
        <div class="row gx-4 gx-lg-5 justify-content-center">
            <div class="col-md-10 col-lg-8 col-xl-7">
                {!! $article->text !!}
            </div>
        </div>
    </div>
</article>
@endsection
                
ახლა შევქმნათ შესაბამისი მარშრუტი : კოპირება
use Illuminate\Support\Facades\Route;

use App\Http\Middleware\Admin;
use App\Http\Controllers\Admin\AdminsController;
use App\Http\Controllers\Admin\ContactsController;
use App\Http\Controllers\Admin\LoginController;
use App\Http\Controllers\Admin\ArticlesController;

use App\Http\Controllers\Front\IndexController;

Route::group(['prefix' => LaravelLocalization::setLocale(),'middleware' => [ 'localeSessionRedirect', 'localizationRedirect', 'localeViewPath' ]], function(){
   
    // მთავარი გვერდი
    Route::get('/', [IndexController::class, 'index'])->name('index');
    // სიახლის შიდა გვერდი
    Route::get('/article/{id}', [IndexController::class, 'article'])->name('article');
    
});

Route::group(['middleware' => ['admin'], 'prefix' => 'admin'], function () {
    
    // ავტორიზაცია და სისტემიდან გასვლა
    Route::get('/login', [LoginController::class, 'showLogin'])->withoutMiddleware([Admin::class])->name('ShowLogin');
    Route::post('/signin', [LoginController::class, 'login'])->withoutMiddleware([Admin::class])->name('AdminLogin');
    Route::get('/logout', [LoginController::class, 'logout'])->name('AdminLogout');
    
    // ადმინისტრატორის პანელის მთავარი გვერდი 
    Route::get('/', function () {
        return view('admin.index');
    })->name('AdminMainPage');
    
    // ადმინისტრატორები
    Route::resource('admins', AdminsController::class);
    
    // საკონტაქტო ინფორმაციის გვერდი
    Route::resource('contacts', ContactsController::class, ['only' => ['edit','update']]);
    Route::get('/contacts/cache', [ContactsController::class, 'cache'])->name('contacts.cache');
    
    // სიახლეები
    Route::resource('articles', ArticlesController::class);

});                      
                
გადმოწერილ Bootstrap შაბლონს თუ დავაკვირდებით ვნახავთ, რომ მთავარ გვერდზე header სექციას აქვს სტატიკური ფონი (home-bg.jpg), ხოლო სიახლის შიდა გვერდს თუ გავხსნით ამ სექციას იქ უკვე სხვა ფონი აქვს, შევიტანოთ შესაბამისი ცვლილებები მშობელ შაბლონში (resources/views/front/layout.blade.php) არსებულ header სექციაში : კოპირება

@php
    $articles_page = Route::current()->getName() == 'article' ? true : false;
@endphp
<header class="masthead" style="background-image: url('{{ $articles_page ? $article->image : asset('assets/front/assets/img/home-bg.jpg') }}')">
    <div class="container position-relative px-4 px-lg-5">
        <iv class="row gx-4 gx-lg-5 justify-content-center">
            <div class="col-md-10 col-lg-8 col-xl-7">
                <div class="site-heading">
                    <h1>{{ $articles_page ? $article->title : 'Clean Blog' }}
                </div>
            </div>
        </div>
    </div>
</header>

                
ანუ Route::current()->getName() ჩანაწერის მეშვეობით მოვახდინეთ იმის გადამოწმება, ვიმყოფებით თუ არა სიახლის შიდა გვერდზე, თუ კი - მაშინ სექციას ფონად ედება სიახლის ფოტო, წინააღმდეგ შემთხვევაში კი სტატიკური სურათი. იგივე ლოგიკით შევცვალეთ სექციაში არსებული ტექსტიც.

აქვე შევიტანოთ ცვლილებები მთავარ გვერდზე გამოტანილი სიახლეების ჩამონათვალის სექციაში, კერძოდ - ბმულები დავაკავშიროთ მარშრუტთან :

კოპირება
@extends('front.layout')
@section('title', trans('menu.index'))
@section('content')
<div class="container px-4 px-lg-5">
    <div class="row gx-4 gx-lg-5 justify-content-center">
        <div class="col-md-10 col-lg-8 col-xl-7">
            @forelse($articles as $article)
                <div class="post-preview">
                    <a href="{{ route('article', $article->id) }}">
                        <h2 class="post-title">{{ $article->title }}</h2>
                        <h3 class="post-subtitle">{{ $article->description }}</h3>
                    </a>
                    <p class="post-meta">
                        @lang('site.author') 
                        <a href="{{ route('article', $article->id) }}">Start Bootstrap</a>
                        @lang('site.date') : {{ $article->created_at }}
                    </p>
                </div>
                @if(!$loop->last)
                    <!-- დიზაინში არსებული გამყოფი ხაზი აღარაა საჭირო ბოლო სიახლის შემდეგ -->
                    <hr class="my-4" />
                @endif  
            @empty
                <div class="alert alert-danger">@lang('site.no_data')</div>
            @endforelse           
        </div>
    </div>
</div>
@endsection
                
14. მომხმარებლების რეგისტრაცია და აუტენტიფიკაცია
მომხმარებელთა რეგისტრაციისა და აუტენტიფიკაციისათვის გამოვიყენოთ, ჩვენთვის უკვე კარგად ნაცნობი - Laravel Breeze : კოპირება
composer require laravel/breeze --dev
                
ამის შემდეგ გავუშვათ ეს ბრძანება : კოპირება
php artisan breeze:install
                
როგორც ვიცით, ინსტალაციის შემდეგ იქმნება routes/auth.php ფაილი და ასევე ხდება ამ ფაილის routes/web.php ფაილში გამოძახება : კოპირება
php require __DIR__.'/auth.php';
                
თუმცა !
პაკეტის ინსტალაციისას იშლება უკვე არსებული routes/web.php ფაილი და იქმნება ახალი, შესაბამისად - იკარგება უკვე შექმნილი მარშრუტებიც !!! რაც, ჩემი აზრით, არც ისე სასიამოვნო სიურპრიზია და ეს მომენტი აუცილებლად უნდა გავითვალისწინოთ ხოლმე ამ პაკეტის გამოყენებისას. თუ ვქმნით პროექტს, რომელშიც გვაქვს რეგისტრაცია/აუტენტიფიკაციის სისტემა, უმჯობესია, რომ ამ პაკეტის ინსტალაციით დავიწყოთ ხოლმე მუშაობა, ხოლო თუ უკვე არსებულ პროექტში გვიწევს ამ სისტემის ჩაშენება მაშინ პაკეტის ინსტალაციამდე აუცილებლად უნდა შევინახოთ ძველი მარშრუტები.
ეს კი routes/web.php ფაილის საჭირო მდგომარეობა : კოპირება
use Illuminate\Support\Facades\Route;

use App\Http\Middleware\Admin;
use App\Http\Controllers\Admin\AdminsController;
use App\Http\Controllers\Admin\ContactsController;
use App\Http\Controllers\Admin\LoginController;
use App\Http\Controllers\Admin\ArticlesController;

use App\Http\Controllers\Front\IndexController;

Route::group(['prefix' => LaravelLocalization::setLocale(),'middleware' => [ 'localeSessionRedirect', 'localizationRedirect', 'localeViewPath' ]], function(){
   
    // მთავარი გვერდი
    Route::get('/', [IndexController::class, 'index'])->name('index');
    // სიახლის შიდა გვერდი
    Route::get('/article/{id}', [IndexController::class, 'article'])->name('article');
    
    Route::get('/dashboard', function () {
        return view('dashboard');
    })->middleware(['auth'])->name('dashboard');
    
});

Route::group(['middleware' => ['admin'], 'prefix' => 'admin'], function () {
    
    // ავტორიზაცია და სისტემიდან გასვლა
    Route::get('/login', [LoginController::class, 'showLogin'])->withoutMiddleware([Admin::class])->name('ShowLogin');
    Route::post('/signin', [LoginController::class, 'login'])->withoutMiddleware([Admin::class])->name('AdminLogin');
    Route::get('/logout', [LoginController::class, 'logout'])->name('AdminLogout');
    
    // ადმინისტრატორის პანელის მთავარი გვერდი 
    Route::get('/', function () {
        return view('admin.index');
    })->name('AdminMainPage');
    
    // ადმინისტრატორები
    Route::resource('admins', AdminsController::class);
    
    // საკონტაქტო ინფორმაციის გვერდი
    Route::resource('contacts', ContactsController::class, ['only' => ['edit','update']]);
    Route::get('/contacts/cache', [ContactsController::class, 'cache'])->name('contacts.cache');
    
    // სიახლეები
    Route::resource('articles', ArticlesController::class);

});                      
    
require __DIR__.'/auth.php';
                

რეგისტრაცია

როგორც ვიცით, Laravel Breeze-ის ინსტალაიის შემდეგ, აუტენტიფიკაცია/რეგისტრაციასთან დაკავშირებული ფაილები შეიქმნებოდა შემდეგ საქაღალდეებში : კონტროლერები - App/Http/Controllers/Auth საქაღალდეში, წარმოდგენის შაბლონები - resources/views/auth საქაღალდეში. თუ ახლა გავხსნით http://127.0.0.1:8000/register ბმულს, ვიხილავთ რეგისტრაციის გაუსტილავ ფორმას. გამოვასწოროთ ეს ხარვეზი, resources/views/auth/register.blade.php ფაილში შევიტანოთ შემდეგი კოდი : კოპირება
@extends('front.layout')
@section('title', trans('site.register'))
@section('content')
<div class="container">
    
    @if($errors->any())
        <div class="row">
            <div class="col-md-4 offset-4">
                <div class="alert alert-danger">
                    <ul>
                        @foreach ($errors->all() as $error)
                            <li>{{ $error }}</li>
                        @endforeach
                    </ul>
                </div>
            </div>
        </div>
    @endif
    
    <div class="row">
        <div class="col-md-4 offset-md-4">
            <form method="POST" action="{{ route('register') }}">
                @csrf
                <div class="form-group">
                    <label>@lang('site.name')</label>
                    <input type="text" name="name" value="{{ old('name') }}" class="form-control" required>
                </div>
                <div class="form-group">
                    <label>@lang('site.email')</label>
                    <input type="email" name="email" value="{{ old('email') }}" class="form-control" required>
                </div>
                <div class="form-group">
                    <label>@lang('site.password')</label>
                    <input type="password" name="password" class="form-control" required>
                </div>
                <div class="form-group">
                    <label>@lang('site.re_password')</label>
                    <input type="password" name="password_confirmation" class="form-control" required>
                </div>
                <button type="submit" class="btn btn-primary mt-3 mb-3" style="width: 100%;">
                    @lang('site.register')
                </button>
            </form>
        </div>
    </div>
</div>
@endsection
                

რეგისტრაციისათვის უნდა ვესტუმროთ შემდეგ ბმულს : http://example.ge/register

რეგისტრაციის შემდეგ სისტემა გადაგვამისამართებს მომხმარებლის კაბინეტში, app/Http/Controllers/Auth/RegisteredUserController კონტროლერის store მეთოდი : კოპირება
public function store(Request $request)
{
    $request->validate([
        'name' => 'required|string|max:255',
        'email' => 'required|string|email|max:255|unique:users',
        'password' => ['required', 'confirmed', Rules\Password::defaults()],
    ]);

    $user = User::create([
        'name' => $request->name,
        'email' => $request->email,
        'password' => Hash::make($request->password),
    ]);

    event(new Registered($user));

    Auth::login($user);

    return redirect(RouteServiceProvider::HOME);
}
                
app/Providers/RouteServiceProvider : კოპირება
...

public const HOME = '/dashboard';

...
                

UserController

მომხმარებლისათვის საჭირო ფუნქციონალთან სამუშაოდ შევქმნათ კონტროლერი : კოპირება
php artisan make:controller Front/UserController
                
ამ კონტროლერში სულ გვექნება სამი მეთოდი : პირადი კაბინეტის გამოსატანი მეთოდი, პირადი ინფორმაციის შეცვლის მეთოდი და პაროლის განახლების მეთოდი. განვსაზღვროთ შესაბამისი მარშრუტები : კოპირება
use Illuminate\Support\Facades\Route;

use App\Http\Middleware\Admin;
use App\Http\Controllers\Admin\AdminsController;
use App\Http\Controllers\Admin\ContactsController;
use App\Http\Controllers\Admin\LoginController;
use App\Http\Controllers\Admin\ArticlesController;

use App\Http\Controllers\Front\IndexController;
use App\Http\Controllers\Front\UserController;

Route::group(['prefix' => LaravelLocalization::setLocale(),'middleware' => [ 'localeSessionRedirect', 'localizationRedirect', 'localeViewPath' ]], function(){
   
    // მთავარი გვერდი
    Route::get('/', [IndexController::class, 'index'])->name('index');
    // სიახლის შიდა გვერდი
    Route::get('/article/{id}', [IndexController::class, 'article'])->name('article');
    
    
    Route::middleware(['auth'])->group(function () {

        Route::get('/dashboard', [UserController::class, 'dashboard'])->name('dashboard');
        Route::post('/update_data', [UserController::class, 'update_data'])->name('update_data');
        Route::post('/update_password', [UserController::class, 'update_password'])->name('update_password');

    });  
    
    
});

Route::group(['middleware' => ['admin'], 'prefix' => 'admin'], function () {
    
    // ავტორიზაცია და სისტემიდან გასვლა
    Route::get('/login', [LoginController::class, 'showLogin'])->withoutMiddleware([Admin::class])->name('ShowLogin');
    Route::post('/signin', [LoginController::class, 'login'])->withoutMiddleware([Admin::class])->name('AdminLogin');
    Route::get('/logout', [LoginController::class, 'logout'])->name('AdminLogout');
    
    // ადმინისტრატორის პანელის მთავარი გვერდი 
    Route::get('/', function () {
        return view('admin.index');
    })->name('AdminMainPage');
    
    // ადმინისტრატორები
    Route::resource('admins', AdminsController::class);
    
    // საკონტაქტო ინფორმაციის გვერდი
    Route::resource('contacts', ContactsController::class, ['only' => ['edit','update']]);
    Route::get('/contacts/cache', [ContactsController::class, 'cache'])->name('contacts.cache');
    
    // სიახლეები
    Route::resource('articles', ArticlesController::class);

});                      
    
require __DIR__.'/auth.php';
                
resources/views/dashboard.blade.php მეთოდი გადავიტანოთ resources/views/front საქაღალდეში, ასევე აღვწეროთ UserController კონტროლერის dashboard მეთოდი : კოპირება
namespace App\Http\Controllers\Front;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;

class UserController extends Controller
{
    public function dashboard()
    {
        return view('front.dashboard');      
    }
}
                
თუ ახლა შევალთ http://127.0.0.1:8000/dashboard ბმულზე, ვიხილავთ მომხმარებლის, ასევე გაუსტილავ კაბინეტს. გამოვასწოროთ ეს ხარვეზიც, resources/views/front/dashboard.blade.php ფაილში შევიტანოთ შემდეგი კოდი : კოპირება
@extends('front.layout')
@section('title', trans('site.dashboard'))
@section('content')
<div class="container">  
      
    @if($errors->any())
        <div class="row">
            <div class="col-md-4 offset-4">
                <div class="alert alert-danger">
                    <ul>
                        @foreach ($errors->all() as $error)
                            <li>{{ $error }}</li>
                        @endforeach
                    </ul>
                </div>
            </div>
        </div>
    @endif  
    @if(Session::has('updating_results'))
        <div class="row">
            <div class="col-md-4 offset-4">
                <div class="alert alert-{{ Session::get('updating_results')['class'] }}">
                    {{ Session::get('updating_results')['message'] }}
                </div>
            </div>
        </div>
    @endif  
      
    <div class="row">
        <div class="col-md-4 offset-md-4">
            
            <h2>@lang('site.personal_data')</h2>
            <form method="POST" action="{{ route('update_data') }}">
                 @csrf
                <div class="form-group">
                    <label>@lang('site.name')</label>
                    <input type="text" name="name" value="{{ Auth::user()->name }}" class="form-control" required>
                </div>
                <div class="form-group">
                    <label>@lang('site.email')</label>
                    <input type="email" name="email" value="{{ Auth::user()->email }}" class="form-control" required>
                </div>
                <button type="submit" class="btn btn-primary mt-3 mb-3" style="width: 100%;">
                    @lang('site.update')
                </button>
            </form>
            
            <h2>@lang('site.change_password')</h2>
            <form method="POST" action="{{ route('update_password') }}">
                @csrf
                <div class="form-group">
                    <label>@lang('site.old_password')</label>
                    <input type="password" name="old_password" class="form-control" required>
                </div>
                <div class="form-group">
                    <label>@lang('site.new_password')</label>
                    <input type="password" name="new_password" class="form-control" required>
                </div>
                <div class="form-group">
                    <label>@lang('site.re_new_password')</label>
                    <input type="password" name="new_password_confirmation" class="form-control" required>
                </div>
                <button type="submit" class="btn btn-primary mt-3 mb-3" style="width: 100%;">
                    @lang('site.update')
                </button>
            </form>
            
        </div>
    </div>
</div>
@endsection
                
UserController კონტროლერის მეთოდები კი იქნება ასეთი : კოპირება
namespace App\Http\Controllers\Front;

use Hash;
use Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;

class UserController extends Controller
{
    public function dashboard()
    {
        return view('front.dashboard');      
    }
    
    public function update_data(Request $request)
    {
        $this->validate($request, [
            'name' => 'required|string|max:255',
            'email' => 'required|string|email|max:255|unique:users,email,' . Auth::user()->id,
        ]);

        $update = Auth::user()->update([
            'name' => $request->name,
            'email' => $request->email
        ]);

        if($update) 
        {
            $request->session()->flash('updating_results', [
                'class' => 'success', 
                'message' => trans('site.info_updated')
            ]);
        } 
        else
        {
            $request->session()->flash('updating_results', [
                'class' => 'danger', 
                'message' => trans('site.update_error')
            ]);
        }
        
        return redirect()->back();
    }

    public function update_password(Request $request)
    {
        // გადავამოწმოთ ემთხვევა თუ არა შეყვანილი ძველი პაროლი ავტორიზებული მომხმარებლის პაროლს
        if(!Hash::check($request->old_password,  Auth::user()->password))
        {
            $request->session()->flash('updating_results', [
                'class' => 'danger', 
                'message' => trans('site.old_password_error')
            ]);
            
            return redirect()->back();
        }
        
        $this->validate($request, [
            'old_password' => 'required|string|min:8',
            'new_password' => 'required|string|min:8|confirmed',
        ]);
        
        $update = Auth::user()->update([
            'password' => Hash::make($request->new_password)
        ]);

        if($update) 
        {
            $request->session()->flash('updating_results', [
                'class' => 'success', 
                'message' => trans('site.info_updated')
            ]);
        } 
        else
        {
            $request->session()->flash('updating_results', [
                'class' => 'danger', 
                'message' => trans('site.update_error')
            ]);
        }
        
        return redirect()->back();        
    }
}                
                
ამ კოდში ახალი და განსაკუთრებული არაფერია და ამიტომ დაკონკრეტებაზე აღარ დავკარგავ დროს.

რეგისტრაცია/ავტორიზაციისა და სისტემიდან გასვლის ბმულები

ჩვენი პროექტის ძირითად შაბლონში ჩავამატოდ რეგისტრაცია/ავტორიზაციისა და სისტემიდან გასვლის ბმულები, resources/views/front/layout.blade.php ფაილში, კონტაქტის გვერდის ბმულსა და ენების გადამრთველს შორის ჩავამატოთ შემდეგი კოდი : კოპირება
@auth
    <li class="nav-item">
        <a href="{{ route('dashboard') }}" class="nav-link px-lg-3 py-3 py-lg-4">
            @lang('site.dashboard')
        </a>
    </li>
    <li class="nav-item">
        <a href="#!" 
           class="nav-link px-lg-3 py-3 py-lg-4" 
           onclick="event.preventDefault(); document.getElementById('logout-form').submit();"
        >
            @lang('site.logout')
        </a>
    </li>
    
    <form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;">
        @csrf
    </form>
    
@else
    <li class="nav-item">
        <a class="nav-link px-lg-3 py-3 py-lg-4" href="{{ route('register') }}">
            @lang('site.register')
        </a>
    </li>
    <li class="nav-item">
        <a class="nav-link px-lg-3 py-3 py-lg-4" href="{{ route('login') }}">
            @lang('site.login')
        </a>
    </li>
@endauth
                
როგორც ვხედავთ სისტემიდან გამოსასვლელად გამოვიყენეთ HTML ფორმა, რომელიც routes/auth ფაილში აღწერილი logout მარშუტის შესაბამისად აკითხავს app/Http/Controllers/Auth/AuthenticatedSessionController კონტროლერის destroy მეთოდს : კოპირება
public function destroy(Request $request)
{
    Auth::guard('web')->logout();

    $request->session()->invalidate();

    $request->session()->regenerateToken();

    return redirect('/');
}
                

ავტორიზაცია

თუ ახლა გავხსნით http://127.0.0.1:8000/login ბმულს, ვიხილავთ ავტორიზაციის გაუსტილავ ფორმას. გამოვასწოროთ ეს ხარვეზი, resources/views/auth/login.blade.php ფაილში შევიტანოთ შემდეგი კოდი : კოპირება
@extends('front.layout')
@section('title', trans('site.login'))
@section('content')
<div class="container"> 
       
    @if($errors->any())
        <div class="row">
            <div class="col-md-4 offset-4">
                <div class="alert alert-danger">
                    <ul>
                        @foreach ($errors->all() as $error)
                            <li>{{ $error }}</li>
                        @endforeach
                    </ul>
                </div>
            </div>
        </div>
    @endif   
     
    <div class="row">
        <div class="col-md-4 offset-md-4">
            <form method="POST" action="{{ route('login') }}">
                @csrf
                <div class="form-group">
                    <label>@lang('site.email')</label>
                    <input type="email" name="email" value="{{ old('email') }}" class="form-control" required>
                </div>
                <div class="form-group">
                    <label>@lang('site.password')</label>
                    <input type="password" name="password" class="form-control" required>
                </div>
                <button type="submit" class="btn btn-primary mt-3 mb-3" style="width: 100%;">
                    @lang('site.login')
                </button>
            </form>
        </div>
    </div>
</div>
@endsection                
                
ამით ავტორიზაცია/რეგისტრაციის ფუნქციონალი დასრულებულია.
15. კომენტარების სისტემა
პირველ რიგში შევქმნათ კომენტარების მოდელი და მასში ასევე აღვწეროთ კავშირი Article და User მოდელებთან : კოპირება
php artisan make:model Comment     
                
კოპირება
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Comment extends Model
{
    public function article()
    {
        return $this->belongsTo(Article::class);
    }

    public function user()
    {
        return $this->belongsTo(User::class);
    }
}    
                
ახლა კომენტარებთან კავშირი აღვწეროთ Article მოდელში : კოპირება
public function comments()
{
    return $this->hasMany(Comment::class);
} 
                
ახლა შევქმნათ კომენტარის დასამატებელი მეთოდის შესაბამისი მარშრუტი : კოპირება
use Illuminate\Support\Facades\Route;

use App\Http\Middleware\Admin;
use App\Http\Controllers\Admin\AdminsController;
use App\Http\Controllers\Admin\ContactsController;
use App\Http\Controllers\Admin\LoginController;
use App\Http\Controllers\Admin\ArticlesController;

use App\Http\Controllers\Front\IndexController;
use App\Http\Controllers\Front\UserController;

Route::group(['prefix' => LaravelLocalization::setLocale(),'middleware' => [ 'localeSessionRedirect', 'localizationRedirect', 'localeViewPath' ]], function(){
   
    // მთავარი გვერდი
    Route::get('/', [IndexController::class, 'index'])->name('index');
    // სიახლის შიდა გვერდი
    Route::get('/article/{id}', [IndexController::class, 'article'])->name('article');
    
    Route::middleware(['auth'])->group(function () {

        Route::get('/dashboard', [UserController::class, 'dashboard'])->name('dashboard');
        Route::post('/update_data', [UserController::class, 'update_data'])->name('update_data');
        Route::post('/update_password', [UserController::class, 'update_password'])->name('update_password');
        Route::post('/comment/{id}', [UserController::class, 'comment'])->name('comment');
    });  
    
});

Route::group(['middleware' => ['admin'], 'prefix' => 'admin'], function () {
    
    // ავტორიზაცია და სისტემიდან გასვლა
    Route::get('/login', [LoginController::class, 'showLogin'])->withoutMiddleware([Admin::class])->name('ShowLogin');
    Route::post('/signin', [LoginController::class, 'login'])->withoutMiddleware([Admin::class])->name('AdminLogin');
    Route::get('/logout', [LoginController::class, 'logout'])->name('AdminLogout');
    
    // ადმინისტრატორის პანელის მთავარი გვერდი 
    Route::get('/', function () {
        return view('admin.index');
    })->name('AdminMainPage');
    
    // ადმინისტრატორები
    Route::resource('admins', AdminsController::class);
    
    // საკონტაქტო ინფორმაციის გვერდი
    Route::resource('contacts', ContactsController::class, ['only' => ['edit','update']]);
    Route::get('/contacts/cache', [ContactsController::class, 'cache'])->name('contacts.cache');
    
    // სიახლეები
    Route::resource('articles', ArticlesController::class);

});                      
    
require __DIR__.'/auth.php';
                
UserController-ის comment მეთოდი იქნება ასეთი : კოპირება
public function comment(Request $request)
{
    $this->validate($request,[
        'article_id' => 'required|integer',
        'comment' => 'required|string|max:255',
    ]); 

    $article = Article::find($request->article_id); 

    if(!$article)
    {
        return redirect()->back();
    }

    $create = Comment::create([
        'user_id' => Auth::user()->id,
        'article_id' => $request->article_id,
        'comment' => $request->comment, 
        'created_at' => new \DateTime()
    ]);

    if($create) 
    {
        $request->session()->flash('inserting_results', [
            'class' => 'success', 
            'message' => trans('site.waiting_for_admin_confirm')
        ]);
    } 
    else
    {
        $request->session()->flash('inserting_results', [
            'class' => 'danger', 
            'message' => trans('site.inserting_error')
        ]);
    }

    return redirect()->back();
}
                
როგორც ვიცით create მეთოდის გამოყენებისას მოდელში უნდა განვსაზღროთ $fillable თვისების მნიშვნელობა, Comment მოდელი : კოპირება
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Comment extends Model
{
    
    protected $fillable = [
        'user_id',
        'article_id',
        'comment'
    ];
    
    
    public function article()
    {
        return $this->belongsTo(Article::class);
    }
    
    public function user()
    {
        return $this->belongsTo(User::class);
    }
}    
                
შევიტანოთ ცვლილებები resources/views/front/article.blade.php ფაილში : კოპირება
@extends('front.layout')
@section('title', $article->title)
@section('content')
<article class="mb-4">
    <div class="container px-4 px-lg-5">
        <div class="row gx-4 gx-lg-5 justify-content-center">
            
            <!-- სიახლის ტექსტი --> 
            <div class="col-md-10 col-lg-8 col-xl-7">
                {!! $article->text !!}
            </div>
            
            <!-- კომენტარების არე --> 
            <div class="col-md-10 col-lg-8 col-xl-7 mt-3">
                
                
                <!-- კომენტარების მთვლელი -->
                <h4 class="mt-5">@lang('site.comments') ({{ $article->comments->where('confirmed')->count() }})</h4>
                <hr class="mt-3">
                
                <!-- თუ გავილი გვაქვს ავტორიზაცია გამოვიტანოთ დასამატებელი ფორმა -->
                @auth
                    <form method="POST" action="{{ route('comment',$article->id) }}">
                        @if($errors->any())
                            <div class="alert alert-danger">
                                <ul>
                                    @foreach ($errors->all() as $error)
                                        <li>{{ $error }}</li>
                                    @endforeach
                                </ul>
                            </div>                            
                        @endif  
                        @if(Session::has('inserting_results'))
                            <div class="alert alert-{{ Session::get('inserting_results')['class'] }}">
                                {{ Session::get('inserting_results')['message'] }}
                            </div>
                        @endif  
                        @csrf
                        <input type="hidden" name="article_id" value="{{ $article->id }}">
                        <div class="form-group">
                            <textarea name="comment" class="form-control" rows="5" placeholder="@lang('site.comment')"></textarea>
                        </div>
                        <div class="form-group mt-1">
                            <input type="submit" value="@lang('site.add')" class="form-control btn btn-success">
                        </div>
                    </form>
                @else
                    <div class="alert alert-warning text-center">
                        <small>@lang('site.login_to_comment')</small>
                    </div>
                @endauth
                
                <!-- კომენტარები -->
                <hr class="mt-3">
                @forelse($article->comments->where('confirmed',1)->reverse() as $comment)
                    <div class="alert alert-primary">
                        {{ $comment->comment }}
                        <p>
                            @lang('site.author') : {{ $comment->user->email }}</span>
                        </p>
                        <p>
                            @lang('site.date') : {{ $comment->created_at }}</span>
                        </p>
                    </div>
                @empty
                    <div class="alert alert-warning text-center">
                        <small>@lang('site.no_comment')</small>
                    </div>
                @endforelse
                
                
            </div>
            
        </div>
    </div>
</article>
@endsection
                
ვფიქრობ ამ კოდში განსაკუთრებული და ახალი არაფერია, ამიტომ აღრ დავწვრილმანდეთ.

მას შემდეგ, რაც მომხმარებელი კომენტარს დაამატებს საჭიროა,რომ ადმინისტრატორმა დაადასტუროს ეს კომენტარი, წინააღმდეგ შემთხვევაში, კომენტარი არ გამოჩნდება საიტზე.

16. კომენტარების მოდული
შევქმნათ კომენტარების მოდულთან სამუშო კონტროლერი - CommentsController : კოპირება
php artisan make:controller Admin/AdminsController 
                
კოპირება
namespace App\Http\Controllers\Admin;

use DB;
use App\Models\Comment;
use Illuminate\Http\Request;

class CommentsController extends BaseController
{
    // ჩამონათვალის გვერდი
    public function index()
    {
        //
    }

    // კომენტარის წაშლა მბ-ში
    public function destroy(Request $request, $id)
    {
        //   
    }
    
    // კომენტარის დადასტურება
    protected function confirm(Request $request)
    {
        // 
    }  
}             
                
ახლა შევქმნათ საჭირო მარშრუტები : კოპირება
use Illuminate\Support\Facades\Route;

use App\Http\Middleware\Admin;
use App\Http\Controllers\Admin\AdminsController;
use App\Http\Controllers\Admin\ContactsController;
use App\Http\Controllers\Admin\LoginController;
use App\Http\Controllers\Admin\ArticlesController;
use App\Http\Controllers\Admin\CommentsController;

use App\Http\Controllers\Front\IndexController;
use App\Http\Controllers\Front\UserController;

Route::group(['prefix' => LaravelLocalization::setLocale(),'middleware' => [ 'localeSessionRedirect', 'localizationRedirect', 'localeViewPath' ]], function(){
   
    // მთავარი გვერდი
    Route::get('/', [IndexController::class, 'index'])->name('index');
    // სიახლის შიდა გვერდი
    Route::get('/article/{id}', [IndexController::class, 'article'])->name('article');
    
    Route::middleware(['auth'])->group(function () {

        Route::get('/dashboard', [UserController::class, 'dashboard'])->name('dashboard');
        Route::post('/update_data', [UserController::class, 'update_data'])->name('update_data');
        Route::post('/update_password', [UserController::class, 'update_password'])->name('update_password');
        Route::post('/comment/{id}', [UserController::class, 'comment'])->name('comment');
    });  
    
});

Route::group(['middleware' => ['admin'], 'prefix' => 'admin'], function () {
    
    // ავტორიზაცია და სისტემიდან გასვლა
    Route::get('/login', [LoginController::class, 'showLogin'])->withoutMiddleware([Admin::class])->name('ShowLogin');
    Route::post('/signin', [LoginController::class, 'login'])->withoutMiddleware([Admin::class])->name('AdminLogin');
    Route::get('/logout', [LoginController::class, 'logout'])->name('AdminLogout');
    
    // ადმინისტრატორის პანელის მთავარი გვერდი 
    Route::get('/', function () {
        return view('admin.index');
    })->name('AdminMainPage');
    
    // ადმინისტრატორები
    Route::resource('admins', AdminsController::class);
    
    // საკონტაქტო ინფორმაციის გვერდი
    Route::resource('contacts', ContactsController::class, ['only' => ['edit','update']]);
    Route::get('/contacts/cache', [ContactsController::class, 'cache'])->name('contacts.cache');
    
    // სიახლეები
    Route::resource('articles', ArticlesController::class);

    
    // კომენტარების გვერდი
    Route::resource('comments', CommentsController::class, ['only' => ['index','destroy']]);
    Route::post('/comments/confirm', [CommentsController::class, 'confirm'])->name('comments.confirm');
    

});                      
    
require __DIR__.'/auth.php';
                
შევიტანოთ ცვლილებები CommentsController-ში : კოპირება
namespace App\Http\Controllers\Admin;

use DB;
use App\Models\Comment;
use Illuminate\Http\Request;

class CommentsController extends BaseController
{
    // ჩამონათვალის გვერდი
    public function index()
    {
        $items = Comment::join('users','comments.user_id','users.id')
                ->join('articles_translates','comments.article_id','articles_translates.article_id')
                ->where('articles_translates.lang','ka')
                ->select('comments.*','users.email','articles_translates.title AS article')
                ->orderBy('id','DESC')
                ->get();
        
        return view('admin.comments.index', compact('items'));
    }

    // კომენტარის წაშლა მბ-ში
    public function destroy(Request $request, $id)
    {
        $delete = Comment::find($id)->delete();
        $request->session()->flash('result', $delete);
        
        return redirect()->back();      
    }
    
    // კომენტარის დადასტურება
    protected function confirm(Request $request)
    {
        if($request->ajax())
        {
            $id = $request->id;
            $item = DB::table('comments')->find($id);

            if(!$item)
            {
                return response()->json([
                    'success' => false, 
                    'message' => 'ჩანაწერი ვერ მოიძებნა'
                ]);
            }

            $affected = DB::table('comments')->where('id', $id)->update(['confirmed' => $item->confirmed ? 0 : 1]);

            if(!$affected)
            {
                return response()->json([
                    'success' => false, 
                    'message' => 'ჩანაწერი ვერ განახლდა'
                ]);
            }

            return response()->json([
                'success' => true, 
                'message' => 'ჩანაწერი განახლდა'
            ]);
        }        
    }  
}             
                
ახლა შევქმნათ resources/views/admin/comments/index.blade.php წარმოდგენის ფაილი კომენტარების ჩამონათვალის გვერდისათვის და შევიტანოთ მასში შემდეგი კოდი : კოპირება
@extends('admin.layout')
@section('title','კომენტარები')
@section('content')
<div class="container-fluid px-4">
    <h1 class="mt-4">კომენტარები</h1>
    
    <div class="row">
        
        
        @if(Session::has('result'))
        <div class="col-md-12">
            <div class="alert alert-{{ Session::get('result') ? 'success' : 'danger'}}">
                ოპერაცია {{ Session::get('result') ? 'წარმატებით' : 'წარუმატებლად'}} დასრულდა
            </div>
        </div>
        @endif
        
        
        <div class="col-md-12">
            <table class="table">
                <thead>
                    <tr>
                        <th scope="col">#</th>
                        <th scope="col">გამოქვეყნდეს</th>
                        <th scope="col">ავტორი</th>
                        <th scope="col">კომენტარი</th>
                        <th scope="col">სიახლე</th>
                        <th scope="col">თარიღი</th>
                        <th scope="col"></th>
                    </tr>
                </thead>
                <tbody>
                @foreach($items as $key => $item)
                        <tr>
                            <th scope="row">{{ ++$key }}</th>
                            <td>
                                <input type="checkbox" class="confirm-checkbox" data-id="{{ $item->id }}" {{ $item->confirmed ? 'checked' : '' }}>
                            </td>
                            <td>{{ $item->email }}</td>
                            <td>{{ $item->comment }}</td>
                            <td>{{ $item->article }}</td>
                            <td>{{ $item->created_at }}</td>
                            <td>
                                
                                <form action="{{ route('comments.destroy', $item->id) }}" method="post">
                                    @csrf
                                    <input type="hidden" name="_method" value="delete">
                                    <a href="#!" class="btn btn-sm btn-danger btn-destroy">
                                        <i class="fa fa-trash"></i> 
                                    </a>
                                </form>
                                
                            </td>
                        </tr>
                        @endforeach
                </tbody>
            </table>
        </div>
    </div>
</div>
@endsection


@section('script')
<script>
    
    $('.confirm-checkbox').change(function() 
    {
        let id = $(this).data('id');

        $.ajax({
            url: "/admin/comments/confirm", // მარშრუტის შესაბამისი ბმული
            type: 'post',
            dataType: 'json',
            data: {id: id, _token : '{{ csrf_token() }}'} // კომენტარის id და POST მეთოდისათვის საჭირო თოქენი
        }).done(function (data){

            alert(data.message);

        });
    });
    
    $('.btn-destroy').on('click', function(){
        
        if(confirm('დარწმუნებული ხართ ?'))
        {
            $(this).parent('form').submit();         
        }
        
    });
    
</script>
@endsection

                
ჩავამატოთ კომენტარების მოდულის ბმულები მენიუში და ადმინსტრატორის განყოფილების მთავარ გვერდზე : კოპირება
            
...

<!-- გვერდითი მენიუ -->
<div class="nav">
    <a class="nav-link" href="{{ route('AdminMainPage') }}">
        <div class="sb-nav-link-icon"><i class="fas fa-tachometer-alt"></i></div>
        მთავარი
    </a>
    <a class="nav-link" href="{{ route('admins.index') }}">
        <div class="sb-nav-link-icon"><i class="fas fa-user"></i></div>
        ადმინისტრატორები
    </a>
    <a class="nav-link" href="{{ route('contacts.edit', 1) }}">
        <div class="sb-nav-link-icon"><i class="fas fa-phone"></i></div>
        საკონტაქტო ინფორმაცია
    </a>
    <a class="nav-link" href="{{ route('articles.index') }}">
        <div class="sb-nav-link-icon"><i class="fas fa-newspaper"></i></div>
        სიახლეები
    </a>
    <a class="nav-link" href="{{ route('comments.index') }}">
        <div class="sb-nav-link-icon"><i class="fas fa-comment"></i></div>
        კომენტარები
    </a>
</div>

...     
                
კოპირება
<div class="container-fluid px-4">
    <h1 class="mt-4">მთავარი</h1>
    <ol class="breadcrumb mb-4">
        <li class="breadcrumb-item active">მთავარი</li>
    </ol>
    <div class="row">
        <div class="col-xl-3 col-md-6">
            <div class="card bg-primary text-white mb-4">
                <div class="card-body">ადმინსტრატორები</div>
                <div class="card-footer d-flex align-items-center justify-content-between">
                    <a class="small text-white stretched-link" href="{{ route('admins.index') }}">სრულად</a>
                    <div class="small text-white"><i class="fas fa-angle-right"></i></div>
                </div>
            </div>
        </div>
        <div class="col-xl-3 col-md-6">
            <div class="card bg-success text-white mb-4">
                <div class="card-body">საკონტაქტო ინფორმაცია</div>
                <div class="card-footer d-flex align-items-center justify-content-between">
                    <a class="small text-white stretched-link" href="{{ route('contacts.edit', 1) }}">სრულად</a>
                    <div class="small text-white"><i class="fas fa-angle-right"></i></div>
                </div>
            </div>
        </div>
        <div class="col-xl-3 col-md-6">
            <div class="card bg-warning text-white mb-4">
                <div class="card-body">სიახლეები</div>
                <div class="card-footer d-flex align-items-center justify-content-between">
                    <a class="small text-white stretched-link" href="{{ route('articles.index') }}">სრულად</a>
                    <div class="small text-white"><i class="fas fa-angle-right"></i></div>
                </div>
            </div>
        </div>
        <div class="col-xl-3 col-md-6">
            <div class="card bg-danger text-white mb-4">
                <div class="card-body">კომენტარები</div>
                <div class="card-footer d-flex align-items-center justify-content-between">
                    <a class="small text-white stretched-link" href="{{ route('comments.index') }}">სრულად</a>
                    <div class="small text-white"><i class="fas fa-angle-right"></i></div>
                </div>
            </div>
        </div>
    </div>
</div>
                
17. ქეშირებული ინფორმაციის გაზიარება წარმოდგენის ფაილებისათვის
ხშირად არის საჭირო ერთი და იგივე ინფორმაციის, წარმოდგენის სხვადასხვა ფაილებისათვის გაზიარება, მაგალითად საკონტაქტო ინფორმაციის ველები გვხვდება ყველა გვერდზე :



გავუზიაროთ ეს ინფორმაცია წარმოდგენის ფაილებს და აქვე გამოვიყენოთ ქეშირებები, ეს ყოველივე გავაკეთოთ app/Http/Controllers/Controller.php ფაილში : კოპირება
namespace App\Http\Controllers;

use DB;
use View;
use Cache;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Routing\Controller as BaseController;

class Controller extends BaseController
{
    use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
    
    public function __construct() 
    {
        View::share('contact', Cache::has('contacts') ? Cache::get('contacts') : DB::table('contacts')->first());      
    } 
    
}
                
ანუ სისტემას ვეუბნებით, რომ View ფასადის share მეთოდის მეშვეობით წარმოდგენის ფაილებს გაუზიაროს $contact ცვლადში მოქცეული საკონტაქტო ინფორმაცია, რომელსაც იგი ამოიღებს ქეშიდან თუ დაქეშილი გვაქვს ეს ინფორმაცია, წინააღმდეგ შემთხვევაში კი მიაკითხავს მონაცემთა ბაზას.

ახლა ეს ინფორმაცია გამოვიყენოთ წარმოდგენის ფაილში, შევიტანოთ ცვლილებები resources/views/front/layout.blade.php ფაილში :

კოპირება
...

<ul class="list-inline text-center">
    <li class="list-inline-item">
        <a href="tel:{{ $contact->phone }}">
            <span class="fa-stack fa-lg">
                <i class="fas fa-circle fa-stack-2x"></i>
                <i class="fas fa-phone fa-stack-1x fa-inverse"></i>
            </span>
        </a>
    </li>
    <li class="list-inline-item">
        <a href="mailto:{{ $contact->email }}">
            <span class="fa-stack fa-lg">
                <i class="fas fa-circle fa-stack-2x"></i>
                <i class="fas fa-envelope fa-stack-1x fa-inverse"></i>
            </span>
        </a>
    </li>
</ul>

...