Archive for the ‘Symfony’ Category

Symfony 筆記 (7): Object-Relational Mapping (ORM) with Propel

Monday, May 26th, 2008

總之,ORM 就是可以省下寫 database specific SQL 的麻煩的 abstraction 就是了。Symfony 用的是 Propel,是一個 PHP5 的 database framework,跟 ADOdb 是類似的東西,雖然兩者到底詳細差在哪我也不知道。不過總之 Symfony 的作者們決定用 Propel 來省下許多 code generation 和實作 ORM。

有幾個非常重要的檔案必須編輯好才能使用 Symfony 裡面的 propel 功能。

  1. myproject/config/databases.yml
  2. myproject/config/propel.ini
  3. myproject/config/schema.yml

老實說 propel.ini 和 schema.yml 為啥要有兩個檔案,我也不知道 (as of now)。總之兩個檔案都得把 database 的設定寫進去就是了。然後 schema.yml 才是整個 ORM 的重點,Symfony 就是讀這個檔案生出所有 database object class 的。習慣直接編輯 schema.yml 的話是會很方便,不過老實說我也還不習慣,所以都用 phpmyadmin 先把 database 生好然後再讓 Symfony 生出 schema.yml。

database.yml

all:
  propel:
    class:          sfPropelDatabase
    param:
      dsn:          mysql://username:password@localhost/database

propel.ini (上面)

propel.targetPackage       = lib.model
propel.packageObjectModel  = true
propel.project             = myproject
propel.database            = mysql
propel.database.createUrl  = mysql://username:password@localhost/
propel.database.url        = mysql://username:password@localhost/database

大致的生產順序如下:

  1. 產生 schema.yml (自己編輯或者讓 symfony 照著建好的 database 生)
    讓 symfony 照著 database 生出 schema.yml:
    symfony propel-build-schema
  2. 產生 ORM classes
    symfony propel-build-model

用這個 schema.yml 當範例

propel:
  blog_article:
    _attributes: { phpName: Article }
    id:
    title:       varchar(255)
    content:     longvarchar
    created_at:
  blog_comment:
    _attributes: { phpName: Comment }
    id:
    article_id:
    author:      varchar(255)
    content:     longvarchar
    created_at:

會生出 8 個檔案,4 個檔案在 lib/model/om/ 下面 (Base Class):

  1. BaseArticle.php
  2. BaseArticlePeer.php
  3. BaseComment.php
  4. BaseCommentPeer.php

然後另外 4 個檔案在 lib/model/ 下面 (Custom Class):

  1. Article.php
  2. ArticlePeer.php
  3. Comment.php
  4. CommentPeer.php

生在 lib/model/om/ 下面的檔案只要每次跑 propel-build-model 就會重新被蓋過,所以如果要自己修改 model 增加功能的話,就得修改 lib/model/ 下面的 class。在 Base class 裡面所有 field 的 accessor 都會被自動生出來,也就是說上面這個 yml 生出來的 model 可以這樣使用。

// 單純是示範怎麼用 primary key
$articles = ArticlePeer::retrieveByPks(array(123, 124, 125));
// 使用 setter/getter
$article = new Article();
$article->setTitle('My first article');
$article->setContent('This is my very first article.\n Hope you enjoy it!');
$article->save();
$title   = $article->getTitle();
$content = $article->getContent();

// 這才是 ORM 的精華
$comments = $article->getComments();

要如何 Query 比較複雜的判斷呢?答案是用 Criteria 這個 class。以下這段的功能:

$c = new Criteria();
$c->add(CommentPeer::AUTHOR, 'Steve');
$c->addJoin(CommentPeer::ARTICLE_ID, ArticlePeer::ID);
$c->add(ArticlePeer::CONTENT, '%enjoy%', Criteria::LIKE);
$c->addAscendingOrderByColumn(CommentPeer::CREATED_AT);
$comments = CommentPeer::doSelect($c);

其實就等於 SQL 下面的:

SELECT blog_comment.ID, blog_comment.ARTICLE_ID, blog_comment.AUTHOR,
       blog_comment.CONTENT, blog_comment.CREATED_AT
