symfony學習筆記2—純的PHP程式碼和symfony的區別

ndblog發表於2015-05-12

Symfony vs 純PHP
為啥symfony比普通的php檔案訪問要好?
這一章我們寫一個簡單的php檔案專案,然後組織它,你會發現為什麼web應用會發展到現在這個樣子。最後我們將學習symfony如何重用程式碼。

使用純PHP建立一個簡單部落格程式
這裡我們先使用純php(flat php我擦 ,怎麼翻譯呢,就是php檔案,但是誰不是php檔案呢?)建立一個部落格程式,先寫一個文章列表,這段程式碼很直接,但是很髒。
<?php
// index.php
$link = mysql_connect(`localhost`, `myuser`, `mypassword`);
mysql_select_db(`blog_db`, $link);

$result = mysql_query(`SELECT id, title FROM post`, $link);
?>

<!DOCTYPE html>
<html>
<head>
<title>List of Posts</title>
</head>
<body>
<h1>List of Posts</h1>
<ul>
<?php while ($row = mysql_fetch_assoc($result)): ?>
<li>
<a href=”/show.php?id=<?php echo $row[`id`] ?>”>
<?php echo $row[`title`] ?>
</a>
</li>
<?php endwhile ?>
</ul>
</body>
</html>

<?php
mysql_close($link);
?>

這個很簡單,也很好寫,但是隨著應用邏輯增多很難維護。這裡有一些問題:
1.沒有錯誤檢查,如果資料庫連線失敗怎麼辦
2.沒有組織,如果應用變大,邏輯增多,這個檔案將會變得很大,不可維護,從那裡驗證輸入,從那裡處理請求,最終寫成一團亂碼
3.程式碼不可重用,所有程式碼都放在一個檔案中,沒法重用

還有一個問題沒有提到,如何從資料庫中取資料,symfony使用Doctrine(一種ORM)來獲取資料很方便。

展現分離
下面做一些該進將邏輯和html展現分離,程式碼如下:
<?php
// index.php
$link = mysql_connect(`localhost`, `myuser`, `mypassword`);
mysql_select_db(`blog_db`, $link);

$result = mysql_query(`SELECT id, title FROM post`, $link);

$posts = array();
while ($row = mysql_fetch_assoc($result)) {
$posts[] = $row;
}

mysql_close($link);

// include the HTML presentation code
require `templates/list.php`;

現在把hmtl內容放在另外一個檔案中templates/list.php類似模板

<!DOCTYPE html>
<html>
<head>
<title>List of Posts</title>
</head>
<body>
<h1>List of Posts</h1>
<ul>
<?php foreach ($posts as $post): ?>
<li>
<a href=”/read?id=<?php echo $post[`id`] ?>”>
<?php echo $post[`title`] ?>
</a>
</li>
<?php endforeach ?>
</ul>
</body>
</html>

一般index.php那個可以叫做控制器,控制這個詞在很多場合都用到,不管任何語言和框架,它指處理使用者輸入和返回響應的地方。在這個例子中控制器從資料庫中查資料,然後包含了一個展現資料的檔案。這樣分離之後如果想修改展示資料的方式例如list.json.php就很容易了

業務邏輯分離
目前這個應用只包含一個頁面,但是如果增加第二個頁面也使用相同的資料連線,相同的傳遞資料,所以我們將主要的獲取資料的邏輯分離出來放在一個model.php中,如下:

<?php
// model.php
function open_database_connection()
{
$link = mysql_connect(`localhost`, `myuser`, `mypassword`);
mysql_select_db(`blog_db`, $link);

return $link;
}

function close_database_connection($link)
{
mysql_close($link);
}

function get_all_posts()
{
$link = open_database_connection();

$result = mysql_query(`SELECT id, title FROM post`, $link);
$posts = array();
while ($row = mysql_fetch_assoc($result)) {
$posts[] = $row;
}
close_database_connection($link);

return $posts;
}

