Sunday, May 19, 2013

Dependency Injection

Định nghĩa

Theo wikipedia:
Dependency Injection is a software design pattern that allows removing hard-coded dependencies and making it possible to change them, whether at run-time or compile-time.
Tạm dịch:
Dependency Injection là một mẫu thiết kết phần mềm mà nó cho phép chúng ta loại bỏ các sự phụ thuộc của các đối tượng trong chương trình và dễ dàng thay đổi chúng tại thời điểm ứng được chạy hay biên dịch.

Đơn giản hóa khái niệm hay tổng quan về DI

Định nghĩa như vậy là quá phức tạp cho những ai lần đầu tiên làm quen với điều này. Do vậy chúng ta cần phác thảo qua một vài ví dụ cũng như lợi ích khi chương trình được thiết kế theo khái niệm (kiến trúc) này.
Nhắc đến phần mềm khi nói đến một phần mềm, hay một tính năng trong phầm mềm đó, hoặc là zoom lại gần hơn để nhìn thấy một đối tượng của một lớp (class) nào đó đều cần nạp Input để ra một Output nào đó. Như trong ZF1 để Application có thể run được nó cần phải gọi bootstrap, thật ra trong quá trình đó bao gồm là quá trình đọc file application.ini và khởi tạo resource cần thiết cho hệ thống.

Nhiệm vụ cơ bản nhất của Dependency Injection (DI) là làm giảm sự phụ thuộc của những thành phần khác nhau trong hệ thống. Các đối tượng giờ chỉ cần tập trung tới hành vi (behavior) của chúng mà không cần quan tâm đến việc thiết lập các thuộc tính của nó.

Do vậy giờ đây một tính năng có thể tách ra làm hai #task riêng biệt mà có thể phân chia cho 2 lập trình viên (Developer) khác nhau. Điều này rất lợi ích trong quá trình phát triển phần mềm. Vì các #task độc lập nhau và có thể phân nhỏ để chia cho nhiều người. Và như vậy khi có lỗi xảy ra thì cũng nhanh chóng biết được từ đâu, do đó chúng dễ dàng được debug hơn.

Ví dụ với PHP.

Nói như vậy là quá nhiều về lý thuyết. Chúng ta cần một ví dụ để làm rõ những điều trên.
Tham khảo qua một vòng các bài viết tiếng Việt khác, thấy chúng được các tác giả mô tả quá phức tạp với nhiều vấn đề khác nhau, khiến chúng ta không còn tập trung vào cái lõi của DI. Nhưng trước khi bạn vào ví dụ, bạn cần biết một điều thú vị sau:
Trên wiki khi nói về DI có đề cập về một tư tưởng dựa trên một câu nói nổi tiếng trong nền công nghiệp giải trí Hollywood: Don't call us, we'll call you. Cái này gọi là Hollywood Principle, tạm dịch là "Đừng gọi cho chúng tôi, chúng tôi sẽ gọi bạn".

Với cách làm truyền thống, chúng ta thử qua một ví dụ sau:

Chúng ta có một class gọi là Taxi, với hai biến khởi tạo chính với các thành phần như: id của nó, tài xế driver,giá tiền mỗi một km pricePerKm,và thuế giá trị gia tăng taxVAT. Có một phương thức là số tiền mà người dùng cần phải trả dựa trên quãng đường.

Code ngắn gọn nhất có thể mô tả như sau:

class Taxi{
    protected $id;
    protected $driver;
    protected $pricePerKm;
    protected $taxVAT;

    public function __construct($taxi_id, $config)
    {
        $this->id = $taxi_id;

        $diver     = UserModel::getDefaultDriverOfTaxi($this->id);
        $this->setDriver($driver);

        $this->setPricePerKm($config['price_per_km']);
        $this->setTaxVAT($config['tax_vat']);
    }
    public function setDriver(User $driver)
    {
        $this->driver = $driver; 
        return $this;
    }
    public function calFee($distance)
    {
        //@todo cal the fee here
        return $distance*$this->getPricePerKm()*(1+$this->getTaxVAT());
    }
}

Phân tích tổng quan về cách cài đặt trên

Với cách cài đặt (implement) như trên, chúng ta thấy có rất nhiều điều cần phải làm khi khởi tạo đối tượng Taxi. Như việc lấy ra tài xế driver, gán dữ liệu cho giá tiền taxi mỗi một km, thuế giá trị gia tăng. Điều này làm tăng sự phụ thuộc (highly coupled dependency) của lớp Taxi vào các lớp khác và vào các thiết lập (setting/configuration) khác ban đầu cho nó.

Bài toán tính giá tiền (output) dựa trên khoảng cách khoảng cách (input) bắt đầu từ việc thiết lập các config (input) như giá tiền taxi, thuế VAT, tài xế trở nên nhập nhằng trong một Concern (class). Điều này dẫn tới nó sẽ trở nên khó cho việc tách #task (input/output) riêng biệt, nếu như bạn phải chia việc cho 2 Developers để tối ưu resource.