FROM   blog_comment, blog_article
WHERE  blog_comment.AUTHOR = 'Steve'
       AND blog_article.CONTENT LIKE '%enjoy%'
       AND blog_comment.ARTICLE_ID = blog_article.ID
ORDER BY blog_comment.CREATED_AT ASC

常用到的 condition 可以參考這個列表:

SQL Criteria
WHERE column = value ->add(column, value);
WHERE column <> value ->add(column, value, Criteria::NOT_EQUAL);
Other Comparison Operators
> , < Criteria::GREATER_THAN, Criteria::LESS_THAN
>=, <= Criteria::GREATER_EQUAL, Criteria::LESS_EQUAL
IS NULL, IS NOT NULL Criteria::ISNULL, Criteria::ISNOTNULL
LIKE, ILIKE Criteria::LIKE, Criteria::ILIKE
IN, NOT IN Criteria::IN, Criteria::NOT_IN
Other SQL Keywords
ORDER BY column ASC ->addAscendingOrderByColumn(column);
ORDER BY column DESC ->addDescendingOrderByColumn(column);
LIMIT limit ->setLimit(limit)
OFFSET offset ->setOffset(offset)
FROM table1, table2 WHERE table1.col1 = table2.col2 ->addJoin(col1, col2)
FROM table1 LEFT JOIN table2 ON table1.col1 = table2.col2 ->addJoin(col1, col2, Criteria::LEFT_JOIN)
FROM table1 RIGHT JOIN table2 ON table1.col1 = table2.col2 ->addJoin(col1, col2, Criteria::RIGHT_JOIN)

Symfony 筆記 (6): User Login & Access Restriction

Monday, May 26th, 2008

雖然說其實翻翻文件就知道要改哪裡,在這邊記錄下來也只是方便自己不用再回去翻。其實在 Symfony 裡面要實作 Access Restriction (存取限制?) 很簡單,真的只是改 2 個 yml 檔案而已:

  1. apps/myapp/modules/mymodule/config/security.yml
  2. apps/myapp/config/settings.yml

apps/myapp/modules/mymodule/config/security.yml 定義著每個 module 下面 action 的存取限制。如果 is_secure = on 的話那就必須要有被認證過 (在 action 下面被 $this->getUser()->setAuthenticated(true);) 才能存取,不然的話依照另一個檔案的設定會被導到預設的 login page。
如果有設定 credentials 的話那除了必須要 authenticated 以外還得有被 addCrendential 過才行。

read:
  is_secure:   off       # All users can request the read action

update:
  is_secure:   on        # The update action is only for authenticated users

delete:
  is_secure:   on        # Only for authenticated users
  credentials: admin     # With the admin credential

all:
  is_secure:  off        # off is the default value anyway

apps/myapp/config/settings.yml 可以設定預設的 login module 和 action

all:
  .actions:
    login_module:           default
    login_action:           login

    secure_module:          default
    secure_action:          secure

簡易的登入登出會是像這樣子

class myAccountActions extends sfActions
{
  public function executeLogin()
  {
    if ($this->getRequestParameter('login') == 'foobar')
    {
      $this->getUser()->setAuthenticated(true);
    }
  }

  public function executeLogout()
  {
    if ($this->getUser()->isAuthenticated())
    {
      $this->getUser()->setAuthenticated(false);
    }
  }
}

然後有關 Credential 的 Demo:

class myAccountActions extends sfActions
{
  public function executeDoThingsWithCredentials()
  {
    $user = $this->getUser();

    // Add one or more credentials
    $user->addCredential('foo');
    $user->addCredentials('foo', 'bar');

    // Check if the user has a credential
    echo $user->hasCredential('foo');                      =>   true

    // Check if the user has both credentials
    echo $user->hasCredential(array('foo', 'bar'));        =>   true

    // Check if the user has one of the credentials
    echo $user->hasCredential(array('foo', 'bar'), false); =>   true

    // Remove a credential
    $user->removeCredential('foo');
    echo $user->hasCredential('foo');                      =>   false

    // Remove all credentials (useful in the logout process)
    $user->clearCredentials();
    echo $user->hasCredential('bar');                      =>   false
  }
}

最後是在 template 裡面要知道 credential 也是透過 $sf_user 這個看過才知道的物件。

