إطار العمل Yii2: الـ forms والـ CRUD operations

التعامل مع الـ forms لإدخال البيانات، وإجراء عمليات CRUD من خلال الـ forms

عدنان توتنيمنذ 5 سنوات

تُعتبر العمليات المعروفة بـ CRUD operations من العمليات الأساسية في كل نظام على اختلاف نوعه، فهي العمليات التي يتم من خلالها إدخال records جديدة إلى قاعدة البيانات أو تعديلها أو حذفها أو استرجاعها من أجل عرضها في الصفحات.

كما ويُعتبر تنفيذ هذه العمليات في أطُر العمل التي تعتمد معمارية MVC سهل جداً، فما عليك إلا استدعاء تابع (دالّة) مثل save أو delete لتقوم بالمهمّة بشكلٍ كاملٍ، بعد أن تُسند بعض البيانات أو تجلب الـ record الذي تودّ التعامل معه من حذفٍ أو عرضٍ أو تعديل.

بعد أن تعرّفنا في المقال السّابق على كيفية التعامل مع الـ models بشكلٍ عام، سنقوم في هذا المقال بإجراء هذه العمليات من خلال الـ forms حتى لا تكون البيانات التي نتعامل معها ستاتيكية نقوم بوضعها من خلال الكود. بل سنقوم بإنشاء form يجعل المستخدم قادراً على إدخال البيانات وعرضها وتعديلها وحذفها.

بدايةً سنتحدّث قليلاً عن الـ forms في إطار العمل Yii2.

تذكّر أنّنا عندما تعاملنا مع الـ model فإنّ كلاس الـ model كان يرِثُ من الكلاس ActiveRecord، وقلنا أنّ ذلك يجعل التعامل مع قاعدة البيانات ديناميكياً وسهلاً.

وكذلك الحال بالنسبة للـ forms، فإنّ الـ form الذي نتعامل معه سيكون نسخةً من ActiveForm، حيث من خلاله نستطيع أن نربطَ ما بين الـ attributes الموجودة في الـ model مع حقول الـ form.

فلنفرض مثلاً أنّه لديك attribute اسمها your_email، وكنتَ قد وضعتَ في التابع rules أنّ هذه الـ attribute يجب أن تكون email (أي يجب أن يحوي رمز @ وينتهي بـ .xyz مثلاً)، فعندما تقوم بربط هذه الـ attribute مع إحدى حقول الـ form فسيتم التحقق من القيمة المُدخلة من خلال تطبيق القاعدة rule التي قمتَ بوضعها؛ أي أنّ المستخدم إذا قام بإدخال قيمة myEmail مثلاً دون وضع إشارة @ فإنّ ذلك يُعتبر مخالفةً للقاعدة في تابع rules، وسيقوم برفض هذه القيمة وإظهار رسالة خطأ تُنذره أنّ القيمة التي قمتَ بكتابتها لا تُعتبر email صحيح، وهكذا.

 

لنقم الآن بتطبيق هذه الأمور بشكلٍ فعليٍ كي نشاهد النتائج مباشرةً.

أولاً سنقوم بإنشاء model وليكن اسمه Cars على سبيل المثال، وسنقوم بإنشاء جدولٍ في قاعدة البيانات لنربط هذا الـ model مع الجدول.

سيكون كلاس الـ model كالتالي:

<?php

namespace app\models;

use Yii;

class Cars extends \yii\db\ActiveRecord{
	
	public static function tableName(){
		return 'cars';
	}

	public function rules(){
		return [
			[['brand', 'model', 'price', 'type'], 'required'],
			[['details'], 'safe'],
			[['price'], 'integer'],
		];
	}
	
}

?>

اعتمدنا في هذا المثال على أن تكون كل سيارة لها brand (مثل BMW أو Mercedes)، و model (مثل M5 أو CLK)، وسعر، وتفاصيل خاصة بالسيارة، وأخيراً نمط السيارة (مثل Sedan أو Coupe أو SUV). 

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

 

