Caution: You are browsing the legacy symfony 1.x part of this website.
Cover of the book Symfony 5: The Fast Track

Symfony 5: The Fast Track is the best book to learn modern Symfony development, from zero to production. +300 pages showcasing Symfony with Docker, APIs, queues & async tasks, Webpack, SPAs, etc.

اليوم 3: طراز البيانات

رأينا سابقا في Jobeet

لمن يحب فتح قارئ نصوصه وكتابة بعض أسطر PHP سيكون سعيدا بمعرفته أن درس اليوم سيمكنه من البرمجة. سنقوم بتعريف طراز بينات Jobeet وسنستعمل ORM للتحاور مع قاعدة البيانات ثم ننشئ أول وحدة من تطبيقنا. و بما أن symfony يقوم بالجزء الأكبر من العمل فسنحصل على وحدة تعمل جيدا دون كتابة الكثير من الرمز PHP .

الطراز الإرتباطي

حكايات المستعملين التي قمنا أمس بسردها تبرز العناصر الأساسية لمشروعنا : الوظائف و الأصناف و المنضمين

وفي مايلي مبيان العلاقات و الكيانات :

Entity relationship diagram

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

و كذلك بالنسبة للحقل updated_at سيأخذ قيمة ساعة النظام أثناء التحديث .

المخطط

لتسجيل الوظائف و الأصناف و المنضمين أكيد سنكون بحاجة لقاعدة بيانات إرتباطية . ولكن بما أن symfony إطار موجه للكائنات فنحن نحب إستخدام الكائنات كلما سمحت الفرصة. على سبيل المثال نفضل إستعمال الكائنات على كتابة إستعلامات SQL للحصول على تسجيلات من قاعدة البيانات. يجب على معلومات قاعدة البيانات الإرتباطية أن توافق طراز الكائن وهذا يمكن فعله عن طريق أداة ORM tool .و شكرا لsymfony الذي يقدم لنا إثنين هما: Propel و Doctrine في هذا الدرس سنستعمل Propel.

ORM تتطلب وصف الجداول و العلاقات بينها لإنشاء الأصناف المرتبطة بها , هناك طريقتان لكتابة مخطط الوصف : إستجواب قاعدة بيانات موجودة أو إنشاء مخطط يدويا.

معلومة توجد أدوات تمكنك من بناء قاعدة بيانات عن طريق مبيان على سبيل المثال Fabforce's Dbdesigner ينشأ أتوماتيكيا ملف schema.xml DB Designer 4 TO Propel Schema Converter

بما أن قاعدة البيانات لا توجد بعد و نريد الحفاظ عليها agnostic في Jobeet. لننشأها يدويا باستعمال الملف config/schema.yml

# config/schema.yml
propel:
  jobeet_category:
    id:           ~
    name:         { type: varchar(255), required: true }
 
  jobeet_job:
    id:           ~
    category_id:  { type: integer, foreignTable: jobeet_category, foreignReference: id, required: true }
    type:         { type: varchar(255) }
    company:      { type: varchar(255), required: true }
    logo:         { type: varchar(255) }
    url:          { type: varchar(255) }
    position:     { type: varchar(255), required: true }
    location:     { type: varchar(255), required: true }
    description:  { type: longvarchar, required: true }
    how_to_apply: { type: longvarchar, required: true }
    token:        { type: varchar(255), required: true, index: unique }
    is_public:    { type: boolean, required: true, default: 1 }
    is_activated: { type: boolean, required: true, default: 0 }
    email:        { type: varchar(255), required: true }
    expires_at:   { type: timestamp, required: true }
    created_at:   ~
    updated_at:   ~
 
  jobeet_affiliate:
    id:           ~
    url:          { type: varchar(255), required: true }
    email:        { type: varchar(255), required: true, index: unique }
    token:        { type: varchar(255), required: true }
    is_active:    { type: boolean, required: true, default: 0 }
    created_at:   ~
 
  jobeet_category_affiliate:
    category_id:  { type: integer, foreignTable: jobeet_category, foreignReference: id, required: true, primaryKey: true, onDelete: cascade }
    affiliate_id: { type: integer, foreignTable: jobeet_affiliate, foreignReference: id, required: true, primaryKey: true, onDelete: cascade }

tip

إذا قررت بإنشاء الجداول بكتابة الرمز SQL تستطيع توليد ملف التهيئة schema.ymlبتطبيق الأمر
propel:build-schema

قاعدة البيانات

الإطار symfony يحتمل جميع قواعد البيانات المتوافقة مع PDO

(..., MSSQL, Oracle, SQLite, PostgreSQL, MySQL).

PDO هي طبقة التجريد لقاعدة البيانات الممنوحة من طرف PHP.

استعملوا MySQL في هذا الدرس :

$ mysqladmin -uroot -pmYsEcret create jobeet 