我們把這個檔案命名為model.php是應為在應用中通常將獲取資料層叫做model,通常主要的業務邏輯放在model裡面。

現在這個控制器index.php可易寫成下面這樣:

<?php
require_once `model.php`;

$posts = get_all_posts();

require `templates/list.php`;

現在這個控制器主要的功能就是從model中獲取資料然後呼叫模板渲染資料,這是個很簡單的模型-檢視-控制器的例子

佈局分離
到目前為止我們已經涉及到三個不同的檔案,幾乎複用了所有的程式碼,只有一個地方我們沒有用到就是佈局檔案,下面建立一個佈局檔案layout.php

<!– templates/layout.php –>
<!DOCTYPE html>
<html>
<head>
<title><?php echo $title ?></title>
</head>
<body>
<?php echo $content ?>
</body>
</html>

模板templete/list.php,現在可以繼承佈局了。
<?php $title = `List of Posts` ?>

<?php ob_start() ?>
<h1>List of Posts</h1>
<ul>
<?php foreach ($posts as $post): ?>
<li>
<a href=”/read?id=<?php echo $post[`id`] ?>”>
<?php echo $post[`title`] ?>
</a>
</li>
<?php endforeach ?>
</ul>
<?php $content = ob_get_clean() ?>

<?php include `layout.php` ?>

現在可以通用這個layout了,但是還需要一些醜陋的php函式例如ob_start(),ob_get_clean()方法,下面再將symfony的處理方式。

新增部落格”show”頁面
部落格列表頁面已經重新設計,程式碼可以複用了。現在新增一個展示部落格的頁面,向這個頁面傳遞引數ID。首先在model.php中新建一個方法,如下:

// model.php
function get_post_by_id($id)
{
$link = open_database_connection();

$id = intval($id);
$query = `SELECT date, title, body FROM post WHERE id = `.$id;
$result = mysql_query($query);
$row = mysql_fetch_assoc($result);

close_database_connection($link);

return $row;
}

然後新建一個檔案show.php,控制器

<?php
require_once `model.php`;

$post = get_post_by_id($_GET[`id`]);

require `templates/show.php`;

最後新建一個模板templates/show.php,渲染檔案

<?php $title = $post[`title`] ?>

<?php ob_start() ?>
<h1><?php echo $post[`title`] ?></h1>

<div class=”date”><?php echo $post[`date`] ?></div>
<div class=”body”>
<?php echo $post[`body`] ?>
</div>
<?php $content = ob_get_clean() ?>

<?php include `layout.php` ?>

新建這個頁面的時候已經很容易,沒有重複程式碼,還是還是有些問題可能導致問題,例如丟失引數id將會使頁面報錯。如果這個導致404錯誤到還好,最壞的情況是sql注入。

另外一個問題是每個控制器檔案都必須應用model.php,當我們要訪問另外一個表,就要在這個控制器中新增另外一個model,這個是比較麻煩的。

前端控制器解決方案
解決這個問題的方法是前端控制器,通過這個控制器檔案所有的請求都可以被處理,唯一要做的是修改url,這樣更加靈活。

Without a front controller
/index.php => Blog post list page (index.php executed)
/show.php => Blog post show page (show.php executed)

With index.php as the front controller
/index.php => Blog post list page (index.php executed)
/index.php/show => Blog post show page (index.php executed)

用apache中的重寫功能可以省略“index.php”這樣的話訪問就更加簡單了,例如/show

使用前端控制器只有單獨的一個index.php檔案可以處理所有的請求,例如訪問show頁面,/index.php/show,將會最終執行index.php,這是一個很強大的功能。我怎麼沒看出來。

建立前端控制器
現在我們再往前走一大步,用一個檔案處理所有的請求,修改index.php檔案如下:

<?php
// index.php

// load and initialize any global libraries
require_once `model.php`;
require_once `controllers.php`;

