Archive for May 26th, 2008

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);
      }
    }
  }
}
All Rights Reserved Copyright © 2008 Design by StyleShout and Clazh