<ul>
  <li><?php echo link_to('section1', 'content/section1') ?></li>
  <li><?php echo link_to('section2', 'content/section2') ?></li>
  <?php if ($sf_user->hasCredential('section3')): ?>
  <li><?php echo link_to('section3', 'content/section3') ?></li>
  <?php endif; ?>
</ul>

Symfony 筆記 (5): Session & Cookie

Monday, May 26th, 2008

Symfony 裡面的 Session 是透過 sfUser 這個 class 來完成的,可以在 action 裡面透過 $this->getUser() 來取得。關於 cookie 本身的設定則是在 apps/myapp/config/factories.yml 下面。Session 的存活時間 (timeout) 則是在 apps/myapp/config/settings.yml 裡面。

class mymoduleActions extends sfActions
{
  public function executeFirstPage()
  {
    $nickname = $this->getRequestParameter('nickname');

    // Store data in the user session
    $this->getUser()->setAttribute('nickname', $nickname);
  }

  public function executeSecondPage()
  {
    // Retrieve data from the user session with a default value
    $nickname = $this->getUser()->getAttribute('nickname', 'Anonymous Coward');
  }
}

除掉 Session 裡面的資料:

class mymoduleActions extends sfActions
{
  public function executeRemoveNickname()
  {
    $this->getUser()->getAttributeHolder()->remove('nickname');
  }

  public function executeCleanup()
  {
    $this->getUser()->getAttributeHolder()->clear();
  }
}

然後在 Template 裡面則是用 $sf_use 這個預設的物件來存取,沒看 documentation 鬼才知道。

<p>
  Hello, <?php echo $sf_user->getAttribute('nickname') ?>
</p>

Symfony 筆記 (4): sfActions

Monday, May 26th, 2008

看到這段 Code 的時候真是有種喔原來有這些喔的感覺,趕快記錄下來。這些都是在 Action 裡面可以用的 method,重點是沒有查 API 誰會知道有這些可以用。[sfActions API]

class mymoduleActions extends sfActions
{
  public function executeIndex()
  {
    // Retrieving request parameters
    $password    = $this->getRequestParameter('password');

    // Retrieving controller information
    $moduleName  = $this->getModuleName();
    $actionName  = $this->getActionName();

    // Retrieving framework core objects
    $request     = $this->getRequest();
    $userSession = $this->getUser(); // 這個最不像
    $response    = $this->getResponse();
    $controller  = $this->getController();
    $context     = $this->getContext();

    // Setting action variables to pass information to the template
    $this->setVar('foo', 'bar');
    $this->foo = 'bar';            // Shorter version

  }
}

以上的 $this 如果在 Template 裡面要呼叫的話,記得使用 $sf_context 這個物件。
sfActions 的不同用法:

class mymoduleActions extends sfActions
{
  public function preExecute()
  {
    // The code inserted here is executed at the beginning of each action call
    ...
  }

  public function executeIndex()
  {
    ...
  }

  public function executeList()
  {
    ...
    $this->myCustomMethod();  // Methods of the action class are accessible
  }

  public function postExecute()
  {
    // The code inserted here is executed at the end of each action call
    ...
  }

  protected function myCustomMethod()
  {
    // You can also add your own methods, as long as they don't start with "execute"
    // In that case, it's better to declare them as protected or private
    ...
  }
}

File Upload 在 Symfony 下面非常簡單就解決,想想看如果指有用 php 寫處理個檔案上傳要寫幾行?

class mymoduleActions extends sfActions
{
  public function executeUpload()
  {
    if ($this->getRequest()->hasFiles())
    {
      foreach ($this->getRequest()->getFileNames() as $fileName)
      {
        $fileSize  = $this->getRequest()->getFileSize($fileName);
        $fileType  = $this->getRequest()->getFileType($fileName);
        $fileError = $this->getRequest()->hasFileError($fileName);
        $uploadDir = sfConfig::get('sf_upload_dir');
        $this->getRequest()->moveFile('file', $uploadDir.'/'.$fileName);
      }
    }
  }
}

Symfony 筆記 (3): MVC Demo

Sunday, May 25th, 2008