note

أنتم أحرار في إختيار محرك آخر لقواعد البيانات . فلن يكون من الصعب تكيف الرمز الذي سنكتبه لأننا نستعمل ORM الذي سيكتب لنا الرمز SQL.

يجب علينا أن ندل symfony على قاعدة البيانات التي سنستعملها لمشروع Jobeet:

$ php symfony configure:database "mysql:host=localhost;dbname=jobeet" root mYsEcret

يتطلب الأمر configure:databaseثلاث معطيات : PDO DSN اسم المستخدم و كلمة المرور للوصول لقاعدة البيانات . لا تستعمل المعطى الثالث إذا كنت لا تستعمل كلمة مرور للوصول لمزود التطوير .

note

يكتب الأمر configure:database تهيئة قاعدة البيانات في الملف config/databases.yml. يمكنكم التعديل على هذا الملف يدويا و الاستغناء عن الأمر.

ORM

بفضل وصف قاعدة البيانات الموجود في الملف schema.yml, نستطيع إستعمال الأوامر المتضمنة في Propel لتوليد تصاريح SQL اللازمة لإنشاء الجداول :

$ php symfony propel:build-model

بما أن النماذج حاضرة الآن يمكنك أن تولد أو تضيف SQL .

$ php symfony propel:build-sql

الأمر propel:build-sql يولد تصاريح SQL في المجلد data/sql الأمثل لمحرك قاعدة البيانات التي قمنا بتهييئها:

#مقتطف من data/sql/lib.model.schema.sql
CREATE TABLE `jobeet_category`
(
  `id` INTEGER  NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(255)  NOT NULL,
  PRIMARY KEY (`id`)
)Type=InnoDB;

الآن أنشئ الجداول في قاعدة البيانات بتنفيذ الأمر propel:insert-sql:

$ php symfony propel:insert-sql

بما أن الأمر يحذف الجداول الموجودة قبل أن ننشأها من جديد, يجب عليكم تأكيد العملية . تستطيعون أيضا إستعمال الخاصية no-confirmation-- لتجنب تأكيد الأمر , و هذا ما يتم تطبيقه إذا كنا نريد تنفيذ الأمر من سطر أوامر غير متفاعل :

$ php symfony propel:insert-sql --no-confirmation

tip

كأي أداة تستعمل سطر الأوامر , يمكن لأوامر symfony أن تأخذ حججا و خواصا. كل أمر مصحوب برسالات مساعدة التي يمكن إظهارها بتنفيذ الأمر help :

$ php symfony help propel:insert-sql

رسالة المساعدة تمنح قائمة الحجج و الخواص الممكنة , تمنح القيم الافتراضية لكل واحدة منهم و تعطي أمثلة مفيدة .

يولد ORM أيضا أصناف PHP التي تقوم بالربط بين تسجيلات الجداول و العناصر :

$ php symfony propel:build-model

الأمر propel:build-model يولد ملفات PHP التي ستستعمل للتفاعل مع قاعدة البيانات في المجلد lib/model.

إذا بحثنا في الملفات المولدة فمن المحتمل أن تلاحظوا بأن Propel يولد 4 أصناف لكل جدول . للجدول jobeet_job :

  • JobeetJob : عنصر من هذا الصنف يمثل تسجيلا واحدا في الجدول jobeet_job. هذا الصنف فارغ إفتراضيا .

  • BaseJobeetJob: هي الصنف الأب ل JobeetJob. في كل مرة تنفذون الأمر propel:build-model, يعاد كتابة الصنف . يجب أن تتم جميع التطويرات في الصنف JobeetJob.

  • JobeetJobPeer: هذا الصنف يعرف الأساليب الثابتة التي تعطي كنتيجة مجموعة عناصر JobeetJob. هذا الصنف فارغ إفتراضيا .

  • BaseJobeetJobPeer: هي الصنف الأب ل JobeetJobPeer. في كل مرة تنفذون الأمر propel:build-model, يعاد كتابة الصنف . يجب أن تتم جميع التطويرات في الصنف JobeetJobPeer.

