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:
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ụ: Propel và Doctrine. 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.
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ộcindex
:true
nếu bạn muốn index cho cộtunique
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_at
và updated_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ảngjobeet_job
. Ban đầu, class chưa có gì.BaseJobeetJob
: Lớp cha củaJobeetJob
. Mỗi khi bạn chạy lệnhpropel:build-model
, lớp này sẽ bị thay đổi, vì thế các thao tác bạn phải để trong lớpJobeetJob
.JobeetJobPeer
: Lớp này chứa các static methods trả về tập cácJobeetJob
object. Ban đầu, class chưa có gì.BaseJobeetJobPeer
: Lớp cha củaJobeetJobPeer
. Mỗi khi bạn chạy lệnhpropel:build-model
, lớp này sẽ bị thay thế, vì thế mọi chỉnh sửa cần để trong lớpJobeetJobPeer
.
Giá trị của các cột có thể được truy cập thông qua các phương thức get*()
và
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-all
và
propel: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_atvà
updated_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
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.
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.