وسنقوم بإنشاء الجدول التالي في قاعدة البيانات:

CREATE TABLE cars (
	id int(11) NOT NULL AUTO_INCREMENT,
	brand varchar(50) NOT NULL,
	model varchar(50) NOT NULL,
	details text,
	price bigint NOT NULL,
	type enum('SUV', 'Sedan', 'Coupe', 'Truck'),
	PRIMARY KEY(id)
);

 

سنقوم أيضاً بإنشاء controller جديد اسمه CarsController كما هو موضّحُ أدناه، فلن نقوم باستخدام الـ SiteController لأنّه ينبغي علينا تنظيم العمل بشكلٍ يمكّننا من سهولة العودة إلى الكود لاحقاً لصيانته والتعديل عليه إن أردنا ذلك، فاصطلاحاً كل model يقابله controller خاص به.

وسنقوم بإنشاء مجلد اسمه cars ضمن مجلد views لنضع داخله الصفحات التي نريد عرضها فيما بعد.

ملاحظة: يمكننا ببساطة أن نتعامل مع Gii، وهي أداة متوفرة مع إطار العمل Yii من أجل إنشاء ملفات الـ models والـ views والـ controllers بكبسة زر، ولكنّني أفضّل في بداية الأمر أن نقوم بذلك بشكلٍ يدويٍ حتى نعرفَ تماماً ما نقوم بفعله وكيفية سير الأمور، أما فيما بعد فيمكننا استخدام Gii لتسهيل الأمور.

سنقوم بتعديل ملف الـ controller لنضع فيه الـ actions التالية وستكون فارغةً في البداية، وسنأتي لذكر تفاصيل كلٍّ منها تباعاً.

<?php 

namespace app\controllers;

use Yii;
use yii\filters\AccessControl;
use yii\web\Controller;
use yii\web\Response;
use yii\filters\VerbFilter;
use app\models\Cars;

class CarsController extends Controller{

    public function actionCreate(){

    }
    Public function actionUpdate($id){

    }
    Public function actionDelete($id){

    }
    Public function actionView($id){

    }
}

لاحظ أنّ الـ actions كلّها باستثناء create لديها parameter وحيد وهو id الـ object الذي نريد تعديله أو حذفه أو عرضه، بينما في create فنحن سنقوم بإنشاء record جديد، فلا نحتاج للـ id الخاص به.

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

إنشاء object جديد:

سنقوم بتعديل التابع create ليكون بالشكل التالي:

public function actionCreate(){
	$car = new Cars();
	if($car->load(Yii::$app->request->post())){

    }
    return $this->render('create', ['my_car' => $car]);
}

ربما يبدو شكل الـ action غير مفهوم في البداية، لكنّنا سنقوم بشرح ذلك ليتّضح معنى ما قمنا بكتابته.

أولاً قمنا بإنشاء object جديد، وهذا الـ object هو الذي سيقوم بحمل القِيَم التي سيقوم المستخدم بإدخالها.

الآن اقفز للسّطر الأخير من الـ action لترى أنّنا قمنا باستدعاء التابع render (والذي كما قلنا سابقاً أنّه المسؤول عن عرض صفحة view)، وقمنا أيضاً من خلاله بتمرير الـ object الذي أنشأناه عن طريق الـ parameter الثاني والذي يكون على شكل مصفوفةٍ من النّمط key/value، أي أنّ الـ key الذي اسمه my_car سيحمل القيمة والتي هي الـ object كاملاً، وهذا الـ key هو الذي سنتعامل معه في صفحة الـ view التي اسمها create.

بمعنى آخر: قمنا بإنشاء object ونقلناه إلى صفحة view، فسيصبح هذا الـ object معرّفاً في صفحة الـ view ونستطيع أن نتعامل معه وكأنّنا قمنا بتعريفه هناك.

الآن فلتنظر إلى التعليمة الشرطية التي تُعتبر بعض الشيء معقدةً وليست بسيطة!

انظر إليها على أنّها كالتالي:

if(request == 'post')

