symfony學習筆記2—純的PHP代碼和symfony的區別
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”頁面
博客列表頁面已經重新設計,代碼可以復用了?,F在添加一個展示博客的頁面,向這個頁面傳遞參數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 Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
$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 Symfony\Component\HttpFoundation\Response;
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 AppBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
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 Symfony\Component\HttpFoundation\Request;
$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.rzrgm.cn/tylerdonet/
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,如有問題,請微信聯系冬天里的一把火
浙公網安備 33010602011771號