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

أساسيات إدارة الصور في Laravel 5 - الجزء الثاني


هشام رزق الله

في الحالة العادية، سوف تقوم بكتابة تابع (store method) أولا، ومن ثم تترك عملية التحقق في النهاية حتى يعمل store بشكل جيد، لكن في حالتنا هذه سيكون الأمر معقدا قليلا بما أننا نتعامل مع ملفات منفصلة يجب حفظها، ففي حالة الصور، سوف نحتاج إلى التحقق أولا.

laravel5-image-management.thumb.png.5c3f

صنف Request

سوف نبدأ بإنشاء صنف (request class) والذي سيقوم بالتعامل مع عملية التحقق من استمارة الإنشاء. سوف نبدأ بكتابة هذا الأمر على سطر الأوامر:

php artisan make:request CreateImageRequest

هذا الأمر سيقوم بإنشاء الملف ووضعه داخل مجلد app/Http/Requests.

بعد ذلك، عدل الملف كالتالي:

<?php
  namespace App\Http\Requests;

  use App\Http\Requests\Request;
  use App\Marketingimage;

  class CreateImageRequest extends Request
  {
    /**
    * Determine if the user is authorized to make this request.
    *
    * @return bool
    */
    public function authorize()
    {
      return true;
    }

    /**
    * Get the validation rules that apply to the request.
    *
    * @return array
    */
    public function rules()
    {
       return [
         'image_name' => 'alpha_num | required | unique:marketing_images',
         'mobile_image_name' => 'alpha_num | required | unique:marketing_images',
         'is_active' => 'boolean',
         'is_featured' => 'boolean',
         'image' => 'required | mimes:jpeg,jpg,bmp,png | max:1000',
         'mobile_image' => 'required | mimes:jpeg,jpg,bmp,png | max:1000'
       ];
    }
}

واحد من الأشياء التي أحبها في إطار Laravel هو كيفية تعامله مع عملية التحقق، فإذا كانت هذه المرة الأولى التي ستتعامل فيها مع عملية التحقق، سوف تجد أن هذه العملية بسيطة للغاية.

لاحظ أننا قمنا بكتابة السطر التالي في تابع authorize:

return true;

هذا السطر سوف يسمح للطلب أن يمر، فإذا وضعنا شرطا عليه فسنستطيع التحكم فيه، فعلى سبيل المثال، يمكننا استخدامه في تسجيل الدخول للأعضاء فقط.

بعد ذلك لدينا قواعد التابع، الذي سوف يتم فرضها على عملية التحقق في حقول الاستمارة، لن نتعمق أكثر هنا، فإذا أردت المزيد يمكنك الإطلاع على توثيق Laravel.

للتذكير فإننا نفصل ما بين قواعد التّحقق بمحرف "|" ولما نضيف قاعدة "unique" فإننا نُرفقها باسم الجدول الذي يجب أن نبحث فيه (يعني الجدول الذي نرغب في أن يكون السّجل record الذي نقوم بحفظه وحيدًا "unique").

ومن الطبيعي أننا أننا قمنا بإضافة قيد على حجم الملفات. يمكنك إيجاد قائمة كاملة من قواعد التحقق في الوثائق.

تابع Store

على أي حال، سوف ننتقل الآن لتابع store في MarketingImageController، عدل التابع كما يلي:

public function store(CreateImageRequest $request)
{
   //create new instance of model to save from form
   $marketingImage = new Marketingimage([
       'image_name'        => $request->get('image_name'),
       'image_extension'   => $request->file('image')->getClientOriginalExtension(),
       'mobile_image_name' => $request->get('mobile_image_name'),
       'mobile_extension'  => $request->file('mobile_image')->getClientOriginalExtension(),
       'is_active'         => $request->get('is_active'),
       'is_featured'       => $request->get('is_featured'),

   ]);

   //define the image paths
   $destinationFolder = '/imgs/marketing/';
   $destinationThumbnail = '/imgs/marketing/thumbnails/';
   $destinationMobile = '/imgs/marketing/mobile/';

   //assign the image paths to new model, so we can save them to DB
   $marketingImage->image_path = $destinationFolder;
   $marketingImage->mobile_image_path = $destinationMobile;

   // format checkbox values and save model
   $this->formatCheckboxValue($marketingImage);
   $marketingImage->save();

   //parts of the image we will need
   $file = Input::file('image');

   $imageName = $marketingImage->image_name;
   $extension = $request->file('image')->getClientOriginalExtension();

   //create instance of image from temp upload
   $image = Image::make($file->getRealPath());

   //save image with thumbnail
   $image->save(public_path() . $destinationFolder . $imageName . '.' . $extension)
       ->resize(60, 60)
       // ->greyscale()
       ->save(public_path() . $destinationThumbnail . 'thumb-' . $imageName . '.' . $extension);

   // now for mobile
   $mobileFile = Input::file('mobile_image');

   $mobileImageName = $marketingImage->mobile_image_name;
   $mobileExtension = $request->file('mobile_image')->getClientOriginalExtension();

   //create instance of image from temp upload
   $mobileImage = Image::make($mobileFile->getRealPath());
   $mobileImage->save(public_path() . $destinationMobile . $mobileImageName . '.' . $mobileExtension);


   // Process the uploaded image, add $model->attribute and folder name
   flash()->success('Marketing Image Created!');

   return redirect()->route('marketingimage.show', [$marketingImage]);
}

حسنا، دعونا نبدأ بفهم هذه الشفرة الطويلة، سنبدأ مع توقيع الدالة:

public function store(CreateImageRequest $request)

هذا الجزء واضح، فلقد قمنا فقط بسحب نسخة (instance) من كائن request يحمل الاسم request$ وهذا سوف يساعدنا على إنشاء نسخة جديدة للنموذج:

//create new instance of model to save from form

$marketingImage = new Marketingimage([
   'image_name'        => $request->get('image_name'),
   'image_extension'   => $request->file('image')->getClientOriginalExtension(),
   'mobile_image_name' => $request->get('mobile_image_name'),
   'mobile_extension'  => $request->file('mobile_image')->getClientOriginalExtension(),
   'is_active'         => $request->get('is_active'),
   'is_featured'       => $request->get('is_featured'),

]);

لاحظ أنني أقوم بالتعليق كثيرا على هذا التابع (method)، لأنه من السهل أن يربكك. على أي حال، قمنا بإنشاء مثيل جديد لـ Marketingimage ووضعنا خصائص النموذج باستخدام مثيل request$.

أما البقية الشيفرة، فلقد قُمنا بسحب البيانات من الاستمارة إذا نجحت في عملية التحقق. ثم سوف نحتاج إلى تعريف مسارات الصور:

//define the image paths

$destinationFolder = '/imgs/marketing/';
$destinationThumbnail = '/imgs/marketing/thumbnails/';
$destinationMobile = '/imgs/marketing/mobile/';

ملاحظة: حاولت أن أجعل هذه الأسماء بديهية قدر الإمكان.
بعد ذلك، سوف نقوم بتعيينها إلى النموذج حتى نتمكن من حفظهم في قاعدة البيانات.

//assign the image paths to new model, so we can save them to DB

$marketingImage->image_path = $destinationFolder;
$marketingImage->mobile_image_path = $destinationMobile;

نستطيع فعل هذا بطريقة أخرى، فيمكننا أن نقوم بتقديم معلومات المسار في الاستمارة أو يمكننا تخطي هذا كليا، لكن وجدت أن إبقاء المسار في قاعدة البيانات يجعل العمل مع الصور أسهل عندما نحتاج إلى استخدامها في العروض (views)، وهذا هو سبب استخدامنا هذه الطريقة.
ثم سنقوم بتهيئة (format) قيم خانة اختيار ومن ثم نقوم بحفظها:

// format checkbox values and save model

$this->formatCheckboxValue($marketingImage);
$marketingImage->save();

يمكنك أن ترى أنني أقوم بتسليم مثيل النموذج marketingImage$ إلى تابع formatCheckboxValue، وهو تابع قمت بعمله للتأكد من أن خانة الاختيار يتم التعامل معها بشكل جيد:

public function formatCheckboxValue($marketingImage)
{
   $marketingImage->is_active = ($marketingImage->is_active == null) ? 0 : 1;
   $marketingImage->is_featured = ($marketingImage->is_featured == null) ? 0 : 1;
}

بسبب نوع البيانات في قاعدة البيانات، يمكننا توفير فقط رقمين 0 و 1، لذلك نحتاج إلى التأكد من أننا نقوم بالتحويل بشكل صحيح من خانة الاختيار (checkbox) في الاستمارة (form). لقد قمنا بتسليمها إلى مثيل النموذج ومن ثم سيقوم بوضع الخصائص وفقا لذلك.
بعد ذلك نعود إلى تابع store، ونقوم ببساطة بالحفظ:

$marketingImage->save();

وهذا سوف يهتم بسجلات قاعدة البيانات، ومع ذلك، لانزال بحاجة إلى التعامل مع الملف. في البداية سوف نعمل على بعض أجزاء الصورة التي سنحتاجها:

//parts of the image we will need

$file = Input::file('image');

$imageName = $marketingImage->image_name;
$extension = $request->file('image')->getClientOriginalExtension();

بدلا من ذلك، يمكنك جعل بعض هذه الشفرات في سطر واحد (inline) لكنك ستحصل على سطور طويلة جدا بالإضافة إلى أن إبقائها هكذا أسهل للتتبع والفهم.
الآن سوف نحتاج إلى استخدام مكتبة الصور (Image library) لمساعدتنا على الخروج من هنا، سنقوم بإنشاء مثيل لهذه الصورة من الصور المرفوعة:

//create instance of image from temp upload
$image = Image::make($file->getRealPath());

بعد ذلك، سنقوم بحفظها وإنشاء صورة مصغرة والتي سنقوم بحفظها عن طريق سَلسَلة التوابع (chaining the methods):

//save image with thumbnail
$image->save(public_path() . $destinationFolder . $imageName . '.' . $extension)
   ->resize(60, 60)
   // ->greyscale()
   ->save(public_path() . $destinationThumbnail . 'thumb-' . $imageName . '.' . $extension);

سوف نقوم بتقسيم هذه الشيفرة إلى أجزاء حتى نفهمها بشكل أفضل، أول جزء يقوم بحفظ الصور الأولية:

$image->save(public_path() . $destinationFolder . $imageName . '.' . $extension)

استخدمنا ()public_path للوصول إلى المكان الذي يمكننا تعريف فيه مجلد الهدف (مجلد الصور) والذي هو imgs/marekting/.

بعد ذلك قمنا بسَلسَلة أجزاء الصورة (مسار ملف الصورة) التي نحتاجها وهذا سوف يعطينا كل شيء نحتاجه وسيسهل علينا عملية متابعة الشيفرة البرمجية.

بما أننا نحتاج أيضا إلى إنشاء صورة مصغرة، قمنا بسَلسَلة ذلك التابع:

->resize(60, 60)

وهذا السطر سوف ينشئ لنا نسخة بحجم 60 × 60 من الصورة، وإذا أردت تغيير حجم النسخة يمكنك فعل هذا بسهولة بتغيير الأرقام، ويمكنك أيضا إنشاء حقل في الاستمارة للمستخدمين لتحديد ارتفاع و عرض الصور المصغر. (لن نقوم بهذا في هذا الدرس.)

