Skip to content
Caution: You are browsing the legacy symfony 1.x part of this website.

Ngày 3: Data Model

Language
ORM

Tóm tắt

Những ai nóng lòng muốn mở text editor và viết vài đoạn code PHP chắc sẽ rất vui khi được biết ngày hôm nay chúng ta sẽ làm điều đó. Chúng ta sẽ xác định Jobeet data model, sử dụng ORM để tương tác với cơ sở dữ liệu, và xây dựng module đầu tiên của ứng dụng. Nhưng symfony sẽ làm nhiều việc thay chúng ta, chúng ta sẽ có một module với đầy đủ các chức năng mà không cần phải viết nhiều code PHP.

Relational Model

Như đã đề cập hôm trước, ứng dụng của chúng ta có các đối tượng chính: jobs, affiliates, và categories. Đây là lược đồ quan hệ giữa chúng:

Entity relationship diagram

Ngoài các cột như đã mô tả, chúng ta có thêm trường created_at trong một số bảng. Symfony ghi nhận những trường này và tự động gán giá trị thời gian hiện tại mỗi khi một bản ghi được tạo. Tương tự với trường updated_at, trường này sẽ được tự động cập nhật mỗi khi cập nhật một bản ghi.

Schema

Để chứa jobs, affiliates, và categories, chúng ta cần một cơ sở dữ liệu quan hệ.

Nhưng symfony là một framework hướng đối tượng, chúng ta muốn thao tác với đối tượng bất cứ khi nào có thể. Ví dụ, thay vì viết câu lệnh SQL để nhận một bản ghi từ cơ sở dữ liệu, ta muốn sử dụng objects.

Thông tin về relational database phải được chuyển thành một object model. Điều đó có thể thực hiện với một ORM tool, symfony cung cấp sẵn 2 công cụ: PropelDoctrine. Trong hướng dẫn này, chúng ta sẽ sử dụng Propel.

ORM cần thông tin mô tả các bảng và quan hệ giữa chúng để tạo class tương ứng. Có hai cách để tạo một schema mô tả: từ một cơ sở dữ liệu có sẵn hoặc tự tạo nó.

note