أي إذا كان نمط الـ request هو post (أي قمنا بالضغط على زر submit مثلاً) فقم بتنفيذ محتوى التعليمة الشرطية.

هكذا يمكنك فهم الشرط، ولكنّ الذي كتبناه يقوم بأكثر من ذلك، فهو يعني أنّ الـ request إذا كانت من النمط post فقم بوضع القِيَم التي جاءت عن طريق هذه الـ request (أي التي جاءت عن طريق الـ form الذي قام المستخدم بتعبئته) وضعها جميعها في الـ object الذي اسمه car.

تذكّر أنّنا نتعامل مع ActiveForm و ActiveRecord، أي أنّ عملية الرّبط ستتم بشكلٍ تلقائي، فلن تكون بحاجةٍ لكتابة التالي:

$car->brand = request('brand');
$car->price = request('price'); 

فسيتم ذلك بشكلٍ تلقائيٍ من خلال التابع load الذي وضعناه في التعليمة الشرطية.

خلاصة: الطريقة المتّبعة في إطار العمل Yii2 هي أنّك تستخدم نفس التابع من أجل التعامل مع الـ get request والـ post request، فمن خلال الـ get request ستقوم بإنشاء الـ object وستقفز مباشرةً إلى نهاية الـ action لتقوم بإعادة صفحة الـ view التي تحوي الـ form دون الدخول إلى التعليمة الشرطية لأنها تتعامل مع post request.

أمّا إذا قمتَ بالضغط على زر submit بعد تعبئة الـ form فستصبح الـ request من النمط post، وسيتم تعريف الـ object ومن ثمّ الدخول إلى التعليمة الشرطية وملء الـ object بقِيَم الـ post request وعمل mapping (ربط) بين القِيَم وبين الـ attributes في الـ object من خلال التابع load.

ولم يتبقَّ لدينا إلى أن نقوم بحفظ وتخزين الـ object في قاعدة البيانات من خلال التابع save وذلك في حال تحقق القواعد الموجودة في تابع rules.

أمّا في حال عدم تحققها فسيتم الخروج من التعليمة الشرطية والعودة إلى الصفحة التي فيها الـ form، وسيتم إظهار الأخطاء التي بسببها لم يتم حفظ الـ object.

لذلك قم بتعديل التابع create ليصبح بالشكل التالي:

public function actionCreate(){
	$car = new Cars();
	if($car->load(Yii::$app->request->post())){
		if($car->save()){
			return '<pre>' . var_export($car->attributes, true) . '</pre>';
        }
     }
     return $this->render('create', ['my_car' => $car]);
}

ملاحظة: الـ attribute التي اسمها attributes والتي قمنا باستدعائها من الـ object الذي اسمه car سيحوي جميع البيانات التي قمنا بإدخالها.

الآن سنقوم بإنشاء ملف create.php ضمن مجلد views/cars من أجل وضع الـ form الخاص بإضافة سيارة جديدة، وسنكتب فيه الكود التالي:

<?php
    use yii\helpers\Html;
    use yii\widgets\ActiveForm;
    
    $cars_types_list = [
	    'SUV' => 'SUV',
	    'Sedan' => 'Sedan',
        'Coupe' => 'Coupe',
	    'Truck' => 'Truck'
    ];
?>

<div>
    <?php $form = ActiveForm::begin(); ?>

    <?= $form->field($my_car, 'brand')->textInput() ?>
    <?= $form->field($my_car, 'model')->textInput() ?>
    <?= $form->field($my_car, 'details')->textarea(['rows' => 4]) ?>
    <?= $form->field($my_car, 'price')->textInput(['type' => 'number']) ?>
    <?= $form->field($my_car, 'type')->dropDownList($cars_types_list) ?>

    <?= Html::submitButton('Add new car', ['class' => 'btn btn-success']) ?>

    <?php ActiveForm::end(); ?>
</div>