قمتُ بإرفاق (على شكل تعليق) تابع متسَلسَل لجعل الصورة المصغرة ذات تدرج رمادي، فإذا أردت صور مصغرة بيضاء وسوداء اجعل السطر شيفرة برمجية (عن طريق إزالة //).

والآن سنقوم بحفظ الصورة المصغرة:

->save(public_path() . $destinationThumbnail . 'thumb-' . $imageName . '.' . $extension);

وسنقوم بنفس الشيء لصور الهاتف:

// create instance of image from temp upload
$mobileImage = Image::make($mobileFile->getRealPath());
$mobileImage->save(public_path() . $destinationMobile . $mobileImageName . '.' . $mobileExtension);

يما أنها مثل الصور الأولية لكن بدون صورة مصغرة، سوف نتركها هكذا.

حسنا، بعد ذلك لدينا رسالة الفلاش (flash message):

flash()->success('Marketing Image Created!');

هذه الشيفرة لن تعمل إلا إذا قمت بتثبيت حزمة الفلاش لجفري ويِ (Jefferey Wey) وقمت باستدعائها في عرض (view) الصفحة الرئيسية، وإذا لم تقم بتثبيت هذه الحزمة فلا تكتب هذه الشيفرة.

في النهاية، بعد حفظ كُل من النموذج وملفات الصور، سوف نقوم بإعادة التوجيه نحو صفحة العرض:

return redirect()->route('marketingimage.show', [$marketingImage]);

الآن يمكنك تجربة رفع وحفظ الصور وعمل صورة مصغرة وصورة للهاتف عن طريق زيارة الاستمارة في العنوان التالي:

yourdomain.com/marketingimage/create

يمكنك التأكد من إنشاء الصور من خلال النظر إلى المجلدات حيث يُفترض أن تكون الصور.

سوف ترى أن تابع store يقوم بالكثير من الأشياء، لكن ينقصه آلية للتحقق من الأخطاء، يمكنك فعل هذا لجعل التطبيق أكثر قوة على الرغم من أنني أعتقد أن أمر ليس ضروري.

بما أن Laravel يقوم بوظيفة جيدة عن طريق فصل استمارة التحقق من تابع store، لن يتوقف التابع إذا فشلت عملية التحقق.
حسنا ماذا يمكن أن يفشل أيضا ؟ حسنا ربما تفشل عملية حفظ الملف وهذا يمكن أن يكون بسبب امتلاء النظام، أو بسبب مشاكل في الصلاحيات أو خطأ في كتابة المسارات وأسماء المجلدات.

هذه الأخطاء صعبة الاكتشاف والتصحيح بسبب أنه ربما قد لا تحصل على رسائل الخطأ المناسبة، وعلى أية حال، هذه هي أشهر الأخطاء إذا أردت اكتشافها وتصحيحها.

يمكنك أن ترى أنه على الرغم من أنه لا شيء صعب في إدارة الصورة إلا أن الصعوبة موجودة بشكل ما.

الخطوة المنطقية التالية هي إنشاء تابع show في المتحكم وإنشاء العرض الخاص به.

تابع show

حسنا، هذه هي شيفرة تابع العرض:

public function show($id)
{
   $marketingImage = Marketingimage::findOrFail($id);
   return view('marketingimage.show', compact('marketingImage'));
}

هذه الشيفرة واضحة للغاية، يقوم Laravel باسترجاع مثيل النموذج ثم يقوم بإرجاعه إلى العرض view، لاحظ استخدام findOrFail والتي تقوم بإرجاع ModelNotFoundException، والتي يمكنك التعامل معها من خلال ملف Handler.php في app/Exceptions.

عرض show

حسنا، بعدما انتهينا من التابع ننتقل إلى العرض view:

@extends('layouts.master')

@section('content')
  {!! Breadcrumb::withLinks(['Home'   => '/',
      'marketing images' => '/marketingimage',
      "show $marketingImage->image_name.$marketingImage->image_extension"
   ]) !!}

  <div>
    {{ $marketingImage->image_name }} :  <br>
    <img src="/imgs/marketing/{{ $marketingImage->image_name . '.' .
         $marketingImage->image_extension . '?'. 'time='. time() }}">
  </div>

  <div>
     {{ $marketingImage->image_name }} - thumbnail :  <br>
     <img src="/imgs/marketing/thumbnails/{{ 'thumb-' . $marketingImage->image_name . '.' .
$marketingImage->image_extension . '?'. 'time='. time() }}">
  </div>

  <div>
    {{ $marketingImage->mobile_image_name }} - mobile :  <br>
    <img src="/imgs/marketing/mobile/{{ $marketingImage->mobile_image_name . '.' .
$marketingImage->mobile_extension . '?'. 'time='. time() }}">
  </div>
@endsection

ملاحظة: يمكنك إزالة مساعدات Breadcrumb إذا لم تكن تستخدم تلك الحزمة.

لم نجعل الواجهة الأمامية فاخرة هنا، قمنا فقط بعمل صفحة بسيطة لإظهار الصور. وبما أننا أرسلنا مثيل marketingImage$ إلى المتحكم، فإننا استخدمنا تركيبة blade لاستدعاء اسم الصورة:

{{ $marketingImage->image_name }}

ثم نقوم بإستدعاء الصور الأولية:

 <img src="/imgs/marketing/{{ $marketingImage->image_name . '.' . $marketingImage->image_extension .
'?'. 'time='. time() }}">

سترى أننا استخدمنا تركيبة blade  طباعة اسم الصورة وامتدادها والتي هي مسجلة في قاعد البيانات، وبإبقاء الامتداد في قاعدة البيانات يمكننا استخدام امتدادات مختلفة بدلا من فرض استخدام jpg. على سبيل المثال.

سوف تلاحظ أيضا أننا أضفنا:

'?'. 'time='. time() 

للحصول على متغير، وهذا الأمر سيمنع عملية تخزين المؤقت للصور نظرا لأنه تم إلحاق الوقت في نهاية الرابط. إن التخزين المؤقت للصور يمكن أن يكون مشكلة بالنسبة لإدارة الصور عندما تقوم برفع الصور.

وبالطبع هذا الأمر اختياري ويمكنك تجاوزه.

بقية الشيفرة البرمجية تقريبا نفسها مع بعض التغييرات للصور المصغرة والصور الهواتف. لن تكون الصفحة جميلة جدا، لكنها تقوم بالمهمة.
والآن إذا كان لديك صورة قمت برفعها لحفظها في قاعدة البيانات، يمكنك مشاهدتها عن طريق الذهاب إلى:

yourproject.com/marketingimage/1

يمكنك تغيير الرقم في النهاية لأن هذا الرقم هو رقم الصورة في سجل البيانات.

حسنا، بعد ذلك، قبل أن نعمل على edit و update، لنقم ببناء تابع index في المتحكم ثم عرض (index view).

تابع index

في MarketingImageController، عدل تابع index كما يلي:

public function index()
{
   $images = Marketingimage::all();
   return view('marketingimage.index', compact('images'));
}

يمكنك أن ترى أن الشيفرة سهلة للغاية، فلقد قمنا فقط باسترجاع جميع السجلات إلى كائن images$، والتي يتم تمريرها إلى العرض view عن طريق تابع compact.

عرض Index

بعد ذلك، سنقوم بتعديل views/marketingimages/index.blade.php كما يلي:

@extends('layouts.master')

@section('content')
   {!! Breadcrumb::withLinks(['Home' => '/', 'marketing images' => '/marketingimage']) !!}
   <br>
   <div>
     <div class="panel panel-default">
       <!-- Default panel contents -->
       <div class="panel-heading">List of Marketing Images </div>
       <div class="panel-body">
         <a href="/marketingimage/create"> <button type="button" class="btn btn-lg btn-success">Create New </button> </a>
       </div>

       <!-- Table -->
       <table class="table">
         <tr>
           <th>Id </th>
           <th>Name </th>
           <th>Thumbnail </th>
           <th>Edit </th>
           <th>Delete </th>
         </tr>
       @foreach($images as $image )
         <tr>
           <td>{{ $image->id }}  </td>
           <td>{{ $image->image_name }} </td>
           <td> <a href="/marketingimage/{{ $image->id  }}">
             <img src="/imgs/marketing/thumbnails/{{ 'thumb-'. $image->image_name . '.' .
             $image->image_extension . '?'. 'time='. time() }}"> </a> 
           </td>
           <td><a href="/marketingimage/{{ $image->id }}/edit">
             <span class="glyphicon glyphicon-edit"          
                                 aria-hidden="true"> </span> </a> 
           </td>
           <td>{!! Form::model($image, ['route' => ['marketingimage.destroy', $image->id],                                'method' => 'DELETE']) !!}
                        
            <div class="form-group">
              {!! Form::submit('Delete', array('class'=>'btn btn-danger', 'Onclick' => 'return ConfirmDelete();')) !!}

            </div>
            {!! Form::close() !!} </td>
          </tr>
        @endforeach
      </table>
    </div>
  </div>
@endsection

