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.
Sử dụng sfDoctrinePlugin
Nếu bạn đang đọc hướng dẫn này, có nghĩa là bạn đã quyết định sử dụng
Doctrine ORM thay cho Propel. Đầu tiên bạn cần
enable sfDoctrinePlugin
và disable sfPropelPlugin
. Để thực hiện điều này, bạn cần thêm đoạn code sau
vào file config/ProjectConfiguration.class.php
.
public function setup() { $this->enablePlugins(array('sfDoctrinePlugin')); $this->disablePlugins(array('sfPropelPlugin')); }
Nếu bạn muốn tất cả các plugin đều được enabled , bạn có thể viết:
public function setup() { $this->enableAllPluginsExcept(array('sfPropelPlugin', 'sfCompat10Plugin')); }
note
Sau thay đổi này bạn sẽ gặp một lỗi khi chúng ta cấu hình file
config/databases.yml
và chúng ta sẽ sử dụng sfDoctrineDatabase
.
Xóa cache để thay đổi có hiệu lực.
$ php symfony cc
Như chúng ta sẽ thấy sau này, mỗi plugin có thể có chứa các assets (javascripts,
stylesheets, and images). Sau khi cài đặt hay enable một plugin, chúng ta cần cài đặt chúng
bằng task plugin:publish-assets
:
$ php symfony plugin:publish-assets
Chúng ta cũng cần xóa thư mục web/sfPropelPlugin
:
$ rm web/sfPropelPlugin
tip
Khi sử dụng Doctrine thay cho Propel bạn có thể xóa file
config/propel.ini
và config/schema.yml
.
$ rm config/propel.ini $ rm config/schema.yml
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 Doctrine.
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
sfDbDesignerPlugin,
bạn có thể convert thành file schema cho doctrine).
(note này do người dịch thêm vào
)
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/doctrine/schema.yml
:
tip
Bạn cần tự tạo thêm tư mục config/doctrine/
trong
project do nó chưa có sẵn:
$ mkdir config/doctrine
# config/doctrine/schema.yml --- JobeetCategory: actAs: { Timestampable: ~ } columns: name: { type: string(255), notnull: true, unique: true } JobeetJob: actAs: { Timestampable: ~ } columns: category_id: { type: integer, notnull: true } type: { type: string(255) } company: { type: string(255), notnull: true } logo: { type: string(255) } url: { type: string(255) } position: { type: string(255), notnull: true } location: { type: string(255), notnull: true } description: { type: string(4000), notnull: true } how_to_apply: { type: string(4000), notnull: true } token: { type: string(255), notnull: true, unique: true } is_public: { type: boolean, notnull: true, default: 1 } is_activated: { type: boolean, notnull: true, default: 0 } email: { type: string(255), notnull: true } expires_at: { type: timestamp, notnull: true } relations: JobeetCategory: { onDelete: CASCADE, local: category_id, foreign: id, foreignAlias: JobeetJobs } JobeetAffiliate: actAs: { Timestampable: ~ } columns: url: { type: string(255), notnull: true } email: { type: string(255), notnull: true, unique: true } token: { type: string(255), notnull: true } is_active: { type: boolean, notnull: true, default: 0 } relations: JobeetCategories: class: JobeetCategory refClass: JobeetCategoryAffiliate local: affiliate_id foreign: category_id foreignAlias: JobeetAffiliates JobeetCategoryAffiliate: columns: category_id: { type: integer, primary: true } affiliate_id: { type: integer, primary: true } relations: JobeetCategory: { onDelete: CASCADE, local: category_id, foreign: id } JobeetAffiliate: { onDelete: CASCADE, local: affiliate_id, foreign: id }
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 doctrine: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 của cột (boolean
,integer
,float
,decimal
,string
,array
,object
,blob
,clob
,timestamp
,time
,date
,enum
,gzip
)notnull
:true
nếu cột đó là bắt buộcunique
:true
nếu bạn muốn tạo một unique index cho cột.
note
Thuộc tính onDelete
xác định ứng xử khi ON DELETE
của khóa ngoài,
và Doctrine hỗ trợ CASCADE
, SET NULL
, và RESTRICT
. Ví dụ, khi một job
record bị xóa, tất cả các jobeet_category_affiliate
liên quan đến records
này sẽ tự động được xóa theo.
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:
Mặc định, config/databases.yml
chứa cấu hình cho propel.
Do chúng ta sử dụng Doctrine, nên chúng ta cần xóa file config/databases.yml
để có thể tạo lại
cho Doctrine.
$ rm config/databases.yml
Bây giờ, chạy lệnh dưới đây để tạo cấu hình tới database cho Doctrine:
$ php symfony configure:database --name=doctrine --class=sfDoctrineDatabase "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 Doctrine để sinh các câu SQL cần thiết để tạo các bảng trong cơ sở
dữ liệu:
Để tạo câu SQL, trước tiên bạn cầu build model từ file schema.
$ php symfony doctrine:build-model
Sau khi đã có model, bạn có thể tạo và insert SQL.
$ php symfony doctrine:build-sql
Lệnh doctrine:build-sql
sinh ra các câu lệnh SQL nằm trong thư mục data/sql
:
# snippet from data/sql/schema.sql CREATE TABLE jobeet_category (id BIGINT AUTO_INCREMENT, name VARCHAR(255) NOT NULL COMMENT 'test', created_at DATETIME, updated_at DATETIME, slug VARCHAR(255), UNIQUE INDEX sluggable_idx (slug), PRIMARY KEY(id)) ENGINE = INNODB;
Để tạo các bảng trong cơ sở dữ liệu, chúng ta chạy lệnh doctrine:insert-sql
:
$ php symfony doctrine:insert-sql
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 doctrine: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 doctrine:build-model
Lệnh doctrine: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 Doctrine tạo 3 classes cho 1 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 trong bảngjobeet_job
. Ban đầu, class chưa có gì.BaseJobeetJob
: parent class củaJobeetJob
. Mỗi khi bạn chạy lệnhdoctrine:build-model
, class này sẽ thay đổi, vì thế các thao tác thực hiện bạn phải để trong classJobeetJob
.JobeetJobTable
: class chứa các phương thức trả về tập cácJobeetJob
objects. Ban đầu, class chưa có gì.
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 doctrine: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 doctrine: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 doctrine:build-all-reload
bao gồm lệnh doctrine:build-all
và
doctrine: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 doctrine: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
'doctrine:data-load` để load chúng vào database:
# data/fixtures/categories.yml JobeetCategory: design: name: Design programming: name: Programming manager: name: Manager administrator: name: Administrator # data/fixtures/jobs.yml JobeetJob: job_sensio_labs: JobeetCategory: programming type: full-time company: Sensio Labs logo: /uploads/jobs/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: '2008-10-10' job_extreme_sensio: JobeetCategory: design type: part-time company: Extreme Sensio logo: /uploads/jobs/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: '2008-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.
note
Propel yêu cầu tên file fixtures cần có số ở trước để quyết định thứ tự file sẽ được load. Với Doctrine điều này là không cần thiết vì tất cả các file fixtures sẽ được load và lưu theo đúng thứ tự.
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 doctrine:data-load
:
$ php symfony doctrine: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 doctrine:generate-module --with-show --non-verbose-templates frontend job JobeetJob
Lệnh doctrine: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 thông tin của một công việc, bạn có thể thấy rằng Category id
hiện ra danh sách tên các category. Các tên này được trả về bởi phương thức
__toString()
.
Doctrine cung cấp sẵn phương thức __toString()
trả về tên của cột: title
,
name
, subject
, ... Nếu bạn muốn chỉnh sửa giá trị trả về bạn cần thêm
phương thức __toString()
như sau.
// 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/doctrine/tags/release_day_03/ jobeet/
This work is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported License license.