يمكن التلاعب بقيم أعمدة تسجيل ما من خلال العنصر باستعمال الموصل
(get*() methods) و (mutators (set*()methods :

$job = new JobeetJob();
$job->setPosition(`Web developer`);
$job->save();
echo $job->getPosition();
$job->delete();

يمكنكم أيضا تعريف المفتاح الأجنبي بربط العناصر ببعضها :

$category = new JobeetCategory();
$category->setName(`Programming`);
$job = new JobeetJob();
$job->setCategory($category);

الأمرpropel:build-all عبارة عن إختصار لتنفيذ الأوامر التي سبق أن رأيناها أو أخرى. إذا نفذ الآن الأمر لتوليد الأشكال و مصادقة طراز عناصر Jobeet:

$ php symfony propel:build-all

و سترون في آخر اليوم تطبيق المصادقة و سنتطرق للأشكال في اليوم 10 بشكل مدقق .

tip

الأمر propel:build-all-load عبارة عن إختصار ل propel:build-all متبوع بالأمر propel:data-load.

و كما سنرى بعد قليل يقوم symfony أتوماتيكيا بشحن أصناف PHP من أجلكم , و هذا ما يعني أنكم لستم بحاجة إلى إستعمال require في برمجتكم . هذه واحدة من العديد من الأشياء التي يقوم symfony بجعلها أتوماتيكيا للمبرمجين , و لكن بالمقابل يجب عليكم في كل مرة تضيفون فيها عنصرا تفريغ المخزن . و بما أن الأمر propel:build-model أنشأ عناصر جديدة , يجب تفريغ المخزن :

$ php symfony cache:clear

tip

يتكون أمر synfony من فضاء من الأسماء و اسم الأمر . لدى كل أمر إختصار بأقل غموض مع الأوامر الأخرى . الأمر التالي موافق للأمر :

$ php symfony cc

المعطيات الأولية

لقد أنشأت الجداول في قاعدة البيانات لكنها فارغة . في كل تطبيق ويب هناك ثلاث أنواع من المعطيات :

  • المعطيات الأولية: المعطيات الأولية مهمة لاشتغال التطبيق. على سبيل المثال,يلزم Jobeet الأصناف , و إلا لن يتمكن أحد من تقديم وظيفة . و يلزمنا أيضا مسير يستطيع أن يدخل لل backend.

  • معطيات الإختبار: معطيات الإختبار ضرورية لتجربة التطبيق. كمبرمج اكتب إختبارات لتتأكد من أن Jobeet يتصرف كما هو مذكور في حكايات المستعملين . أفضل طريقة هي كتابة إختبارات أتوماتيكية . إذا في كل مرة تريدون عمل إختبارات فأنتم بحاجة إلى قاعدة بيانات نظيفة بمعطيات جديدة .

  • معطيات المستعمل: معطيات المستعمل ينشئها المستعملون خلال الحياة العادية للتطبيق .

    في كل مرة يقوم symfony بإنشاء الجداول في قاعدة البيانات نفقد المعطيات. لإرسال المعطيات الأولية للقاعدة نستطيع كتابة سكريبت PHP, أو تنفيذ رمز SQL بواسطة البرنامج mysql. بما أن الحاجة مشتركة , هناك طريقة أفضل بواسطة symfony: إنشاء ملف YAML في المجلد data/fixtures/ و إستعملوا الأمر propel:data-load لإرسالهم لقاعدة البيانات:

    # data/fixtures/010_categories.yml
    JobeetCategory:
      design:        { name: Design }
      programming:   { name: Programming }
      manager:       { name: Manager }
      administrator: { name: Administrator }
     
    # data/fixtures/020_jobs.yml
    JobeetJob:
      job_sensio_labs:
        category_id:  programming
        type:         full-time
        company:      Sensio Labs
        logo:         sensio_labs.png
        url:          http://www.sensiolabs.com/
        position:     Web Developer
        location:     Paris, France
        description:  |
          You've already developed websites with symfony and you want to work
          with Open-Source technologies. You have a minimum of 3 years
          experience in web development with PHP or Java and you wish to
          participate to development of Web 2.0 sites using the best
          frameworks available.
        how_to_apply: |
          Send your resume to fabien.potencier [at] sensio.com
        is_public:    true
        is_activated: true
        token:        job_sensio_labs
        email:        [email protected]
        expires_at:   2010-10-10
     
      job_extreme_sensio:
        category_id:  design
        type:         part-time
        company:      Extreme Sensio
        logo:         extreme_sensio.png
        url:          http://www.extreme-sensio.com/
        position:     Web Designer
        location:     Paris, France
        description:  |
          Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do
          eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut
          enim ad minim veniam, quis nostrud exercitation ullamco laboris
          nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor
          in reprehenderit in.
     
          Voluptate velit esse cillum dolore eu fugiat nulla pariatur.
          Excepteur sint occaecat cupidatat non proident, sunt in culpa
          qui officia deserunt mollit anim id est laborum.
        how_to_apply: |
          Send your resume to fabien.potencier [at] sensio.com
        is_public:    true
        is_activated: true
        token:        job_extreme_sensio
        email:        [email protected]
        expires_at:   2010-10-10

    ملف fixtures للإختبارات مكتوب ب YAML. يستعمل طراز العناصر مع اسم وحيد لكل تسجيل . يصلح هذا الإسم لربط العناصر الارتباطية في ما بينها دون ضرورة تعريف المفتاح الرئيسي (و غالبا ما يكون هذا الحقل يزيد أتوماتيكيا و لا يعرف ). على سبيل المثال, الوظيفة job_sensio_labs يستعمل الصنف programming, و هذاالذي يتعلق بالصنف Programming.

    يمكن لملف fixture الإحتواء على عنصر طراز واحد أو العديد.

tip

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

في ملف fixtures لستم بحاجة إلى تعريف قيم جميع الأعمدة . في هذه الحالة سيستعمل symfony القيمة الإفتراضية المشار إليها في مخطط قاعدة البيانات . و بما أن symfony يستعمل Propel لتحميل المعطيات في القاعدة, تم تفعيل جميع تصرفات الإنشاء مثل (created_at أو updated_at) أو السلوكيات التي أضفتموها للنموذج .

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

$ php symfony propel:data-load 

See it in Action in the Browser

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

ترون كيف تظهر قائمة الوظائف , كيفية تحرير وظيفة موجودة , و كيفية حذفها . وكما شرحنا في اليوم الأول مشروع symfony مكون من تطبيق .
كل واحدة منها مكونة من modules. يحتوي module على رمز PHP الذي يمثل مهمة من التطبيق (مثلا API module) أو التلاعب في نموذج العنصر الذي يمكن لمستعمل أن يقوم به (مثلا job module).

symfony قادر على إن يولد أتوماتيكيا لنموذج وحدات الذي يمنح مهمات أولية :

 $ php symfony propel:generate-module --with-show --non-verbose-templates frontend job JobeetJob

الأمر propel:generate-module يولد الوحدة job في التطبيق frontend في نموذج JobeetJob. كما في مجمل أوامر symfony, أنشئت من أجلك مجلدات و ملفات في المجلد apps/frontend/modules/job :

دليل الوصف
/actions عمل الوحدة
/templates قوالب الوحدة

الملف actions/actions.class.php يعرف جميع الاعمال المتوفرة للوحدة job :

دليل الوصف
index تظهر تسجيلات الجدول
show تظهر حقل تسجيل معطى
new تظهر قائمة لإنشاء تسجيل جديد
create إنشاء تسجيل جديد
edit تظهر قائمة لتحرير تسجيل
update تحديث تسجيل وفقا لقيمة مقدمة
delete حذف تسجيل معطى

يمكنك من الآن تجربة الوحدة job في متصفحك :

 http://jobeet.localhost/frontend_dev.php/job

Job module

إذا كنت تريد تحرير وظيفة, فستحصل على إستثناء لأن symfony يحتاج تمثيل كتابي لصنف . تمثيل عنصر PHP يمكن أن يعرف عن طريق الدالة السحرية ()toString__. يجب أن يعرف التمثيل الكتاب لتسجيل لصنف في الصنف JobeetCategory:

// lib/model/JobeetCategory.php
class JobeetCategory extends BaseJobeetCategory
{
  public function __toString()
  {
    return $this->getName();
  }
}

الآن في كل مرة يحتاج symfony لتمثيل لصنف , تنادى الدالة السحرية ()toString__ و تعطي كنتيجة اسم الصنف. و بما أننا سنكون بحاجة لتمثيل لجميع أصناف الطراز في وقت أو آخر , عرفوا في كل صنف طراز الدالة ()toString__.

// lib/model/JobeetJob.php
        class JobeetJob extends BaseJobeetJob
    {
      public function __toString()
      {
        return sprintf('%s at %s (%s)', $this->getPosition(), $this->getCompany(), $this->getLocation());
      }
    }
 
// lib/model/JobeetAffiliate.php
        class JobeetAffiliate extends BaseJobeetAffiliate
    {
      public function __toString()
      {
        return $this->getUrl();
      }
    }

تستطيع إنشاء أو تحرير وظيفة . جربوا أن تتركوا حقل مطلوب فارغا أو بإدخال تاريخ خاطئ . جيد لقد أنشأ symfony قواعد مصادقة أساسية بفحص نموذج قاعدة البيانات .

validation

نراكم غدا إن شاء الله

نكتفي اليوم بهذا القدر. لقد حذرناكم في المقدمة. اليوم, بالكاد كتبنا رمز PHP لكن لدينا وحدة تعمل على النموذج job, جاهز للتغيير حسب رغبتكم. تذكروا, دون رمز PHP يعني لا وجود للأخطاء المعوقة . إذا بقيت لديك بعد الطاقة كونوا أحرار في قراءة شفرة لكل وحدة أو لكل نموذج و حاولوا فهم طريقة عملها. و إذا لم تتمكنوا من الفهم فلا تقلقوا و ناموا جيدا , لأننا غدا سنتكلم عن أحد النماذج الأكثر إستعمالا في إطارات الويب MVC design pattern.

رمز اليوم متوفر في مخزن Jobeet SVN بالوسم release_day_03

(/http://svn.jobeet.org/tags/release_day_03).