اذهب إلى المحتوى

سمير عبود

الأعضاء
  • المساهمات

    3552
  • تاريخ الانضمام

  • تاريخ آخر زيارة

  • عدد الأيام التي تصدر بها

    34

كل منشورات العضو سمير عبود

  1. أولا يجب إضافة قواعد التحقق في المُكون بالشكل التالي مثلاً: protected $rules = [ 'title' => 'required|min:10', 'description' => 'required' ]; بعد ذلك تستدعي دالة التحقق في الدالة addTask: <?php public function addTask() { $this->validate(); // ... } ثم تضيف التابع updated للمُكون ليُصبح بالشكل التالي: <?php class AppAddTask extends Component { public $title; public $description; protected $rules = [ 'title' => 'required|min:10', 'description' => 'required' ]; public function render() { return view('livewire.app-add-task'); } public function updated($property) { $this->validateOnly($property); } public function addTask() { $this->validate(); // ... } } ثم في صفحة عرض المُكون تعرض رسائل الخطأ: <div> <h3 class="text-center">Add New Task</h3> <div class="form-group"> <label for="title">Title</label> <input type="text" wire:model="title" class="form-control @error('title') is-invalid @enderror"> @error('title') <span class="error" role="alert"> <strong class="text-danger">{{ $message }}</strong> </span> @enderror </div> <div class="form-group"> <label for="description">Description</label> <textarea wire:model="description" class="form-control @error('description') is-invalid @enderror"></textarea> @error('description') <span class="error" role="alert"> <strong class="text-danger">{{ $message }}</strong> </span> @enderror </div> <div class="form-group"> <button wire:click.prevent="addTask" class="btn btn-primary btn-block">Add</button> </div> </div> و بهذا الشكل سيتم التحقق في الوقت الفعلي و يتم عرض أي رسالة خطأ إن وجدت.
  2. الأفضل دائماً إستخدام إطار عمل جاهز عند إنشاء مشاريعك التي تريد أن تطلقها على شبكة الإنترنيت أو مشاريع لعُملائك لأن هناك العديد من الثغرات و المشاكل التي قابلت المُطورين من قبل و قامو بإيجاد حل لها في أطر العمل و هذا من أجل حماية موقعك و ضمان خصوصية مستخدمين تطبيقك. و لهذا تجد مُعظم الخبراء في المجال ينصحون بهذا الأمر فإطار العمل لا يُسهل و يُسرع الإنتاجية فقط و إنما يحميك من مُعظم الثغرات الموجودة، و إذا حاولت أن تُعالج موضوع الحماية بنفسك ستتعب كثيراً و ستقل إنتاجيتك فبدل أن تُركز على خصائص التطبيق نفسه تجد نفسك تبحث عن الثغرات و كيفية مُعالجتها خصوصاً إن كان الشخص مبتدئ و لا يفقه شيء في مواضيع الحماية. أيضاً عملية صيانة و تحديث المواقع في أطر العمل تكون سهلة مُقارنة ب php native. أخيراً إن كنت تريد التعلم فلا مانع من إستخدام php native لكن في مشاريعك الحقيقية الأفضل إستخدام إطار عمل، أما إن كنت تريد أخذ المُخاطرة فأنت حر في ذلك.
  3. يُمكنك إستخدام الtrait WithPagination الذي يسمح لك بإضافة التصفح لمكون livewire ثم تحدد القالب على أنه bootstrap بهذا الشكل: use Livewire\WithPagination; class AppTasks extends Component { use WithPagination; protected $paginationTheme = 'bootstrap'; protected $listeners = ['taskAdded' => '$refresh']; public function render() { $totalTasks = auth()->user()->tasks()->count(); $tasks = auth()->user()->tasks()->latest()->paginate(5); return view('livewire.app-tasks', [ 'totalTasks' => $totalTasks, 'tasks' => $tasks ]); } } ثم إضافة السطر: {{ $tasks->links() }} لصفحة العرض. و سيظهر ترقيم الصفحات بشكل عادي، يُمكنك أيضاً إستخدام قالب خاص بك للصفحات إن رغبت في ذلك و الطريقة موجودة في التوثيق الرسمي : Pagination
  4. نعم فالفكرة تكمن في إستخدام الأحداث و هناك عدة طرق لإستخدامها من بينها إرسال أو عمل emit للحدث من خلال المُكون الأول و التسمع على الحدث في المُكون الثاني لكن بما أننا نريد عمل تحديث ف livewire توفر طريقة خاصة إسمها $refresh، يُمكن التعامل مع الأحداث بعدة طرق عن طريق القالب أو عن طريق كلاس المُكون أو عن طريق javascript و هذا شرح الطريقة الثانية: إرسال الحدث من المكون الأول: class AppAddTask extends Component { public $title; public function render() { return view('livewire.app-add-task'); } public function addTask() { auth()->user()->tasks()->create([ 'title' => $this->title, 'status' => false, ]); $this->title = ""; $this->emit('taskAdded'); // هنا قمنا بإرسال الحدث } } التسمع على الحدث من خلال المُكون الثاني و عمل refresh: class AppTasks extends Component { protected $listeners = ['taskAdded' => '$refresh']; // التسمع و عمل تحديث public function render() { $totalTasks = auth()->user()->tasks()->count(); $tasks = auth()->user()->tasks()->latest()->get(); return view('livewire.app-tasks', [ 'totalTasks' => $totalTasks, 'tasks' => $tasks ]); } }
  5. هذا غالباً يحدث بسبب فشل ال validation حيث أنك تتحقق من mime type لكن في الإختبار الملف الوهمي لا يحتوي على نفس mime type هنا: <?php $file = UploadedFile::fake()->create( 'video.mp4', $sizeInKilobytes, 'mp4' ); لذلك حاول تغيير هذه الأسطر: <?php $file = UploadedFile::fake()->create( 'video.mp4', $sizeInKilobytes, 'mp4' ); إلى mime type صحيح بهذا الشكل: <?php $file = UploadedFile::fake()->create( 'video.mp4', $sizeInKilobytes, 'video/mp4' );
  6. المُشكلة أن حقول الإدخال لا تكون موجودة عند تحميل الصفحة و إنما يتم إضافتها عن طريق جافاسكربت فيما بعد، لذلك حتى تعمل يجب أن تُغير هذه: $(function () { $('.marker-hight-production, .layers-count').keyup (function() { let x = $(".marker-hight-production").val(), y = $(".layers-count").val(); $('.consumption-m').val(x * y); }); }); إلى هذا الشكل: $(function () { $(document).on('keyup', '.marker-hight-production, .layers-count', function() { let x = $(".marker-hight-production").val(), y = $(".layers-count").val(); $('.consumption-m').val(x * y); }); }); و ستعمل.
  7. نعم يُمكنك عمل ذلك بإستخدام بإستخدام مُعامل الشرط الثلاثي داخل الدالة update كما هو موضح أدناه: <?php Company::find($id)->update([ 'company_name' => $request->company_name, 'former_company' => $request->former_company ] + ($imageName ? [ 'company_logo' => $imageName ] : [])); و هذا يعني أننا نريد إضافة مصفوفة جديدة للمصفوفة الأولى تضم الحقل company_name في حالة ما إذا كان imageName لا يساوي null أما في الحالة المُعاكسة نضيف مصفوفة فارغة. أو يُمكنك إستخدام التالي: <?php Company::find($id)->update($request->only('company_name', 'former_company') + ($imageName ? [ 'company_logo' => $imageName ] : [])); هذا بطريقة مُختصرة يُمكنك أيضاً إنشاء المصفوفة خارج الدالة update. ثم تتفحص إن كان imageName يساوي null و على أساسه تضيف حقل جديد للمصفوفة ثم تمررها للدالة update
  8. يوجد ملفات ناقصة، لا يُمكنني تجربة المشروع بدونها، و لا أدري ماهي المُشكلة أو الخطأ الذي يظهر لك.
  9. هل يُمكنك رفع الملفات التي تعمل عليها حتى أجرب عندي و أخبرك بالحل
  10. يُمكنك عمل ذلك عن طريق التالي: لنفترض أنه لديك الحقول بالشكل التالي: <input type="number" class="marker-hight-production" placeholder="marker-hight-production" value="0"> <input type="number" class="layers-count" placeholder="layers-count" value="0"> <input type="number" class="consumption-m" placeholder="consumption-m"> بعد ذلك يُمكنك إستخدام jQuery للتسمع على حدث keyup أو الحدث الذي تريده في كل من الحقلين و على أساسه تقوم بتغير قيمة الحقل الثالث كما هو موضح أدناه: $(function () { $('.marker-hight-production, .layers-count').on('keyup', function() { let x = $(".marker-hight-production").val(), y = $(".layers-count").val(); $('.consumption-m').val(x * y); }); }); و هذه الpen توضح العملية: إضغط هنا
  11. يُمكنك إستخدام when بالشكل التالي: $getSales = Shoppingcart::when($this->searchTerm, function ($query, $searchTerm) { return $query->where('trans_code', 'LIKE', "%{$searchTerm}%"); }, function ($query) { return $query->with('user'); })->select('trans_code', 'created_at', DB::raw("SUM(sub_total) as total")) ->groupBy('trans_code') ->paginate(10); يُمكنك الإطلاع على توثيق موسوعة حسوب: البنود المشروطة (Conditional Clauses) و ستجد مثال يُشابه لما تريد في هذه الفقرة: يُنفّذ التابع when النطاق المُغلق المحدّد Closure فقط عندما تكون المعاملة الأولى true. في حالة كانت المعاملة الأولى false لن يُنفّذ النطاق المغلق Closure. تستطيع تمرير نطاق مُغلق Closure آخر كثالث معامل للتابع when. سيُنفّذ هذا النطاق المُغلق في حالة كانت المعاملة الأولى false.
  12. هذه المُشكلة تحدث لأن الأمر: php artisan queue:work سيُواصل العمل لحين يتم إنهاؤه يدوياً او إغلاق الطرفية. لذلك laravel تُوفر الأمر: php artisan queue:work --tries=3 يُمكنك تمرير عدد مرات المُحاولة بإستخدام --tries لتجنب الحلقة الغير مُنتهية التي تحدث بسبب خطأ في تنفيذ ال job و لمعرفة الخطأ الذي يُسبب المُشكلة يُمكنك الإطلاع على ملف laravel.log و ستجد سبب المُشكلة أيضاً يُمكنك الإطلاع على جدول failed_jobs في العمود exception لتعرف سبب المُشكلة إذا كنت تستخدم database driver.
  13. إذا كان المطلوب هو حساب مجموع عناصر كل مصفوفة جزئية و عرضه لوحده يُمكنك إستخدام حلقة foreach عادية بهذا الشكل: <?php $matrix = [ [1, 2, 3], [4, 5, 6], [7, 8, 9] ]; foreach ($matrix as $numbers) { $sum = 0; foreach ($numbers as $number) { $sum += $number; } echo "$sum \n"; } او يُمكنك إستخدام الدالة array_sum بهذا الشكل: <?php $matrix = [ [1, 2, 3], [4, 5, 6], [7, 8, 9] ]; foreach ($matrix as $numbers) { $sum = array_sum($numbers); echo "$sum \n"; }
  14. للتخلص من الإستعلامات الإضافية او تخفيض عدد الإستعلامات إستخدم دائماً التحميل الحثيث Eager loading او التحميل المُسبق للعلاقات لأنه عند إستخدامك للعلاقة كخاصية من دون التحميل الحثيث سيتم إجراء إستعلام إضافي في كل مرة و هذه المُشكلة تُسمى ب N + 1 أي من أجل كل كائن تستعلم N مرة إضافية. ففي حالتك مثلاً: في النموذج Comment public function children() { return $this->hasMany(Comment::class, 'parent_id'); } في المُتحكم: public function show($slug) { $article = Article::with(['comments' => function ($query) { $query->with('children')->where('status', true); }])->where('slug', $slug)->firstOrFail(); return view('article.show', compact('article')); } في صفحة العرض تكون بهذا الشكل مثلاً: @foreach($article->comments as $comment) {{ $comment->body }} @foreach($comment->children as $subComment) {{ $subComment->body }} @endforeach @endforeach
  15. مرحباً @آية جمال سالم و عليكم السلام: يمكنكِ الاستفسار عن ذلك عن طريق التواصل مع مركز المساعدة عبر الرابط التالي: مركز المُساعدة تواصلي معهم وسيقومون بتوضيح ما تريدين بكل سهولة وشفافية.
  16. المُشكلة التي ظهرت لك بسبب هذا السطر: let name = fares; لان جافاسكربت لا تعرف الكلمة fares فهي ليست كلمة محجوزة في اللغة، كما أنه لا يوجد في السكربت أي مُتغير بالإسم fares. أعتقد أنك تريد تعريف مُتغير name و إسناد إسمك له كقيمة. لكن لعمل هذا تحتاج أن تُخبر جافاسكرت بأنه نص بإستعمال علامتي التنصيص بهذا الشكل مثلاً: let name = "fares"; و هذا ما تُوضحه رسالة الخطأ: Uncaught ReferenceError: fares is not defined
  17. يُمكنك عمل هذا الأمر بإستخدام أحد هذه الطرق: تمرير قيمة ال slug أثناء إنشاء السجل في المُتحكم: <?php use Illuminate\Support\Str; Post::create([ 'title' => request('title'), 'body' => request('body'), 'slug' => Str::slug(request('title')) ]); إعادة تعريف الدالة boot في المُتحكم Post و التسمع على الحدث creating: <?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Str; class Post extends Model { use HasFactory; protected $guarded = []; protected static function boot() { parent::boot(); static::creating(function ($post) { $post->slug = Str::slug($post->title); }); } } يُمكن التسمع على عدة أحداث: creating، created ، deleting، deleted، updating، updated ... لكن إن كانت الأحداث التي تُريد التسمع عليها كثيرة و ترغب بعمل شيء ما عند كل حدث فسيُصبح النموذج مُكتض بالشيفرات و ستصعب عليك عملية ال refactoring ففي هذه الحالة الأفضل إستخدام مُراقب (observer) لهذا النموذج. إستخدام مراقب (observer): أولاً إنشاء المُراقب: php artisan make:observer PostObserver --model=Post سيُنشئ الأمر ملف جديد في المسار: app/Observers و من خلاله يُمكنك مراقبة النموذج الخاص بك. ثانياً في حالتنا سنُضيف الدالة: creating : <?php namespace App\Observers; use App\Models\Post; use Illuminate\Support\Str; class PostObserver { public function creating(Post $post) { $post->slug = Str::slug($post->title); } // others methods } ثالثاً نقوم بربط المُراقب بالنموذج الذي سيُراقبه و يتم ذلك على مُستوى مُزود الخدمة EventServiceProvider في الدالة boot: <?php use App\Models\Post; use App\Observers\PostObserver; public function boot() { Post::observe(PostObserver::class); }
  18. يُمكن أيضاً إستخدام حزمة Laravel Localization التي تُوفر العديد من الميزات فيما يخص موضوع localization تثبيت الحزمة: composer require mcamara/laravel-localization إستدعاء ملف إعدادات الحزمة ضمن ملفات المشروع: php artisan vendor:publish --provider="Mcamara\LaravelLocalization\LaravelLocalizationServiceProvider" سيتم إنشاء ملف config/laravellocalization.php الذي من خلاله يُمكنك تحديد اللغات التي سيدعمها المشروع بالإضافة إلى بعض الإعدادات الأخرى. إضافة ال middlewares الخاصة بالحزمة ضمن ملف app/Http/Kernel.php: <?php namespace App\Http; use Illuminate\Foundation\Http\Kernel as HttpKernel; class Kernel extends HttpKernel { /** * The application's route middleware. * * @var array */ protected $routeMiddleware = [ /**** OTHER MIDDLEWARE ****/ 'localize' => \Mcamara\LaravelLocalization\Middleware\LaravelLocalizationRoutes::class, 'localizationRedirect' => \Mcamara\LaravelLocalization\Middleware\LaravelLocalizationRedirectFilter::class, 'localeSessionRedirect' => \Mcamara\LaravelLocalization\Middleware\LocaleSessionRedirect::class, 'localeCookieRedirect' => \Mcamara\LaravelLocalization\Middleware\LocaleCookieRedirect::class, 'localeViewPath' => \Mcamara\LaravelLocalization\Middleware\LaravelLocalizationViewPath::class ]; } بعد ذلك يُمكنك إستخدام الحزمة: في المسارات: // routes/web.php Route::group(['prefix' => LaravelLocalization::setLocale()], function() { /** المسارات التي تعتمد على عدة لغات **/ }); /** المسارات الأخرى **/ أما في العرض: <ul> @foreach(LaravelLocalization::getSupportedLocales() as $localeCode => $properties) <li> <a rel="alternate" hreflang="{{ $localeCode }}" href="{{ LaravelLocalization::getLocalizedURL($localeCode, null, [], true) }}"> {{ $properties['native'] }} </a> </li> @endforeach </ul> لمعلومات أكثر يُمكنك تصفح توثيق الحزمة.
  19. يُمكنك ذلك من خلال إنشاء ملف php عادي في أي مسار تريده مثلاً في: tests |_ Utilities |_|___ functions.php و تضع الدوال التي تحتاجها فيه مثلاً: <?php function create($class, $attributes = []) { return $class::factory()->create($attributes); } function make($class, $attributes = []) { return $class::factory()->make($attributes); } الآن ستقوم بإضافة عملية التحميل التلقائي للملف من خلال composer و لعمل ذلك تحتاج التعديل على ملف composer.json: إن كنت تحتاج أن تتم عملية التحميل التلقائي للملف فقط في مرحلة التطوير ستُضيف مسار الملف داخل مصفوفة files ضمن الخاصية autoload-dev: "autoload-dev": { "psr-4": { "Tests\\": "tests/" }, "files": ["tests/Utilities/functions.php"] }, أما إن أردت أن تشمل العملية مرحلة الإنتاج فستضع مسار الملف داخل مصفوفة files في الخاصية autoload: "autoload": { "psr-4": { "App\\": "app/", "Database\\Factories\\": "database/factories/", "Database\\Seeders\\": "database/seeders/" }, "files": ["tests/Utilities/functions.php"] }, بعد ذلك قم بتنفيذ أمر: composer dump-autoload ثم ستتمكن من إستخدام الدوال التي أنشأتها في أي مكان تريد، في المثال أرفقت دالتين إحداهما لإنشاء سجل جديد من نموذج معين و الثانية تُنشئ السجل و تقوم بتخزينه في قاعدة البيانات، و هاتين الدالتين تحتاج لهما كثيرا في الإختبارات: و هذا مثال عن إستخدامها: <?php /** @test */ function an_authenticated_user_may_participate_in_forum_threads() { $this->signIn(); $reply = make(Reply::class); $this->post($this->thread->path . "/replies", $reply->toArray()); $this->get($this->thread->path) ->assertSee($reply->body); }
  20. تحدث هذه المُشكلة لك بسبب أنك حددت إسم المُجلد في المُعامل الأول للدالة put، مثلاً إن كنت تريد وضع الملفات داخل مُجلد images يُمكنك ذلك من خلال: public function test(Request $request) { $fileInForm = 'file'; if ($request->hasFile($fileInForm)) { $file = $request->file($fileInForm); if ($file->isValid()) { // Filename is hashed filename + part of timestamp $hashedName = hash_file('md5', $file->path()); $timestamp = microtime(); $newFilename = $hashedName . $timestamp . '.' . $file->getClientOriginalExtension(); Storage::disk('local')->putFile('images', $file); } } } في هذه الحالة سيتم حفظ الملفات داخل مُجلد images و ستُعطي لارافيل إسم مُختلف للملف المرفوع في كل مرة تلقائياً، و ستُرجع الدالة putFile المسار الذي تم حفظ الملف فيه يُمكنك حفظه إن احتجت له. إن كنت ترغب في وضع الإسم الذي يُحفظ به الملف بنفسك يُمكنك إستخدام الدالة putFileAs بنفس الطريقة و تُمرر الإسم الذي ترغب فيه كمعامل ثالث: public function test(Request $request) { $fileInForm = 'file'; if ($request->hasFile($fileInForm)) { $file = $request->file($fileInForm); if ($file->isValid()) { // Filename is hashed filename + part of timestamp $hashedName = hash_file('md5', $file->path()); $timestamp = microtime(); $newFilename = $hashedName . $timestamp . '.' . $file->getClientOriginalExtension(); Storage::disk('local')->putFileAs('images', $file, $newFilename); } } } تُرجع الدالة أيضاً مسار الملف. تُتيح لارافيل أيضاً دالتي store و storeAs لكن هاتين الدالتين لا تُستخدمان بإستعمال الfacade بل مُباشرة على الملف بهذا الشكل مثلاً: $path = $request->file('file')->store('images'); $path = $request->file('file')->storeAs( 'images', $newName );
  21. مرحباً @Mohamed Elgaml لا أدري ما المُشكلة التي تواجهها في فعل المطلوب لأن الأمر بسيط للغاية: بما أن الشريحة التي تحتوي على القيمة 3500 يتم إدخالها من طرفك فلا يوجد أي تعديل او دوال فيها لنفترض أنك وضعتها في الخانة H4 بمعنى العمود H و الصف الرابع. بالتالي H5 التي ستعرض بها الدخل السنوي ستكون تساوي H4 * 12 و هي الشريحة الثانية لديك. أما الشريحة الثالثة ستكون في الخانة H6 مثلاً و ستحتوي على H5 - 24000 بما أن القيمة 24000 قيمة ثابتة. الشريحة الرابعة ستكون قيمتها ثابتة في هذه الحالة لأن كل من القيمتين 15000 و 2,5% ثابتتين و ستحتوي القيمة 375 في كل الأحوال. ستكون القيمة ضمن الخانة H7 الشريحة الخامسة ستكون قيمتها متغيرة حسب قيمة H6 و سنضعها في الخانة H8 و ستحتوي على: (H6 - 15000) * 10% بخصوص الشريحة الأخيرة أنت تقول أنها تحتوي المبلغ النهائي لكن لا أدري ماذا تقصد بذلك لكن إذا فهمت الأمر فهو بسيط يُمكنك القيام بكل سهولة. هذا ملف يحتوي على كافة الشرائح ما عدا الشريحة الأخيرة لأني لا أعلم ماذا سيكون محتواها: Simple.xlsx
  22. إطار العمل لارافيل مُجهز بشكل جيد للقيام بالإختبارات بشتى أنواعها يُمكنك إستخدام قاعدة بيانات ثانية للقيام بالإختبارات عليها لكن أي مشروع تقوم بإنشائه ستجده مُجهز مُسبقاً للقيام بالإختبارات على قاعدة بيانات من نوع sqlite في الذاكرة أي لست بحاجة لإنشاء قاعدة بيانات بالأساس فكما تعلم أن التعامل مع الذاكرة يكون أسرع من التعامل مع ملف في القرص الصلب لذلك تحتاج فقط للذهاب إلى ملف phpunit.xml حيث ستجد السطرين التاليين: <!-- <server name="DB_CONNECTION" value="sqlite"/> --> <!-- <server name="DB_DATABASE" value=":memory:"/> --> مٌعلقين فقط قم بإزالة التعليق: <server name="DB_CONNECTION" value="sqlite"/> <server name="DB_DATABASE" value=":memory:"/> لكن قبل أن تُفكر في قاعدة البيانات يجب عليك كتابة الخطوات التي تريد أن تسير عليها و تُنفذ أمر phpunit و كل مرة يظهر لك خطأ تُحاول تصليحه إلى أن تصل إلى نجاح الإختبار مثلا لنكتب إختبار خاصية إضافة كتاب: class BookTest extends TestCase { /** @test */ public function a_book_can_be_added_to_the_library() { } } ماهي خطوات إضافة كتاب؟ أليست كالتالي إرسال بيانات الكتاب إلى مسار مُعين ثم التأكد من أن الكتاب أضيف لقاعدة البيانات إذا لنكتب هذه الخطوات داخل التابع الذي أنشأناه: <?php $this->post('/books', [ 'title' => 'Book one', 'author' => 'Kamel', ]); $this->assertCount(1, Book::all()); إذا قمنا بذلك ثم نفذنا أمر phpunit سيفشل الإختبار بالطبع لكنه سيفشل في الخطوة الثانية و نحن ننتظر منه أن يفشل في الخطوة الأولى، لماذا؟ لان لارافيل إفتراضياً تقوم بمُعالجة الإستثناءات التي تُرمى لذلك يجب أن نُخبرها بأن لا تُعالج الإستثناء و بأن تقوم بعرضه و لحسن الحظ هي توفر دالة للقيام بذلك إسمها withoutExceptionHandling و دالة أخرى إسمها withExceptionHandling و هي ما يتم تنفيذه لذلك لابد من تعديلها حتى نرى فشل الإختبار: class BookTest extends TestCase { /** @test */ public function a_book_can_be_added_to_the_library() { $this->withoutExceptionHandling(); $this->post('/books', [ 'title' => 'Book one', 'author' => 'Kamel', ]); $this->assertCount(1, Book::all()); } } الآن لو قمنا بتنفيذ أمر phpunit سيفشل و سيُعطينا السبب: NotFoundHttpException: POST http://localhost/books هذا الإستثناء يعني أن المسار الذي نُحاول الوصول له غير موجود و بالتالي نفتح ملف web.php ثم نضيف المسار: Route::post('/books', [BookController::class, 'store']); بعد أن أنشأنا المسار نُعيد تجربة الإختبار و في هذه المرة سيظهر الخطأ التالي: BindingResolutionException: Target class [BookController] does not exist. ما يعني أن المُتحكم الذي أنشأناه غير موجود. فلنقم بإنشائه: php artisan make:controller BookController ثم نقوم بإستدعائه داخل ملف web.php: <?php use App\Http\Controllers\BookController; بعد ذلك نُعيد تجربة الإختبار عن طريق الأمر phpunit و في هذه الحالة سيفشل الإختبار بالسبب التالي: BadMethodCallException: Method App\Http\Controllers\BookController::store does not exist. يعني أن التابع store غير موجود في المُتحكم BookController دعنا نضيفه: <?php namespace App\Http\Controllers; use Illuminate\Http\Request; class BookController extends Controller { public function store() { } } مرة أخرى نُجرب الأمر phpunit و سيفشل الإختبار لكن في هذه المرة سيفشل في الخطوة الثانية و سيُعطي السبب التالي: Class 'Tests\Feature\Book' not found أي أن الصنف Book غير موجود دعنا نقوم بإضافة النموذج و ملف التهجير الخاص به و نستدعيه في كلاس الإختبار فوق: php artisan make:model Book -m وفي الكلاس BookTest نقوم بإستدعائه: use App\Models\Book; مرة أخرى نُجرب الأمر phpunit و بطبيعة الحال سيفشل أيضا و سيُعطي السبب التالي: QueryException: SQLSTATE[HY000]: General error: 1 no such table: books (SQL: select * from "books") هذا معناه أنه يُحاول جلب كل الكتب لكن نحن لغاية الآن لم نقم بتهجير قاعدة البيانات و نرجع لأول ما أخبرتك به أن إستعمال قاعدة بيانات ثانية من أجل الإختبارات لذلك نقوم بإلغاء التعليق من السطرين في ملف phpunit.xml، ثم نضيف ال trait الذي إسمه RefreshDatabase داخل صنف الإختبار ستجد أنه قد تم إستدعاؤه لكنه غير مستخدم دعنا نستخدمه: use Illuminate\Foundation\Testing\RefreshDatabase; class BookTest extends TestCase { use RefreshDatabase; } و هذا ال trait الهدف منه هو تنفيذ أمر التهجير عند تنفيذ الإختبار و عند إنتهاء الإختبار يقوم بعمل reset. بعد ذلك نقوم مرة أخرى بتنفيذ الأمر phpunit و بطبيعة الحال سيفشل أيضا و سيُعطي السبب التالي: Failed asserting that actual size 0 matches expected size 1. هذا الخطأ يعني أنه لم يتم إضافة الكتاب فهو جلب كل الكتب لكن وجد أن عددها 0 لكن نحن توقعنا 1 و هذا لأن الدالة store لا تقوم بشيء لغاية الآن، دعنا نضيف أكواد إضافة كتاب: use App\Models\Book; class BookController extends Controller { public function store() { Book::create([ 'title' => request('title'), 'author' => request('author') ]); } } بعد ذلك نقوم مرة أخرى بتنفيذ الأمر phpunit و بطبيعة الحال سيفشل لكن هذه المرة سيُعطي الخطأ التالي: MassAssignmentException: Add [title] to fillable property to allow mass assignment و هذا لأن لارافيل تُحاول حمايتك يُمكننا في هذه الحالة إخبار لارافيل أننا سنقوم بحماية أنفسنا و لا داعي أن تتدخل في هذا الأمر بعمل set للخاصية guarded في النموذج Book و جعلها فارغة: class Book extends Model { use HasFactory; protected $guarded = []; } بعد ذلك نقوم مرة أخرى بتنفيذ الأمر phpunit و بطبيعة الحال سيفشل أيضاً بسبب أن ملف التهجير الخاص بالكتب لا يوجد به الأعمدة title و author: QueryException: SQLSTATE[HY000]: General error: 1 table books has no column named title نقوم بإضافتها في ملف التهجير: $table->string('title'); $table->string('author'); ثم نعيد تجربة الإختبار عن طريق الأمر phpunit و أخيراً هذه المرة سينجح الإختبار. بسبب أن خاصية إضافة كتاب تم إضافتها بنجاح. و هكذا تقوم بإتباع نفس الآلية في بقية الإختبارات، نحن لم نختبر ال validation و لم نختبر إعادة توجيه المُستخدم مثلاً ، يُمكنك إختبار الخصائص بنفس الطريقة
  23. البرمجة الموجهة بالإختبار هي البرمجة عن طريق البدء بكتابة إختبارات لكل وظيفة بالتطبيق، وكل إختبار يجب أن يقوم بتحديد وشرح الكيفية التي سيتم التعامل بها مع الكود وما هو الناتج الذي من المفترض أن يقوم بإرجاعه في حال تم تنفيذ كل شيء بنجاح، وتقوم هذه الاختبارات بإرجاع إما حالة نجاح والتي تعني أن الكود يعمل بالطريقة الصحيحة التي يجب أن يقوم بها، أو حالة فشل في حال أن الكود به أخطاء وأنه لا يقوم بإرجاع الناتج الصحيح الذي يجب أن يقوم بإرجاعه لحالة الاختبار تلك. لذلك إن كنت تريد إنشاء تطبيق أو مشروع يعمل بشكل جيد و مُختبر من عدة نواحي، و تنتقل بمستواك في تطوير البرمجيات إلى مُستوى أعلى فعليك النظر في هذا الموضوع. هذه بعض الفوائد: تحدد لك الطريق للبدء بكتابة الكود فعوض أن تكون متردد في كتابة خصائص التطبيق وقلق بشأن إذا ما كان الكود الذي تكتبه سيعمل بشكل صحيح فالإختبارات ستقوم بقيادتك تقليل الأخطاء في المشروع يعطيك الثقة بالنفس تسهيل عملية إعادة بناء التعليمات البرمجية أو ما يُسمى بال refactoring دون القلق على عمل الخصائص إضافة خصائص و العمل على طلبات العميل بشكل سليم و بدون أية قلق هناك بعض القواعد التي يجب أن تنتبه لها عند إنشائك لأصناف الإختبار في laravel ف phpunit تبحث عن الملفات التي تنتهي ب Test.php لذلك على كل من الملف و الكلاس أن ينتهيان ب Test. و عند إنشائك لملف Test جديد عن طريق أمر Artisan عليك أن تُحدد ذلك: php artisan make:test JustExampleTest أيضاً أسماء التوابع يجب أن تبدأ بالكلمة test كما يلي: public function test_method_to_assert_something() { $result = 2 * 5; $this->assertEquals(50, $result); } الآن إذا قمت بتنفيذ أمر phpunit سيفشل الإختبار و هو ما نتوقعه ان يحدث. وسيُعطيك النتيجة التالية: Failed asserting that 10 matches expected 50. إذا أردنا تسمية التوابع بأسماء أخرى غير التي تبدأ ب test يُمكننا ذلك لكن يجب أن نُحدد ال annotation لكل تابع على أنه test بهذا الشكل مثلا: class JustExampleTest extends TestCase { /** @test */ public function it_just_a_simple_test() { $result = 2 * 5; $this->assertEquals(50, $result); } } بعض هذه الإعدادات يُمكن تعديلها من خلال ملف phpunit.xml لكن لا داعي لذلك، فقط إحترم تسمية الملفات و ضع ال annotation.
  24. يُمكنك حذف جميع ملفات مُجلد بإستخدام التابع cleanDirectory على الواجهة: File بهذا الشكل: File::cleanDirectory("storage/app/public/products"); و سيُعطيك true في حالة تم الحذف أما إن كان المسار غير موجود سيُعطيك false. او بإمكانك إستخدام التالي أيضاً: use Illuminate\Filesystem\Filesystem; $fs = new Filesystem; $fs->cleanDirectory("storage/app/public/products"); أما إن أردت حذف مُجلد بكافة الملفات التي بداخله يُمكنك إستخدام التابع deleteDirectory بهذا الشكل: File::deleteDirectory("storage/app/public/products"); الفرق بين الطريقتين ان الأولى تحذف الملفات التي بداخل المُجلد فقط و تُبقي على المُجلد نفسه أما الثانية فتحذف المُجلد نفسه و ما بداخله. أما الطريقة التي تستخدمها فهي لحذف ملف واحد فقط بتمرير مساره للتابع delete كما يمكنك إستخدام نفس التابع لحذف مجموعة من الملفات بتمرير مسارات هذه الملفات في مصفوفة و بالتالي يُمكن إستخدام التابع allFiles لجلب الملفات الموجودة في مجلد ما على شكل مصفوفة و نُمررها للتابع delete بالشكل التالي: $files = Storage::allFiles("public/products"); Storage::delete($files); لاحظ أننا لم نمرر المسار بداية من app لأنه توابع الصنف Storage تنظر مباشرة داخل المجلد app و بالتالي إن قمت بتمريره في مسار الملف ستفترض أن هناك مجلد app داخل مُجلد app هكذا: storage/app/app
  25. ليس هناك فترة محددة فبمجرد إشتراكك بأحد الدورات تستطيع الوصول إلى محتوياتها مدى الحياة اي أنك لن تكون مقيد بوقت أو تاريخ محدد لإنهائها. لذلك فقضية إكمال الدورة راجعة لك كشخص و طبيعة دراستك و فهمك. أهم شيء أن تركز على فهم الدروس بشكل جيد و لا تقيد نفسك بوقت أو تاريخ محدد
×
×
  • أضف...