// route the request internally
$uri = parse_url($_SERVER[`REQUEST_URI`], PHP_URL_PATH);
if (`/index.php` == $uri) {
list_action();
} elseif (`/index.php/show` == $uri && isset($_GET[`id`])) {
show_action($_GET[`id`]);
} else {
header(`Status: 404 Not Found`);
echo `<html><body><h1>Page Not Found</h1></body></html>`;
}

為了組織好程式碼控制器檔案(index.php,show.php)現在變成了方法並且放在一個檔案中,controllers.php如下:

function list_action()
{
$posts = get_all_posts();
require `templates/list.php`;
}

function show_action($id)
{
$post = get_post_by_id($id);
require `templates/show.php`;
}

作為一個前端控制器index.php中有新的規則,一是載入核心的類,可以呼叫到控制方法list_action()和show_action()。事實上這裡的前端路由已經和symfony的機制很接近了。

現在,這個這個應用已經從一個簡單的php檔案重構成一個有組織的結構,最大程度的複用程式碼,但是還是看到一些程式碼不協調。為了完成這個blog我們可能需要寫很多類似的程式碼,還要處理使用者輸入,驗證,日誌,安全等等。

初始symfony
牛逼哄哄的symfony出場了。(Symfony to the rescue)我想對手冊作者說,你能不能不裝逼,把要講的東西講清楚就好了!在使用之前我們要下載symfony,可以使用composer,這個工具可以下載正確版本的symfony和它所依賴的所有檔案,提供一個自動下載器,我擦你妹 ,autoloader是個可以一個工具,用來使用類但是不顯示的包含類檔案,我擦啊 你他媽還玩花啊,不引用就用,構高階的。

有沒有考慮windows使用者的感受你!

 

在根目錄下建立一個檔案composer.json,如下:

{
“require”: {
“symfony/symfony”: “2.6.*”
},
“autoload”: {
“files”: [“model.php”,”controllers.php”]
}
}

然後下載Composer並安裝,命令如下:

$ composer install

下載依賴檔案的時候Composer建立了一個檔案vendor/autoload.php,這個檔案中有所有symfony frameword中所有需要的檔案,就是composer.jeson中的。當初說好的symfony處理請求響應呢, 這裡幹嗎呢?

symfony提供Request和Response兩個類,他們處理請求和響應,這個不知道重複多少遍了!下面用symfony來寫這個blog

<?php
// index.php
require_once `vendor/autoload.php`;

use SymfonyComponentHttpFoundationRequest;
use SymfonyComponentHttpFoundationResponse;

$request = Request::createFromGlobals();

$uri = $request->getPathInfo();
if (`/` == $uri) {
$response = list_action();
} elseif (`/show` == $uri && $request->query->has(`id`)) {
$response = show_action($request->query->get(`id`));
} else {
$html = `<html><body><h1>Page Not Found</h1></body></html>`;
$response = new Response($html, Response::HTTP_NOT_FOUND);
}

// echo the headers and send the response
$response->send();

控制器現在返回一個response物件,可以新增一個render_template()方法,和symfony中的模板很像,如下:

// controllers.php
use SymfonyComponentHttpFoundationResponse;

function list_action()
{
$posts = get_all_posts();
$html = render_template(`templates/list.php`, array(`posts` => $posts));

return new Response($html);
}

function show_action($id)
{
$post = get_post_by_id($id);
$html = render_template(`templates/show.php`, array(`post` => $post));

return new Response($html);
}

// helper function to render templates
function render_template($path, array $args)
{
extract($args);
ob_start();
require $path;
$html = ob_get_clean();

return $html;
}

好吧, 通過使用symfony我們的程式更加靈活,又來,request提供訪問http請求的可靠方法,getPathInfo()方法返回一個乾淨的url,例如:/show,/index.php/show,這樣即使訪問index.php/show,應用程式可以聰明的呼叫show_action()方法。

返回響應的時候response物件可以靈活的返回結果。

symfony簡單示例
現在這個blog已經做好,但是裡面還有很多程式碼,但是有沒有辦法使用更好的程式碼來實現這個blog,有沒有辦法來代替ob_start(),和ob_get_clean()呢?可以使用symfony來簡化這些,如下:

// src/AppBundle/Controller/BlogController.php
namespace AppBundleController;

use SymfonyBundleFrameworkBundleControllerController;

class BlogController extends Controller
{
public function listAction()
{
$posts = $this->get(`doctrine`)
->getManager()
->createQuery(`SELECT p FROM AcmeBlogBundle:Post p`)
->execute();

return $this->render(`Blog/list.html.php`, array(`posts` => $posts));
}

public function showAction($id)
{
$post = $this->get(`doctrine`)
->getManager()
->getRepository(`AppBundle:Post`)
->find($id);

if (!$post) {
// cause the 404 page not found to be displayed
throw $this->createNotFoundException();
}

return $this->render(`Blog/show.html.php`, array(`post` => $post));
}
}

這兩個控制器依然很重要哦,用Doctrin ORM來從資料庫中來獲取資料,模板元件渲染模板並返回一個Response物件,模板現在看上去比較簡單,

<!– app/Resources/views/Blog/list.html.php –>
<?php $view->extend(`layout.html.php`) ?>

<?php $view[`slots`]->set(`title`, `List of Posts`) ?>

<h1>List of Posts</h1>
<ul>
<?php foreach ($posts as $post): ?>
<li>
<a href=”<?php echo $view[`router`]->generate(
`blog_show`,
array(`id` => $post->getId())
) ?>”>
<?php echo $post->getTitle() ?>
</a>
</li>
<?php endforeach ?>
</ul>

layout佈局是差不多的

<!– app/Resources/views/layout.html.php –>
<!DOCTYPE html>
<html>
<head>
<title><?php echo $view[`slots`]->output(
`title`,
`Default title`
) ?></title>
</head>
<body>
<?php echo $view[`slots`]->output(`_content`) ?>
</body>
</html>

媽蛋 $view從那裡來的,沒交代清除。

當symfony引擎啟動的時候,媽蛋啊 , 說的這麼高階 ,不就是訪問網站的時候麼。它需要通過請求資訊和一個對映表知道執行那一個控制器。路由配置提供這個資訊,如下:

# app/config/routing.yml
blog_list:
path: /blog
defaults: { _controller: AppBundle:Blog:list }

blog_show:
path: /blog/show/{id}
defaults: { _controller: AppBundle:Blog:show }

然後symfony處理一些簡單的任務,前端控制器是很簡單的,建立之後你就不需要再管它,說的輕巧,吃根燈草。還有如果使用symfony distribution根本不需要建立它,好吧,被你的裝逼精神深深的折服了!

// web/app.php
require_once __DIR__.`/../app/bootstrap.php`;
require_once __DIR__.`/../app/AppKernel.php`;

use SymfonyComponentHttpFoundationRequest;

$kernel = new AppKernel(`prod`, false);
$kernel->handle(Request::createFromGlobals())->send();

前端控制器的唯一任務就是初始化symfony引擎Kernel,然後傳送一個請求物件,symfony核心然後用這個路由對映找到執行那個控制器,最後控制器方法返回最終的響應物件。(編者一再強調很簡單)

symfony的優點
開啟裝逼模式
什麼是symfony framework,symfony framework是一個php類庫,包含兩個主要的任務
1.提供可選的第三方的類庫元件(symfony components)
2.提供直觀的配置和一個可以把很多php程式碼片段組合起來的膠水類庫

symfony的終極目標是整合很多互不影響的元件來為開發者提供一致的使用體驗。symfony本身也是一個束可以被配置和替換。symfony提供一套快速開發的工具而不需要在專案中新增額外的元件。普通使用者可以快速開發,高手可以任意馳騁。

 

作者:Tyler Ning

出處:http://www.cnblogs.com/tylerdonet/

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段宣告,且在文章頁面明顯位置給出原文連線,如有問題,可以通過以下郵箱地址williamningdong@gmail.com
 聯絡我,非常感謝。


相關文章