Model-view-controller (MVC) is an architectural pattern used in software engineering. Successful use of the pattern isolates business logic from user interface considerations, resulting in an application where it is easier to modify either the visual appearance of the application or the underlying business rules without affecting the other. In MVC, the Model represents the information (the data) of the application and the business rules used to manipulate the data, the View corresponds to elements of the user interface such as text, checkbox items, and so forth, and the Controller manages details involving the communication to the model of user actions such as keystrokes and mouse movements.


在 Symfony 裡面不用 MVC 來寫程式的話那就完全沒有意義。Model 是資料庫的 Layer,View 是 Template Layer,而 Controller 則是運算 Layer。雖然這些觀念不會太難,但是實際上要把舊有的程式分開還是得仔細想想。下面是書裡面的一段 Demo,算是很好的示範。

Flat PHP File
這種大概是最古老的寫作方式,把所有的程式邏輯與資料庫存取通通包在同一個檔案裡面,HTML 和 PHP 還有 SQL 通通混在一起不但不好修改,而且要維護也比較上多很難。

<?php

// Connecting, selecting database
$link = mysql_connect('localhost', 'myuser', 'mypassword');
mysql_select_db('blog_db', $link);

// Performing SQL query
$result = mysql_query('SELECT date, title FROM post', $link);

?>

<html>
  <head>
    <title>List of Posts</title>
  </head>
  <body>
   <h1>List of Posts</h1>
   <table>
     <tr><th>Date</th><th>Title</th></tr>
<?php
// Printing results in HTML
while ($row = mysql_fetch_array($result, MYSQL_ASSOC))
{
echo "\t<tr>\n";
printf("\t\t<td> %s </td>\n", $row['date']);
printf("\t\t<td> %s </td>\n", $row['title']);
echo "\t</tr>\n";
}
?>
    </table>
  </body>
</html>

<?php

// Closing connection
mysql_close($link);

?>

MVC - Model
Model 是 Database 的 Abstraction。這裡是一段小 demo,實際上 Symfony 使用的是 Propel,大部分的 accessor method 都會自動生出來。

<?php

function open_connection($host, $user, $password)
{
  return mysql_connect($host, $user, $password);
}

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

function query_database($query, $database, $link)
{
  mysql_select_db($database, $link);

  return mysql_query($query, $link);
}

function fetch_results($result)
{
  return mysql_fetch_array($result, MYSQL_ASSOC);
}

function getAllPosts()
{
  // Connecting to database
  $link = open_connection('localhost', 'myuser', 'mypassword');

  // Performing SQL query
  $result = query_database('SELECT date, title FROM post', 'blog_db', $link);

  // Filling up the array
  $posts = array();
  while ($row = fetch_results($result))
  {
     $posts[] = $row;
  }

  // Closing connection
  close_connection($link);

  return $posts;
}

?>

MVC - Controller
Controller 是邏輯的部份。這個 demo 需要用到的邏輯根本沒有,就只是 “從資料庫裡面找出所有 post” 而已,所以基本上只有一行。

<?php

// Requiring the model
require_once('model.php');

// Retrieving the list of posts
$posts = getAllPosts();

// Requiring the view
require('view.php');

?>

MVC - View (Template)
Symfony 的 view 大致上可以分為 Template 和 Layout,差別在 Layout 比 Template 大,或者說 Template 在 Layout 裡面。

<h1>List of Posts</h1>
<table>
<tr><th>Date</th><th>Title</th></tr>
<?php foreach ($posts as $post): ?>
  <tr>
    <td><?php echo $post['date'] ?></td>
    <td><?php echo $post['title'] ?></td>
  </tr>
<?php endforeach; ?>
</table>

MVC - View (Layout)

<?php 

$title = 'List of Posts';
$content = include('mytemplate.php');

?>
<html>
  <head>
    <title><?php echo $title ?></title>
  </head>
  <body>
    <?php echo $content ?>
  </body>
</html>

實際在 Symfony 裡面每個檔案所在的地方不同,雖然一開始有點混亂,但是學習後會發現其實 Symfony 把檔案分配的很好,就類似設計很好的作業系統或者Mudlib之類的感覺吧。

All Rights Reserved Copyright © 2008 Design by StyleShout and Clazh