قمنا أولاً بتضمين الكلاس المساعد Html والذي من خلاله يمكننا استخدام توابعه لإضافة عناصر الـ Html وتزويدها بالخصائص الهامّة بشكلٍ مبسّطٍ ومنظّم.

وقمنا أيضاً بتضمين ActiveForm، حيث استدعينا منه التابع begin في بداية الـ form لإنشاء object سيحوي حقول الـ form فيما بعد، وفي نهاية الـ form استدعينا منه التابع end، ونكون بذلك قد حصرنا الحقول بين التابعَين begin و end.

وقمنا بتعريف مصفوفة cars_types_list والتي سنستخدمها في قائمةٍ منسدلة لاحقاً. 

أمّا طريقة إنشاء حقل تابع للـ form فيكون ذلك من خلال استدعاء التابع field من الـ form object، والذي يأخذ وسيطَين:

$form->field(model_object, attribute_in_model_object)

الوسيط الأول هو الـ object الذي أنشأناه في الـ controller ونقلناه إلى صفحة الـ view الحاوية على الـ form، أمّا الوسيط الثاني فهو إحدى الـ attributes الموجودة في الـ model، فنكون بذلك قد ربطنا بين حقل الـ form والـ attribute التابعة للـ model، وذلك يفيدنا في التحقق التلقائي من القواعد الموجودة قي تابع rules كما بيّنّا من قبل.

ملاحظة: عندما نقوم بإضافة حقل إلى الـ form نقوم بوضعها ضمن الوسم <?=  ?> والذي هو بمثابة التعليمة echo.

نلاحظ أنّه في الـ form لم نقم بوضع الـ method ولا الـ action، وذلك لأنّ الحالة الافتراضية للـ method هي post، أمّا الـ action فتكون هي نفسها المكان الذي قمنا باستدعاء صفحة الـ view منه (ما لم نقم بتغييره من خلال إضافة مصفوفة options إلى التابع begin الذي استدعيناه عند إنشاء الـ form object)، فالآن عند الضغط على زر الـ submit سيتم نقل بيانات الـ form إلى التابع create، وحينها سيتم عمل load للبيانات وإدخالها في الـ object المُعرّف من الـ model.

أي كأنّنا جلبنا البيانات الموجودة في مصفوفة $_POST (المُعرّفة مسبقاً في لغة php) ومن ثمّ قمنا بالرّبط بين كل attribute والحقل المقابل لها في الـ form.

الآن لتجريب ما قمنا بكتابته توجّه إلى المسار التالي:

localhost:8080/cars/create

فسيتم تعريف الـ object من الـ model والانتقال مباشرةً إلى تعليمة return الأخيرة لعرض صفحة الـ view التي تحوي الـ form، وستنقل تعليمة return الـ object الذي أنشأناه معها من أجل بناء الـ form اعتماداً عليه وعلى الـ attributes الموجودة فيه.

قم بالمرور على الحقول دون تعبئتها (اضغط على الحقل ومن ثمّ اضغط خارجه دون أن تكتب فيه شيئاً)، فستلاحظ ظهور رسائل خطأ لأنّك تركتَ الحقول فارغة (وهذه فائدة من فوائد استخدام ActiveForm وربط الحقول مع الـ attributes).

الآن قم بتعبئة الحقول بقِيَم صحيحة واضغط على زر submit، فسيتم نقل البيانات إلى التابع create ليتم تخزينها في الـ object (في حال حقّقت جميع القواعد بالطبع)، وسيتم إعادة الـ object الذي تمّ تخزينه في قاعدة البيانات وعرضه في صفحة بيضاء (لأنّنا لم نقم بإعادة صفحة view وإنما قمنا بعمل var_export).

هكذا نكون قد انتهينا من شرح -ربما يكون مملّاً بعض الشيء- لإنشاء object من خلال الـ form، وتعمّدت الإطالة في شرحه كي تكون الأمور واضحة تماماً، لأنّ جميع الأمور اللاحقة في هذا التطبيق وفي غيره ستعتمد هذه الفكرة أو أفكاراً مشابهةً لها.

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

تعديل object:

الآن ننتقل إلى تعديل object، ولن نطيل الشرح لأنّ الفكرة نفسها في التابع create، إلّا أنّنا هنا نقوم بجلب الـ object من قاعدة البيانات اعتماداً على الـ id الخاص به، ثمّ نقوم بإرسال الـ object إلى صفحة update التي تحوي نفس الـ form الموجود في صفحة create، ولكن بما أنّ هذا الـ object يحوي قِيماً مخزّنة فيه من قبل، فستلاحظ أنّ حقول الـ form تكون ممتلئة بالقيمة الخاصّة بكل attribute (وهذه فائدة أخرى من فوائد استخدام ActiveForm وربط الحقول مع الـ attributes)، فما عليك الآن إلا تعديل البيانات التي تريدها ومن ثمّ الضغط على زر submit لحفظ التعديلات.

تابع update:

public function actionUpdate($id){
	$car = Cars::findOne($id);
	if($car->load(Yii::$app->request->post())){
		if($car->save()){
			return '<pre>' . var_export($car->attributes, true) . '</pre>';
        }
    }
    return $this->render('update', ['my_car' => $car]);
}

لا تنسَ أن تنشئ صفحة update.php ضمن مجلد views/cars والذي يحوي نفس الكود الموجود في صفحة create.php (طبعاً لا يجب تكرار الكود نفسه في عدّة صفحات، ولكنّنا هنا نقوم بشرح الأمور خطوةً خطوة، ويمكننا فيما بعد أن نستغني عن هذا التكرار).

الآن ومن أجل الوصول إلى صفحة تعديل الـ object نتوجّه إلى المسار التالي (والذي من خلاله نستدعي الـ action التي اسمها update ونمرّر لها الـ id كـ parametetr):

localhost:8080/cars/update?id=1

ملاحظة: التابع findOne المُستدعى من كلاس الـ model يأخذ وسيطاً وهو الـ id الخاص بالـ object الذي نريد جلبه من قاعدة البيانات، ويمكننا أيضاً (كطريقةٍ بديلة) استخدام التابع where لجلب نفس الـ object كالتالي:

Cars::find()->where(['id' => $id])->one();

لاحظ أنّنا استخدمنا التابع find (بدلاً من findOne) لربطه مع التابع where الذي يأخذ كـ paramter مصفوفة من النمط key/value، حيث يمثّل الـ key اسم الـ attribute .. وأخيراً ربطنا التابع where مع التابع one لجلب object واحد، لأنّنا نعلم مسبقاً أنّ الـ id هو حقل فريد (unique) ولا يتكرر، أمّا لو أردنا مثلاً أن نقوم بجلب كل الـ objects التي تحقق شرطاً معيناً فنكتب:

Cars::find()->where(['brand' => 'BMW'])->all();

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

عرض object وحذفه:

سنقوم أخيراً بدمج أمرَي العرض والحذف سويةً لنُكمل بذلك CRUD operations.

أنشئ صفحةً اسمها view في مجلد views/cars من أجل عرض معلومات الـ object، وسنضع فيها الكود التالي:

<?php 
    use yii\helpers\Html;
    use yii\widgets\DetailView;
?>

<h1>Car information:</h1>
<div class='col-md-8'>
    <p>
	    <?= Html::a('Update', ['update', 'id' => $my_car->id], ['class' => 'btn btn-primary']) ?>
	    <?= Html::a('Delete', ['delete', 'id' => $my_car->id], [
		    'class' => 'btn btn-danger',
		    'data' => [
			    'confirm' => 'Are you sure you want to delete this item?'
		    ],
	    ]) ?>
    </p>

    <?= DetailView::widget([
	    'model' => $my_car,
	    'attributes' => [
		    'id',
		    'brand',
		    'details',
		    'price',
		    'type'
	    ],
    ]) ?>
</div>