Hơn nữa, việc thiết lập config ngay bên trong cùng một concern (Taxi class) sẽ dẫn đến phức tạp như, giả sử khi đó cái config không phải là array mà là một object khi đó sẽ là vấn đề như:
public function __construct($taxi_id, $config)
{
    /**...*/

    if( $config instanceOf StdClass)
    {
        $this->setPricePerKm($config->price);
        $this->setTaxVAT($config->tax);
    }
    else
    {
        $this->setPricePerKm($config['price_per_km']);
        $this->setTaxVAT($config['tax_vat']);
    }   
}
Việc không đồng nhất propery trong object và array cũng như các tham số truyền vào lẽ ra không thuộc phạm vi (concern) của class Taxi, nó chỉ cần biết các thuộc tính của chính nó: id, driver, pricePerKm, taxVAT là có thể làm việc được, mà không cần quan tâm đến việc từ đâu có các giá trị config như trên.
Do vậy chúng ta cần thiết kế lại việc set giá trị cho nó được độc lập với các thành phần khác.

Ví dụ tiếp theo về cải tiến chương trình theo DI:

class Taxi{

    public function __construct($id_taxi, Config $config)
    {
        $this->id = $taxi_id;

        $diver     = UserModel::getDefaultDriverOfTaxi($this->id);
        $this->setDriver($driver);

        $config->setConfigurationToTaxi($this);
    }
}

class Config{
    public function setConfigurationToTaxi(Taxi $taxi)
    {
        $taxi->setPricePerKm(
            $this->getConfigPrice()
        );
        $taxi->setTaxVAT(
            $this->getConfigVAT()
        );
    }
    public function getConfigPrice()
    {
        //Lấy giá trị giá tiền trên mỗi từ database, const...
    }
    public function getConfigVAT()
    {
        //Lấy giá trị thuế VAT từ database, const...
    }
}
Giờ đây, bạn có thể nhìn ra được một phần nào đó tách biệt giữa inputouput như đã đề cập. Đối tượng Config (input) sẽ thiết lập các giá trị ban đầu cho đối tượng của lớp Taxi. Lớp Taxi giờ đây chỉ quan tâm tới việc thực hiện tính toán logic trong hàm calFee mà không quan tâm đến việc tử đâu có được giá tiền và thuế. Đây chính là cốt lõi của Dependency Injection.

Nhưng như vậy là chưa đủ đẹp, bạn cần phải chuyển code của việc get $driver ra khỏi __construct của lớp Taxi. Đến đây chúng ta có thể nghĩ: vậy thì setDriver tại nơi nó gọi ra như trong Controller hay Model nào đó. Như vậy thì có thể đúng nhưng chưa đủ, thậm chí dẫn đến vi phạm nguyên tắc phạm nguyên tắc Don't repeat yourself , code sẽ được lặp đi lặp lại rất nhiều mỗi khi ai đó muốn sử dụng cái đối tượng đó.

Trong ngữ cảnh này tốt nhất là chúng ta nên có thêm một Container, hay một Service hay một Factory, tùy ngữ cảnh bạn muốn sử dụng. Ví dụ trong trường hợp này, chúng ta có một Service để lấy ra chiếc một chiếc Taxi như sau:
interface ITaxi{
    public function calFee($distance);
}

class Taxi implement ITaxi{
    public function __construct($id_taxi, Config $config)
    {
        $this->id = $taxi_id;
        $config->setConfigurationToTaxi($this);
    }
}

class Service{
    protected $arrTaxi;
    /**
     * @return ITaxi Trả về một Interface chỉ có phương thức tính tiền. 
     */
    public function getTaxi($taxi_id)
    {
        if( isset($this->arrTaxi[$taxi_id]) )
        {
            return $this->arrTaxi[$taxi_id];
        }
        $taxi  = new Taxi($taxi_id, new Config());
        $diver     = UserModel::getDefaultDriverOfTaxi($taxi_id);
        $taxi->setDriver($driver);
        return $this->arrTaxi[$taxi_id] = $taxi;
    }
}

Giải thích thêm về cách implement ở trên:

  1. Việc thêm Interface là ITaxi nhằm báo cho những lập trình viên khác khi sử dụng đối tượng thuộc lớp này, chỉ cần quan tâm đến phương thức tính tiền của nó.
  2. Đảm bảo rằng đối tượng sẽ chỉ khởi tạo một lần và được sử dụng cho các lần sau.
  3. Việc thiết lập này sẽ tránh đẩy phần việc đó cho những chỗ gọi ra nó, do đó cũng tránh được việc quên thiết lập giá trị ban đầu như driver.

Kết quả

Giờ đây rõ ràng là việc sử dụng class Taxi để tính tiền đã được dễ dàng hơn rất nhiều. Người dử dụng giờ đây chỉ cần gọi hàm:
$taxi  = Service::getTaxi($taxi_id); //An instance of ITaxi
$fee   = $taxi->calFee($distance = 10);

Lời kết

Việc tiếp cận khái niệm này dành cho PHP Developer là điều cần thiết, khi đó bạn sẽ dễ dàng hơn trong việc tiếp cận các Framework thế hệ tiếp theo như ZF2. Cũng như nâng cao lý luận và khái niệm của bạn trong việc kiến trúc ứng dụng và quản lý resource dễ dàng.