@section('scripts')
    <script>
       function ConfirmDelete()
       {
           var x = confirm("Are you sure you want to delete?");
           if (x)
               return true;
           else
               return false;
       }
    </script>
@endsection

لقد بدأنا بتوسيع layouts.master ومن ثم قمنا بفتح content كما يلي:

@section('content')

ومن ثم استخدمنا Breadcrumb. (مرة أخرى، إذا كنت لا تستخدم الحزمة فاحذف هذا السطر.)

بعد ذلك قمنا باستخدام شيفرات Bootstrap أساسية، والتي حصلنا عليها من الموقع الرسمي.

وفي النهاية سنجد جدول داخل مُكوّن panel لـ bootstrap بالإضافة إلى زر لإنشاء صورة جديدة، وأزرار أخرى للتعديل والحذف لكل صورة، وستظهر الصور المصغرة لكل صورة في الجدول.

سوف ترى في رأسية مُكوّن panel كيف قمنا بالتعامل مع زر create:

<a href="/marketingimage/create"> 
    <button type="button" class="btn btn-lg btn-success">
        Create New 
    </button> 
</a>

كل شيء واضح، فالجدول ليس معقدا، لكنه تطلب حلقة foreach لطباعة جميع الصفوف (rows):

<!-- Table -->
  <table class="table">
    <tr>
      <th>Id </th>
      <th>Name </th>
      <th>Thumbnail </th>
      <th>Edit </th>
      <th>Delete </th>
    </tr>

@foreach($images as $image )
  <tr>
    <td>{{ $image->id }}  </td>
    <td>{{ $image->image_name }} </td>
    <td> <a href="/marketingimage/{{ $image->id  }}">
      <img src="/imgs/marketing/thumbnails/{{ 'thumb-'. $image->image_name . '.' .
        $image->image_extension . '?'. 'time='. time() }}"> </a>     
    </td>
    <td>  <a href="/marketingimage/{{ $image->id }}/edit">
       <span class="glyphicon glyphicon-edit" aria-hidden="true"> </span> </a> 
    </td>
    <td>{!! Form::model($image, ['route' => ['marketingimage.destroy', $image->id],
                               'method' => 'DELETE'
                 ]) !!}
     <div class="form-group">
       {!! Form::submit('Delete', array('class'=>'btn btn-danger', 'Onclick' => 'return ConfirmDelete();')) !!}

     </div>
       {!! Form::close() !!} 
    </td>
  </tr>
@endforeach
</table>

لا توجد أية صعوبة في الشيفرة، يمكنك أن ترى أنني أضفت مساعدات النموذج form helper حول رابط الحذف delete، وهذا لأسباب أمنية، يجب علينا الحذف باستخدام POST بدل السماح بأن تكون مفتوحة بشكل واسع من خلال استعمال GET.

سوف تلاحظ أيضا أننا قمنا باستخدام التالي:

'Onclick' => 'return ConfirmDelete();'

والتي استخدمناها مع الجافاسكربت لفتح صندوق التأكيد confirm box، حتى لا يقوم المستخدمون بحذف شيء عن طريق الخطأ.
ثم في ('section('scripts@ أضفنا شيفرات جافاسكربت التالية، وكملاحظة سريعة، افترضنا أنه لديك ('yeld('scripts@ في صفحتك الرئيسية (master page)، وإذا لم يكن لديك هذا السطر، يمكنك ببساطة إرفاق الجافاسكربت قبل endsection@ التابعة لـ ('section('content@.

هذه هي شيفرة الجافاسكربت:

 <script>
   function ConfirmDelete()
   {
       var x = confirm("Are you sure you want to delete?");
       if (x)
           return true;
       else
           return false;
   }
 </script>

شيفرة بسيطة للغاية.

والآن، يجب أن تحصل على صفحة index تعمل بدون مشاكل، قم بتجربة ذلك عن طريق الذهاب بمتصفحك إلى:

yourproject.com/marketingimage

نُكمل في الجزء الثالث من الدرس إن شاء الله.

ترجمة -وبتصرّف- للمقال Basic Image Management Part 2 لصاحبه Bill Keck.

حقوق الصورة البارزة: Designed by Freepik.


تفاعل الأعضاء

أفضل التعليقات



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...