Một vài công cụ cho phép bạn tạo database ở chế độ đồ họa (ví dụ Fabforce's Dbdesigner) và sinh ra trực tiếp file schema.xml (với DB Designer 4 TO Propel Schema Converter, bạn có thể chuyển thành propel schema).

Cơ sở dữ liệu chưa tồn tại và chúng ta muốn cơ sở dữ liệu Jobeet là agnostic, do đó chúng ta tự tạo file schema 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

Nếu bạn tạo bảng bằng cách viết lệnh SQL, bạn có thể tạo ra file schema.yml tương ứng bằng lệnh propel:build-schema.Lệnh này sẽ chuyển đổi trực tiếp từ lược đồ quan hệ sang YAML format.

sidebar

YAML Format

theo website YAML, YAML là "là tập các dữ liệu chuẩn đối với mọi ngôn ngữ lập trình, dễ hiểu đối với con người"

Nói cách khác, YAML là một ngôn ngữ đơn giản đề mô tả dữ liệu (strings, integers, dates, arrays, và hashes).

Trong YAML, cấu trúc được xác định thông qua dấu lùi dòng, cặp key/value được cách nhau bởi dấu hai chấm (:). YAML cũng có các kí hiệu để mô tả cấu trúc với ít dòng hơn, như arrays được xác định trong cặp [] và hashes với {}.

Nếu bạn chưa quen với cấu trúc của YAML, bạn sẽ quen dần khi sử dụng symfony framework bởi nó được dùng trong các file cấu hình.

File schema.yml chứa thông tin về tất cả các bảng và cột tương ứng. Mỗi cột được mô tả bao gồm:

  • type: kiểu dữ liệu (boolean, tinyint, smallint, integer, bigint, double, float, real, decimal, char, varchar(size), longvarchar, date, time, timestamp, blob, và clob)
  • required: true nếu cột đó là bắt buộc
  • index: true nếu bạn muốn index cho cột unique nếu bạn muốn unique index cho cột.

Với những cột được set ~ (id, created_at, và updated_at), symfony sẽ tự cấu hình phù hợp (khóa chính với id và timestamp với created_atupdated_at).

note

Thuộc tính onDelete xác định ứng xử khi ON DELETE của khóa ngoài, và Propel hỗ trợ CASCADE, SETNULL, và RESTRICT. Ví dụ, khi một job record bị xóa, tất cả các jobeet_category_affiliate record liên quan sẽ tự động được xóa theo bởi database hoặc bởi Propel nếu underlying engine không hỗ trợ chức năng này.

Database

Framework symfony hỗ trợ tất cả các sơ sở dữ liệu hỗ trợ PDO (MySQL, PostgreSQL, SQLite, Oracle, MSSQL, ...). PDO là database abstraction layer đi kèm trong PHP.

Chúng ta sử dụng MySQL trong tutorial này:

$ mysqladmin -uroot -pmYsEcret create jobeet

note

Bạn có thể thoải mái chọn bất kì database engine nào bạn muốn. Chúng ta sử dụng ORM nên việc sử dụng các database engine khác nhau không gây ra khó khăn!

Chúng ta cần khai báo với symfony cơ sở dữ liệu ta sử dụng cho Jobeet project:

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

Lệnh configure:database có 3 tham số: PDO DSN, tên, và mật khẩu truy cập cơ sở dữ liệu. Nếu password là rỗng, hãy bỏ qua tham số này

note

Lệnh configure:database chứa cấu hình của cơ sở dữ liệu vào file config/databases.yml. Bạn có thể sửa trực tiếp file này thay vì sử dụng lệnh.

ORM

Nhờ những mô tả về cơ sở dữ liệu trong file schema.yml , chúng ta có thể sử dụng của Propel để sinh các câu SQL cần thiết để tạo các bảng trong cơ sở dữ liệu:

$ php symfony propel:build-sql

Lệnh propel:build-sql sinh ra các câu lệnh SQL nằm trong thư mục data/sql:

# snippet from data/sql/lib.model.schema.sql
CREATE TABLE `jobeet_category`
(
        `id` INTEGER  NOT NULL AUTO_INCREMENT,
        `name` VARCHAR(255)  NOT NULL,
        PRIMARY KEY (`id`),
        UNIQUE KEY `jobeet_category_U_1` (`name`)
)Type=InnoDB;

Để tạo các bảng trong cơ sở dữ liệu, chúng ta chạy lệnh propel:insert-sql:

$ php symfony propel:insert-sql

Do lệnh này sẽ xóa các bảng hiện tại trước khi tạo bảng mới, nên bạn được yêu cầu xác nhận hành động này. Bạn có thể thêm option --no-confirmation để bỏ qua việc xác nhận, việc này sẽ hữu ích khi bạn cần chạy lệnh một cách tự động:

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

tip

Như bất kì công cụ dòng lệnh nào, các lệnh symfony cũng có một vài tham số và lựa chọn. Có thể dùng help để xem chi tiết các lệnh:

$ php symfony help propel:insert-sql

help sẽ hiện danh sách các tham số và lựa chọn, các giá trị mặc định, và một vài ví dụ sử dụng.

ORM tạo các PHP classes tương ứng từ các table records sang objects:

$ php symfony propel:build-model

Lệnh propel:build-model tạo các file PHP trong thư mục lib/model dùng để tương tác với database.

Khi vào thư mục này, bạn có thể thấy rằng Propel tạo 4 class cho một table. Ví dụ với bảng jobeet_job:

  • JobeetJob: mỗi object của class này tương đương với một record của bảng jobeet_job. Ban đầu, class chưa có gì.
  • BaseJobeetJob: Lớp cha của JobeetJob. Mỗi khi bạn chạy lệnh propel:build-model, lớp này sẽ bị thay đổi, vì thế các thao tác bạn phải để trong lớp JobeetJob.

  • JobeetJobPeer: Lớp này chứa các static methods trả về tập các JobeetJob object. Ban đầu, class chưa có gì.

  • BaseJobeetJobPeer: Lớp cha của JobeetJobPeer. Mỗi khi bạn chạy lệnh propel:build-model, lớp này sẽ bị thay thế, vì thế mọi chỉnh sửa cần để trong lớp JobeetJobPeer.

Giá trị của các cột có thể được truy cập thông qua các phương thức get*()set*():

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

Bạn có thể trực tiếp xác định khóa ngoài bằng cách link đến objects đó:

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

Lệnh propel:build-all bao gồm các thao tác mà chúng ta đã làm. Ngoài ra, nó còn tạo ra các forms và validators cho Jobeet model classes:

$ php symfony propel:build-all

Validators in action được đề cập ở cuối ngày hôm nay và forms được miêu tả chi tiết trong ngày 10.

tip

Lệnh propel:build-all-load bao gồm lệnh propel:build-allpropel:data-load task.

Như bạn sẽ thấy ở các phần sau, symfony tự động gọi các PHP classes, do đó bạn sẽ không bao giờ phải sử dụng require trong mã nguồn. Đó là một trong nhiều thứ mà symfony tự động làm cho lập trình viên, nhưng nó cũng có bất tiện: khi bạn thêm một class mới, bạn cần xóa symfony cache. Lệnh propel:build-model đã tạo ra rất nhiều classes mới, do đó chúng ta cần xóa cache:

 $ php symfony cache:clear

tip

Lệnh symfony bao gồm 1 namespace và tên thao tác. Các lệnh có thể viết tắt nếu không trùng với lệnh khác. Lệnh sau tương đương với cache:clear:

$ php symfony cc

Khởi tạo dữ liệu

Chúng ta đã tạo ra các bảng trong database nhưng chưa có dữ liệu. Bất kì một ứng dụng web nào đều có 3 kiểu dữ liệu:

  • Initial data: các dữ liệu cần thiết để ứng dụng làm việc. Ví dụ, Jobeet cần có một vài categories. Nếu không, người dùng sẽ không thể đăng tuyển dụng. Chúng ta cũng cần tạo một tài khoản admin để đăng nhập vào backend

  • Test data: dữ liệu test là cần thiết để test ứng dụng. Là developer, bạn cần viết test để chắc rằng ứng dụng hoạt động đúng như mô tả và cách tốt nhất là viết test tự động. Vì thế mỗi khi bạn chạy test, bạn cần một cơ sở dữ liệu với một vài dữ liệu để test.

  • User data: dữ liệu tạo bởi người dùng trong quá trình sử dụng ứng dụng.

Mỗi khi symfony tạo 1 bảng trong database, rất nhiều dữ liệu bị mất. Để tạo database với một vài dữ liệu khởi tạo, chúng ta có thể dùng PHP script, hoặc thực thi câu lệnh SQL. Nhưng có một cách tốt hơn trong symfony: tạo một file YAML trong thư mục data/fixtures/ và dùng lệnh 'propel:data-load` để load chúng vào database:

# 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:        job@example.com
    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:        job@example.com
    expires_at:   2010-10-10

note

job fixture file sử dụng 2 ảnh. Bạn có thể download chúng (/get/jobeet/sensio-labs.gif, /get/jobeet/extreme-sensio.gif) và đặt trong thư mục uploads/jobs/.

Một file fixtures được viết bằng YAML, với mỗi model object được gán một label duy nhất. Label được sử dụng để link đến object liên quan mà không cần dùng khóa chính (khóa này tự động tăng và ta không gán giá trị cho nó). Ví dụ, công việc job_sensio_labs có category là programming, là label của category 'Programming'.

Một file fixture có thể chứa object của 1 hoặc vài model.

tip

Chú ý đến số đằng trước ở tên file. Đó là một cách đơn giản để điều khiển thứ tự nạp dữ liệu. Sau này, nếu bạn cần thêm một vài dữ liệu mới, bạn sẽ dễ dàng thêm một số mới vào giữa những số đã có.

Trong file fixture, bạn không cần cung cấp giá trị tất cả các cột, symfony sẽ sử dụng các giá trị mặc định trong database schema. Giá trị các cột 'created_atupdated_at` sẽ được tự động thêm vào.

Load các dữ liệu khởi tạo vào database bằng lệnh propel:data-load:

$ php symfony propel:data-load

Xem một Action trên trình duyệt

Chúng ta đã sử dụng dòng lệnh rất nhiều nhưng nó không thực sự hứng thú, đặc biệt là với một dự án web. Bây giờ chúng ta đã có mọi thứ để tạo một trang Web tương tác với database.

Hãy xem cách hiển thị các công việc, chỉnh sửa một công việc, và xóa một công việc. Như đã nói trong ngày 1, một symfony project được tạo bởi các application. Mỗi application bao gồm nhiều modules. Một module là tập các mã nguồn PHP mô tả các tính năng của ứng dụng (như module API chẳng hạn), hay các thao tác của người dùng với một model object (như module job).

Symfony có thể tự động tạo module cho một model với các tính năng cơ bản:

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

Lệnh propel:generate-module tạo module job ở application frontend ứng với model JobeetJob. Như phần lớn các lệnh symfony khác, một vài file và thư mục được tạo trong thư mục apps/frontend/modules/job:

Thư mục Mô tả
actions/ chứa các action của module
templates/ chứa các template của module

file actions/actions.class.php chứa các action của module job:

Tên Action Mô tả
index hiển thị các records của table
show hiển thị các fields của 1 record
new hiển thị form để tạo một record mới
create tạo một record mới
edit hiển thị form để sửa một record
update cập nhật một record thông qua các giá trị người dùng chỉnh sửa
delete xóa một record khỏi table

Bây giờ bạn có thể test module job trên trình duyệt:

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

Job module

Nếu bạn thử sửa một công việc, bạn sẽ nhận được một exception vì symfony cần một đoạn text để mô tả mỗi category. Việc mô tả một PHP object có thể được xác định thông qua magic method __toString(). Đoạn text mô tả một category record được xác định trong JobeetCategory model class:

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

Bây giờ, mỗi khi symfony cần một đoạn text để mô tả một category, nó có thể gọi phương thức __toString() trả về tên của category. Do chúng ta sẽ cần một đoạn text để mô tả tất cả các model class, nên hãy viết các phương thức __toString() cho các model class còn lại:

// 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();
  }
}

Bây giờ, bạn có thể tạo và sửa một công việc. Thử để trống các trường bắt buộc, hoặc điền một giá trị không hợp lệ. Symfony đã tạo sẵn các validation rules cơ bản dựa vào database schema.

validation

Hẹn gặp lại ngày mai

Đó là tất cả những công việc của hôm nay. Như đã nói trong phần giới thiệu, chúng ta không phải viết PHP code nhưng chúng ta đã có một web module ứng với job model, sẵn sàng cho chúng ta chỉnh sửa.

Nếu bạn vẫn còn hứng thú, hãy đọc mã nguồn đã được tạo ra tự động và cố gắng hiểu cách làm việc của nó. Nếu không, bạn có thể đi ngủ, và ngày mai, chúng ta sẽ nói về một trong những mô hình phổ biến nhất của web framework MVC design pattern.

Giống như hôm qua, hôm nay mã nguồn được public lên kho chứa của Jobeet SVN . Checkout tag release_day_03:

$ svn co http://svn.jobeet.org/propel/tags/release_day_03/ jobeet/

This work is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported License license.