في الكود السابق قمنا بتضمين الكلاس Html الذي تعرّفنا عليه من قبل، وقمنا أيضاً بتضمين الـ widget التي اسمها DetailView، وهي أداة تسهّل علينا عرض البيانات الخاصّة بـ object معيّن، حيث تقوم بعرض البيانات على شكل جدول أحد أعمدته يعرض أسماء الـ attributes الخاصّة بالـ object، والعمود الآخر يعرض قِيَم هذه الـ attributes.

بعدها قمنا بوضع زرَّين باستخدام Html helper، وتحديداً استخدمنا التابع a() الذي يأخذ كوسيطٍ أول الاسم الذي نريد وضعه على الزر، أما الوسيط الثاني فيمثّل خاصية href الموجودة في وسم <a> الموجود في HTML، حيث يكون هذا الوسيط عبارة عن مصفوفة؛ القيمة الأولى فيها هي اسم الـ action التي سيتم تنفيذها عند الضغط على الزر (لاحظ أنّنا لسنا بحاجة لوضع اسم الـ controller)، أما القيمة الثانية في المصفوفة فهي الـ parameters التي نقوم بتمريرها للـ action.

الوسيط الثالث للتابع a() عبارة عن مصفوفة من الـ options، فيمكننا أن نضع فيها الكلاسات التي يمكن للزر أن يأخذها أو الـ style (عناصر CSS) التي نودّ إضافتها للزر.

في الزر update وضعنا كلاس من كلاسات bootstrap، أما في الزر delete فوضعنا كلاس bootstrap، ووضعنا أيضاً الخاصّية data التي من خلالها وضعنا رسالة تأكيد يتم إظهارها قبل الحذف.

وفي نهاية الكود استعملنا الأداة DetailView لرسم جدول البيانات.

الأداة DetailView تأخذ عدّة attributes، أهمّا الخاصّية model والتي تكون قيمتها هي الـ object الذي نريد وضع بياناته في الجدول (وفي حالتنا هنا وضعنا الـ object الذي قمنا بتمريره من الـ controller)، أمّا الخاصية attributes فنحدد من خلالها الـ attributes الخاصة بالـ object التي نريد عرضها، فربما لا نريد عرض كل الـ attributes.

الآن سنقوم بتعديل التابع view الموجود في الـ controller ليقوم بعرض صفحة view التي أنشأناها:

public function actionView($id){
	$car = Cars::findOne($id);
	//or: $car = Cars::find()->where(['id' => $id])->one();
	return $this->render('view', ['my_car' => $car]);
} 

وبالتوجه إلى المسار التالي:

localhost:8080/cars/view?id=1

الآن عند الضغط على زر Update سيتم تنفيذ الـ action التي اسمها update، وبالتالي سيتم عرض صفحة تعديل object كما بيّنا من قبل.

أخيراً سنقوم بتعديل التابع delete من أجل حذف object:

public function actionDelete($id){
	$car = Cars::findOne($id);
	if($car->delete()){
		return var_dump('The car has been deleted successfully');
    }
}

التابع delete (المُستدعى من الـ object) يُعيد قيمة true في حال تمّت عملية الحذف بنجاح.

الآن عند الضغط على زر delete سيتم التوجّه تلقائياً إلى المسار التالي لتنفيذ عملية الحذف:

localhost:8080/cars/delete?id=1

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

تعرّفنا في هذا المقال على كيفية تنفيذ عمليات CRUD، واستخدمنا الـ forms وتعرّفنا على الحقول وكيفية إضافتها، والآن يمكنك إنشاء الـ models وتطبيق ما تعلّمتَه عدّة مرّات لتتمكّن من الأفكار بشكلٍ كاملٍ قبل أن تبدأ ببناء تطبيقاتٍ أكثر تعقيداً.

0
إعجاب
1410
مشاهدات
0
مشاركة
1
متابع

التعليقات (0)

لايوجد لديك حساب في عالم البرمجة؟

تحب تنضم لعالم البرمجة؟ وتنشئ عالمك الخاص، تنشر المقالات، الدورات، تشارك المبرمجين وتساعد الآخرين، اشترك الآن بخطوات يسيرة !