Tham khảo:

  1. What is dependency injection? - Stackoverflow
  2. What is Dependency Injection? - Fabien Potencier

Friday, May 10, 2013

Hướng dẫn viết chức năng đăng nhập với Zend Authentication

 

Lời nói đầu

Chức năng đăng nhập cơ bản nhất như hình trên là người sử dụng nhập thông tin của mình bao gồm: username, password và cho phép remember hay không?

Và cách làm thường gặp nhất là thiết kế cơ sở dữ liệu có tableuser với username, password, thực hiện query để kiểm tra sự tồn tại trong cơ sở dữ liệu? Sau đó thì lưu trữ thông tin đăng nhập của user vào session.

Trong bài viết này, sẽ giúp bạn làm lại điều này nhưng với ZF2.

Vấn đề đăng nhập với các tài khoản khác như: Google, Yahoo, Facebook...

Một ngày nào đó , bạn nhận được yêu cần, cần phải mở rộng chương trình cho phép người sử dụng có thể đăng nhập bằng tài khoản khác như: Google, Facebook, Yahoo...
Và tất cả sẽ được trình bày trong bài viết này thông qua cơ chế Authentication được cung cấp bởi ZF2.

Mô tả về bảng user

+----------+--------------+------+-----+---------+----------------+
| Field    | Type         | Null | Key | Default | Extra          |
+----------+--------------+------+-----+---------+----------------+
| id       | int(11)      | NO   | PRI | NULL    | auto_increment |
| name     | varchar(255) | YES  |     | NULL    |                |
| username | varchar(32)  | YES  |     | NULL    |                |
| password | varchar(32)  | YES  |     | NULL    |                |
+----------+--------------+------+-----+---------+----------------+

Đăng kí authentication service vào Service Manager

'Zend\Authentication\AuthenticationService' => function($sm){
    $dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');

    $dbTableAuthAdapter = new \Zend\Authentication\Adapter\DbTable(
        $dbAdapter,        #database adapter
        'user',         #table name
        'username',     #identiy column
        'password',     #credential column
        'MD5(?)'        #credential treatment
    );

    $authService = new \Zend\Authentication\AuthenticationService();
    $authService->setAdapter($dbTableAuthAdapter);
    $authService->setStorage(
        new \Zend\Authentication\Storage\Session('zf2')
    );

    return $authService;
}
Rõ ràng các làm này không có gì khác với những khái niệm truyền thống mà bạn đã từng làm trước đó. Chỉ có khác là cách code khác đi thôi.

Xử lý việc Action login trong Controller

/**
 *
 * @property Zend\Http\Request $request
 * @method Zend\Http\Request getRequest()
 */
class LoginController extends AbstractActionController{

    public function loginAction() {

        if( isset($_POST['username']) && isset($_POST['password']) )
        {
            $this->getAuthAdapter()
                ->setIdentity($_POST['username'])
                ->setCredential($_POST['password']);
            $result = $this->getAuthService()->authenticate();
            if( $result->isValid() )
            {
                #It login ok
                $this->getAuthService()
                    ->getStorage()
                    ->write($result->getIdentity());
            }
        }
    }

    /**
     * 
     * @return \Zend\Authentication\AuthenticationService
     */
    public function getAuthService() {

        return $this->getServiceLocator()
            ->get('Zend\Authentication\AuthenticationService');

    }

    /**
     * 
     * @return \Zend\Authentication\Adapter\DbTable
     */
    public function getAuthAdapter()
    {
        return $this->getAuthService()
                ->getAdapter();
    }
}

Sử dụng lại thông tin Authentication trong Controller

Bạn cần phải kiểm tra xem một user đã đăng nhập hay chưa? Điều này thông thường bạn sẽ phải kiểm tra trong $_SESSION. Và như đã viết ở phần đầu, khi bạn sử dụng Zend Authentication, bạn cần phải khai báo Storage cho nó, có nhiều loại khác nhau, nhưng cái mà chúng ta sử dụng ở đây là Session.
Và với ZF2, bạn không cần gọi lại Authentication Service phức tạp như lúc xử lý login, mà bạn chỉ cần gọi helper: indentity:
public function loginAction() {

    if( $this->identity() )
    {
        #Redirect to another action
    }
    if( isset($_POST['username']) && isset($_POST['password']) )
    {
        $this->getAuthAdapter()
            ->setIdentity($_POST['username'])
            ->setCredential($_POST['password']);
        $result = $this->getAuthService()->authenticate();
        if( $result->isValid() )
        {
            #It login ok
            $this->getAuthService()
                ->getStorage()
                ->write($result->getIdentity());
        }
    }
}

Sử dụng lại thông tin authentication trong View

Trong file view bạn cũng chỉ cần gọi helper identity nữa là được.

Đăng nhập với Facebook, Google v.v...

Như bạn thấy ở trên, Authentication Service cần setAdapter, và với Adapter là table: user thì cách code như ở trên. Còn nếu đăng nhập với Facebook, Google bạn sẽ phải viết FacebookAdapter, GoogleAdapter. Nhưng một bài viết như thế này thì quá dài. Hi vọng bạn còn mong đợi tìm hiểu ở bài tiếp theo.

Friday, March 29, 2013

Xây dựng ứng dụng chat - P2: Giới thiệu Node.js



Trong loạt bài xây dựng một ứng dụng chat sử dụng MongoDb, Nodejs để tận dụng các công nghệ hỗ trợ phù hợp và hiểu quả.

Và bài viết này là phần tiếp theo giới thiệu về Nodejs.

Nodejs là gì? Chắc hẳn lập chúng ta cũng một đôi lần nghe hoặc đọc đâu đó về nó, thậm chí đơn khi đọc qua bài này, có người đã kịp search Google. Có người thì cũng thử qua với ví dụ từ document của nó. Và bài viết này, chúng tôi chỉ nằm đưa ra cái nhìn nhanh nhất, cùng với cách tiếp cận so với PHP, thông qua một giả định như sau:

Xây dựng một chương trình đếm số lượng truy cập (pageview) đơn giản, mỗi lần người dùng bấm F5 sẽ tăng lên 1 đơn vị bằng PHP và Nodejs như bảng so sánh bên dưới:

 NodeJS PHP
Code
var view_number = 0;
http.createServer(function (req, res) {
   view_number++;
   res.end(view_number.toString());
}).listen(80, '127.0.0.1');
$view_number = 
@file_get_contents('view_number.txt');
$view_number = $view_number + 1;
@file_put_contents('view_number.txt', $view_number);
echo $view_number;
 Execution
Run trực tiếp qua command: node counter.js, rồi truy xuất bằng browser: http://localhost/
 Với Web server như Apache, rồi truy xuất bằng cách http://localhost/counter.php
 Lưu trữ  Lưu qua biến view_number  Lưu xuống file view_number.txt



Sự khác biệt đầu tiên mà bạn có thể nhận ra được là: với PHP chúng ta phải lưu cái biến đếm số lượng truy cập trong file text (view_number.txt), còn với Nodejs thì không.

Và ngay lập tức sẽ có ai đó phản đối rằng, thì Nodejs nó lưu trên RAM thôi, giống như phần mềm Desktop, vậy thì nếu có sự cố như tắt chương trình/khởi động lại máy biến đó sẽ bị mất. Do vậy thì PHP dù sao vẫn tốt hơn.

Thật ra đây là lỗi của bài viết. Vì mục đích làm thế nào để nhanh nhất giúp độc giả của butchiso tiếp cận được sự khác nhau cơ bản giữa Nodejs và PHP, chúng tôi buộc phải dùng cách tiếp cận này.

Giải quyết vấn đề với Nodejs khi server bị crash 

Để làm rõ điều này, chúng tôi tiếp theo đây sẽ phải cải tiến chương trình của mình để giải quyết vấn đề mà độc giả thắc mắc. Bởi vì bạn đúng, dữ liệu không thể được lưu trữ trên RAM, nó cần phải lưu trữ xuống ổ cứng vật lý (bằng file hoặc db). Và điều mà có thể bạn chưa biết, thì ngày cả việc lưu trữ xuống file, thì với Nodejs, bạn vẫn có thể tạo sự khác biệt tạo nên tốc độ đáp ứng cho người dùng.

Một cách cải tiến theo chúng tôi có thể được làm như sau:

Chỉ ghi xuống file sau mỗi lần tăng biến view, và chỉ đọc lên lần đầu tiên khi mà biến view chưa được gán giá trị (do chương trình được chạy lại mỗi lần khởi động lại server chẳng hạn, hay restart lại chương trình). Ưu điểm trong giải pháp này là: chúng ta chỉ tốn động tác ghi file, mà không cần phải đọc file.

        -Biến view_number ban đầu được khởi tạo là NULL,
        -Nếu NULL thì đọc lên từ file (NULL nguyên nhân do server được khởi động lại)
            -Nếu có file thì lấy giá trị của nó bỏ vào biến view (như lần cuối cùng)
            -Nếu không thì gán giá trị bằng 0
        -Tăng giá trị của biến lên 1 đơn vị
        -Gởi về cho client/browser
            -Ghi giá trị đó xuống file
    

    Code được cải tiến lại theo hướng trên:

    var http = require('http');
    var fs = require('fs');
    var view_number = -1;
    http.createServer(function (req, res) {
    
       if( view_number === -1 ){
           data = fs.readFileSync('view_number.txt');
           view_number = parseInt(data);
       }
    
       view_number++;
       res.end(view_number.toString());
       fs.writeFile("view_number.txt", view_number);
    }).listen(80, '127.0.0.1');
    

    Xây dựng ứng dụng chat - P1: Giới thiệu MongoDB



    Đây là một trong những bài viết trong loạt bài tìm hiểu xây dựng ứng dụng chat online bằng Node.js và MongoDB. Chúng tôi sẽ từng bước một tiếp cận các công nghệ liên quan trước khi đi vào bài viết hướng dẫn cụ thể.

    Thường khi nói về database mọi người sẽ liên tưởng tới RDBMS - cơ sở dữ liệu quan hệ như MS SQL hay MySQL. Bởi vì một số lý do (như khó đảm bảo tính ACID khi dữ liệu nằm rải rác trên nhiều máy vật lý), các hệ cơ sở dữ liệu quan hệ thường khó chạy phân tán vì database phải chắn rằng các dữ liệu liên quan không bị sửa đổi hay xóa ngoài tầm kiểm soát trong một transaction. Điều này cũng dẫn tới các RDBMS khó scale up (khó chứ không phải không thể), cho nênthường người ta sẽ bổ sung thêm phần cứng thay vì cho nó chạy song song trên nhiều máy chủ.


    Khái niệm NoSQL được tạo ra vào năm 1998 bởi Carlo Strozzi. Nhiều người nghĩ rằng khái niệm này dùng để hạ thấp SQL nhưng thực ra nó có nghĩa là Not Only SQL. Về mặt ý tưởng thì cả hai (NoSQL và RDMS) sẽ sống chung và bổ sung cho nhau.

    NoSQL database được phân ra thành nhiều loại, bao gồm:
    • Key/values
      • Dynamo
      • Apache Cassandra
      • Voldemort
      • memcached
    • Tabular
      • BigTable
    • Document database
      • MongoDB
      • Apache CouchDB
    • Graph database
      •  Neo4j

    Nó được dùng khi nào?
    •     Lượng dữ liệu lớn (large data set)
    •     Chịu tải cao
    Nó không được dùng khi nào?
    •  Các ứng dụng cần sử dụng nhiều transaction (như ngân hàng)
    •  Các ứng dụng cần SQL (sử dụng joins)
    Bài viết này nhằm mang lại cho người đọc một cái nhìn tổng quan nhất về MongoDB một document-oriented database. MongoDB được rút ra từ “humongous”, có nghĩa là rất lớn. Nó được phát triển từ C++ bởi công ty 10gen

    Về cơ bản, mọi thứ trong MongoDb được lưu trữ với dạng document, cụ thể là json-style document (BSON). Và như vậy là nó sẽ không có schema, không có lưu trữ dữ liệu dạng bảng như  cơ sở dữ liệu quan hệ, và cũng không có joins.

    Dữ liệu được lưu trữ sẽ trông giống như thế này:
    {
      _id: 0,
      title: '',
      body: '',
      comments: [{
        person: '',
        comment: '',
        created_at: new Date()
      }],
      created_at: new Date()
    }
    


    Câu hỏi khiến nhiều người thắc mắc là làm thế nào để truy vấn NoSQL nếu như nó không hỗ trợ ngôn ngữ SQL. Mặc dù không phải là RDBMS nhưng MongoDB vẫn cung cấp các cơ chế để truy vấn dữ liệu. Cụ thể như sau:

    db.collection.find( <query>,  )
    
    

    Phương thức find() tương đương với câu SELECT trong SQL, và tương đương với mệnh đề WHERE, còn tương ứng với danh sách các fields ta cần truy vấn. 

    Ví dụ ta có một collection chứa các bộ phim, mỗi phần tử gồm có title và rating của khán giả.

    > db.movies.insert({title:"SHERLOCK BBC", rating:9.2})
    > db.movies.insert({title:"Elementary TV Series", rating:7.6})
    > db.movies.insert({title:"Lie to Me (2009-2011)", rating:7.8})
    
    /*SELECT * FROM movies WHERE title= 'Elementary TV Series'*/
    
    > db.movies.find({title:"Elementary TV Series"})
    {
       "_id" : ObjectId("51556737c79ef506ca8fc47d"),
       "title" : "Elementary TV Series",
       "rating" : 7.6
    }   
    
    /*SELECT * FROM movies WHERE rating>9*/
    > db.movies.find({rating:{$gt:9}})
    {
       "_id"         : ObjectId("515567f1c79ef506ca8fc47f"),
       "title"         : "SHERLOCK BBC",
       "rating"     : 9.2 }
    }
    

    _id là một filed được mongoDB tự động sinh ra với kiểu dữ liệu là ObjectId
    ObjectId là một giá trị BSON 12-byte được cấu thành từ:
    •     4-byte timestamp,
    •     3-byte machine identifier,
    •     2-byte process id
    •     3-byte counter, starting with a random value.


    Bạn có thể tham khảo list các operation của mongodb ở đây

    Những chức năng hay ho khác:
    • Map/reduce
      • Sẽ giới thiệu trong một bài viết khác
    • Capped collection
    Capped collections are fixed-size collections that support high-throughput operations that insert, retrieve, and delete documents based on insertion order. Capped collections work in a way similar to circular buffers: once a collection fills its allocated space, it makes room for new documents by overwriting the oldest documents in the collection.

    Đây là một fixed collection, tức là một tập hợp có số phần tử cố định khi khai báo. Ngoài ra nó đảm bảo lưu đúng thứ tự được chèn vào (natural order), mà không tự sắp xếp lại theo index hay bất kỳ yếu tố nào khác, đảm bảo thứ tự chèn vào và thứ tự lưu trên disk là giống nhau. Và khi số lượng phần tử trong collection đã chạm ngưỡng max thì khi chèn thêm một phần tử mới vào thì phần tử cũ nhất sẽ bị tống ra. Capped collection thích hợp để làm logging vì tốc độ nhanh do natural ordering (dữ liệu được lưu theo thứ tự nào thì lấy ra theo thứ tự đó không cần sort).

    db.createCollection("someCollection",{
       capped: true,
       size:100000,
       max:100
    
    })
    

    size là maximum size của capped collection tính bằng byte
    max là số phần tử tối đa trong capped collection đó
    • GridFS
    GridFS giúp lưu trữ những file có kích thước vượt quá giới hạn của BSON-document 16MB trên một document (định dạng lưu trữ trong mongodb BJON = “binary” + “JSON”). Nó chia một file ra nhiều phần nhỏ (parts hoặc chunks).

    Mỗi chunks được lưu trữ với dạng:
    {
    
     "_id" : ,
     "files_id" : ,
     "n" : ,
     "data" : 
    }
    
    Ví dụ:
    // returns default GridFS bucket (e.g. "fs"  collection)
    GridFS myFS = new GridFS(myDatabase); 
    // saves the file to "fs" GridFS bucket
    myFS.storeFile(new File("/tmp/largething.mpg"));
    
    • Geopartial Index
    Nếu bạn muốn tìm một địa điểm có tọa độ X,Y trên bản đồ có bao nhiêu nhà hàng nằm gần đó (giả sử bạn có danh sách các địa điểm lưu trong database) thì bạn sẽ cần tới Geopartial Index. Nếu bạn cần tính khoảng cách giữa 2 điểm trên bản đồ thì bạn cũng sẽ cần dùng nó. Giá trị của index được gọi là geohash được tính bằng cách liên tục chia 4 bản đồ không ngừng, mỗi một phần tư như vậy sẽ mang một giá trị 2-bit.

    MongoDB hỗ trợ các operation như:

    Tìm chính xác
    > db.places.find( { loc:[50,50]})
    
    Tìm địa điểm gần
    > db.places.find( { loc:{$near:[50,50]}})
    
    Tìm địa điểm trong phạm vi cho trước
    > db.places.find( { loc:{$near:[50,50], $maxDistance:5}})
    

    Một lưu ý nhỏ là bởi vì trái đất của chúng ta không phải là một cái dĩa dẹt nên ta phải dùng công thức lượng giác để tính. Tuy nhiên MongoDB cũng có hỗ trợ vấn đề này sẵn, nên chúng ta chỉ việc dùng như sau:
    db.runCommand({
        geoNear : "points" ,
        near : [0,0],
        spherical :  true
    })

    References:
    MongoDB document

    Saturday, March 23, 2013

    Tìm hiểu về PHP Reflection

    Trong khi định viết bài giới thiệu tiếp về ZF2, chúng tôi nhận thấy rằng cần phải giới thiệu cho các bạn về Reflection[1] trong PHP.

    Theo dõi ví dụ sau đây sẽ giúp bạn nhanh nhất tiếp cận về nó:

    1. Giả sử bạn một class: A với một method: protected b, với điều kiện không được sửa code, bạn làm thế nào để gọi được phương thức b.
    class A{
       protected function b(){
           echo 'Hello world!';
       }
    }

    Nếu cố tình gọi phương thức b trong class A như bên dưới bạn sẽ thấy lỗi:
    $a = new A();
    $a->b();
    

    Fatal error: Call to protected method A::b() from context '' on line 10


    Nhưng nếu bạn dùng ReflectionMethod thì bạn có thể gọi nó theo cách sau:
    $b = new ReflectionMethod('A', 'b');
    $b->setAccessible(true);
    $b->invoke(new A());
    

    Hello world!


    Ví dụ trên hơi thiếu thuyết phục đối với bạn, nhiều người cho rằng, chỉ cần mở source lên, sửa lại, không nhất thiết phải phức tạp như vậy. Do vậy chúng ta có thể đi qua một ví dụ thứ hai: 2. Bạn cần những hằng số của PHP sẽ được sử dụng lại trong Javascript, ví dụ như:
    interface IType{
       //...
       const ID_TYPE_TEXT      = 1000;
       const ID_TYPE_NUMBER    = 2000;
       const ID_TYPE_IMAGE     = 3000;
       const ID_TYPE_VIDEO     = 4000;
       //...
    }
    

    Bạn không thể ngồi code lại từng dòng, mặc dù chỉ với thao tác Ctrl+C và Ctrl+V, nhưng như vậy rất dễ dẫn tới thiếu sót, và nhầm lẫn cho người khác sau này tiếp tục copy-paste đoạn code này.Ngoài ra nó còn có thể dẫn tới sự thiếu nhất quan giữa các hằng số khi chương trình có thay đổi trong tương lai.

    //Javascript
    var ID_TYPE_TEXT      = 1000;
    var ID_TYPE_NUMBER    = 2000;
    var ID_TYPE_IMAGE     = 3000;
    var ID_TYPE_VIDEO     = 4000;
    

    Đó là lúc dành cho việc suy nghĩ xem, làm thế nào để thực hiện việc đồng bộ này, và chúng tôi có ngay một cách sử dụng Reflection:
    $r   = new ReflectionClass('IType');
    foreach( $r->getConstants() as $const => $value)
    {
         echo "var $const = $value;"
    }
    

    Ngắn gọn nhất thì có thể nói Reflection cung cấp khả năng phân tích cấu trúc bên trong một cái class[2] bao gồm các: method, property, const, comment và thay đổi (modify) chúng.

    3. Chúng còn giúp chúng ta lấy được các doc comment, như ví dụ bên dưới của class và method, property.
    */
    class A{
       protected function b()
       {
           return 'Hello world!';
       }
    }
    
    $a = new ReflectionClass('A');
    echo $a->getDocComment();
    
    
    Kết quả là:
    /**
    * @author butchiso
    */ 
    

    Và trong một bài viết sắp tới về Form trong ZF2, bạn sẽ hiểu điều này sẽ được dùng như thế nào, và các vấn đề của nó[3]

    Mọi góp ý bạn có thể để lại comment, hoặc gởi email cho chúng tôi. Chúc các bạn một cuối tuần vui vẻ.

    [1] Reflection cũng có trong ngôn ngữ khác chẳng hạn trong Java, C#.
    [2] PHP Reflection cung cấp cho ta đủ để phân tích class, và function. Nhưng để đơn giản cho bài viết nhóm tác giả butchiso chỉ phân tích quanh về class mà thôi.
    [3] Vấn đề đem ra phân tích chúng tôi định dựa theo bài viết: PHP Annotations Are a Horrible Idea

    Monday, March 18, 2013

    Tim hiểu về ZF2 - event-based programming

    (An introduction to Zend framework 2 - event-based programming)

    Điều gì sẽ không phải là nội dung của bài viết?

    Bài viết này sẽ không tập trung nói giải thích ZF2 là gì, và càng không phải nhằm để làm rõ khái niệm như thế nào là event-based programming.

    Khía tiếp cận của bài viết:
    Bài viết sẽ tập trung viết về một vấn đề cụ thể trong mô hình lập trình event-based programming. thông qua ví dụ sẽ được đề cập ngay sau đây để thu nhỏ phạm vi của bài viết. Qua đó, butchiso mong muốn cung cấp cho bạn đọc điều cơ bản nhất.

    Vấn đề giả sử của bài viết:
    • Giả sử bạn viết chức năng blog.
    • Database có hai table: blog và comment.
    • Trong table blog có hai column: tổng số lượng comment, tác giả bài viết.
    Với resource gồm có 3 Developer, được giao 3 Task khác nhau, vào 3 thời điểm khác nhau như sau:
    • Ban đầu dev A, được giao một task #1:
      • Mỗi khi có một comment được insert vào db thì tăng số lượng comment lên 1
    • Sau đó một tuần, dev B, được giao một task #2 là:
      • Mỗi khi có một comment được insert vào db thì thông báo đến tác giả của bài viết
    • Sau đó một tuần, dev C, được giao một task #3 là:
      • Mỗi khi có một comment được insert vào db thì kiểm tra xem có phải là spam hay không.
    Những giải pháp trong đầu bạn có thể nghĩ ra là:
    • Với task #1, bạn sử dụng trigger của database
    • Còn với task #2, và #3 bạn sẽ code

    Nhưng điều giả sử tiếp theo ở đây là, hệ quản trị cơ sở dữ liệu của bạn không hỗ trợ cái này (trigger). Và do đó là bạn phải code.

    Nhìn ở góc độ Zend Framework 1, các Dev sẽ làm gì?

    + Với task #1, dev A sẽ override cái phương thức postInsert() trong comment class:
    class Application_Model_DbTable_Comment extends Zend_Db_Table{
        protected function postInsert(){
            //@todo task 1 of @author Dev A
            $this->increaseTotalCommentOfBlog();
            parent::postInsert();
        }
    }
    
    + Với task #2, dev B sẽ tiếp tục chèn code vào cái đó để xử lý chức năng cần implement của mình:
    class Application_Model_DbTable_Comment extends Zend_Db_Table{
        protected function postInsert(){
            //@todo task 1 of @author Dev A
            $this->increaseTotalCommentOfBlog();
            //@todo task 2 of @author Dev B
            $this->notifyToAuthorOfBlog();
            parent::postInsert();
        }
    }
    
    + Với task #3, dev C sẽ tiếp tục chèn code vào để xử lý:
    class Application_Model_DbTable_Comment extends Zend_Db_Table
        protected function postInsert(){
            //@todo task 3 of @author Dev C
            //Viết rất nhiều inline code ở đây để kiêm
            //tra xem, có phải là spam hay không?
            //@todo task 1 of @author Dev A
            $this->increaseTotalCommentOfBlog();
            //@todo task 2 of @author Dev B
            $this->notifyToAuthorOfBlog();
            parent::postInsert();
        }
    
    
    }
    
    Nhưng sự không ổn ở đây là:
    • Dev C, làm task thứ 3, có thể gây ra lỗi gì đó có thể làm ảnh hưởng đến các task khác của các dev khác còn lại hay không? Không có gì để đảm bảo các dev sẽ code đúng như một process nào đó. Còn nếu như chúng ta lập luận, cái này buộc sẽ dùng rules, sẽ review code, thì cái này nằm ở tư duy giải quyết hậu quả, tốn kém thời gian... chứ không phải từ việc xây dựng nên một phương pháp và nền tảng đúng đắn từ đầu.
    • Thứ hai chúng ta phải override một phương thức nào đó chỉ để code cái điều của mình. Thậm chí trong một số ngữ cảnh để tránh conflict với người khác chúng ta phải extends class, rồi lại tiếp tục override phương thức.
    • Hơn nữa mỗi hàm để đảm bảo tính đúng đắng thì đều phải có khả năng UnitTest. Viết tất cả vào một chỗ e là khó thực hiện như UnitTest ở chỗ này.
    Một giải pháp khác:
    Như ở bài viết đề cập Anonymous function của PHP 5.3.x mà chúng tôi đã viết ở bài trước, chúng ta liên tưởng đến hình ảnh giống như Javascript. Nếu nhìn từ góc độ của của các khái niệm event, trigger, bind. Chúng ta có thể sẽ bind các function xử lý của mình khi xảy ra sự kiện postInsert.

    Task 1, Dev A:
    $comment_table->attachEvent('postInsert', function(Event $e){
         //Xử lý task 1 của @autho Dev A
    });
    

    Task 2, Dev B:
    $author = new Author($blog);
    $comment_table->attachEvent('postInsert', function(Event $e) use ($author) {
         //Xử lý task 2 của @autho Dev B
         $comment_table->notifyComment($author);
    });
    

    Tương tự cho task 3, Dev C, vì là chỉ là pseudo-code nên chúng tôi sẽ không tiếp tục viết mã giả thêm ở đây nữa.

    Kết luận:
    • Bản thân chúng ta sẽ không vội vàng so sánh ưu điểm giữa hai cách làm của ZF 1 vs. 2, và trong khuôn khổ của bài viết cũng không có từ ngữ nào là tác giả cố tình diễn đạt cách làm với ZF2(event-based programming) là tốt hơn so với của ZF1.
    • Ở đây, tác giả chỉ muốn cung cấp thêm khái niệm cho những lập trình viên đã có kinh nghiệm với Javascript, và nay cũng xuất hiện trong PHP được ứng dụng bởi Zend Framework 2, để có giải quyết vấn đề hẹp được đề cập như trên.
    • Riêng một thuận lợi mà với tư duy hướng sự kiện (event) mà dưới quan điểm cá nhân mà butchiso muốn gởi đến cho những ai đọc được bài viết này được áp dụng trong trường hợp requirement bị thay đổi. Ví dụ, ở task #3 của Dev 3 như sau:
      • Nếu như một comment detect là spam thì không được cần phải notify đến tác giả bài viết nữa.
      • Điều này, sẽ dẫn đến khó khăn cho bạn nếu tiếp cận với giải pháp truyền thống trên ZF1. Nay với ZF2, suy nghĩ như Javascript, bạn chỉ cần học cơ chế làm thế nào để stop-propagation các event của nó để giải quyết vấn đề này của bạn.


    P/S:Nếu các bạn thích học về Zend Framework 2 thì lên trang web của nó có hướng dẫn đầy đủ để tải về framework với những bước cơ bản để install và run.
    Và nếu như bạn đã có thể run được Zend Framework, và muốn cài đặt để thử nghiệm những gì được đề cập ở bài viết này, và nếu xảy ra vấn đề, bạn có thể liên hệ đến chúng tôi.

    Trần Phong Phú (vietean)

    Tuesday, March 12, 2013

    [PHP 5.3] Anonymous function

    Anonymous function (hay còn gọi là closures) được đưa vào từ phiên bản PHP 5.3.0. Nó cho phép tạo ra những functions không có tên ví dụ như sau:

     $greet = function($msg){
      echo "Hello, " . $msg;
     }; 
     $greet(" World");//outputs Hello, World
    

    Trong Python, hàm cũng được xem như là một object, nhưng trong PHP thì không. Tuy nhiên bản chất của anonymous function là một instance của class Cloures. Nên ta có thể gán cho một biến mới như sau:
     $greet = function($msg){
      echo "Hello, " . $msg;
     }; 
            $chao = $greet;
     $chao(" World");//outputs Hello, World
    

    Trong class Closures có định nghĩa magic function __invoke(), sẽ bị thay thế bởi cái hàm mà ta định nghĩa. Ngoài ra magic function này sẽ được gọi khi object Closures được gọi như một method.

    The __invoke() method is called when a script tries to call an object as a function.

    Closures còn giúp truyền anonymous function để gọi trong một function khác:
     function say($value, $callback){
      echo $callback($value);
     }
     say('Hung', function($name){
      return 'Hello, ' . $name;
     });
    

    Q.H 
    [1] 12-3-2013
    References:
    http://www.php.net/manual/en/language.oop5.magic.php#object.invoke
    http://php.net/manual/en/functions.anonymous.php
    http://www.slideshare.net/hipot/anonymous-functions-in-php-53-matthew-weier-ophinney

    Disqus for butchiso