Bài viết này bàn về S trong SOLID principle.

Trong bài viết này http://longka.info/blog/2015/10/26/a-note-about-solid-principles-demo-in-symfony2/ , mình nói sơ qua về ý nghĩa của từng nguyên tắc trong SOLID và demo với symfony2.

S: Single Responsibility - tức là một class chỉ nên đảm nhận một nhiệm vụ, hoặc một class nếu có một lý do nào đó để thay đổi thì chỉ có một lý do (_ A class should have only one reason to change_ [1])

Để hiểu rõ hơn, mình sẽ phân tích một ví dụ nó vi phạm qui tắc S sờ này, và làm sao để refactor nó đáp ứng qui tắc S (sờ, không phải rờ mó lung tung nhé).

Giả sử ta có một class Student như sau:

<?php 

namespace Longka;

use PDO;

class Student
{
    /**
     * @var string
     */
    protected $name;

    /**
     * @var string studentId
     */
    protected $studentId;

    /**
     * @var string birthday
     */
    protected $birthday;

    public function __construct()
    {
        //echo "this is student constructor" . "\n";
        //assume that this studentId = 1
        $this->studentId = 1;
    }

    /**
     * Returns all courses that students enrolled
     * todo: This to prove broken single responsibility in solid principle
     * connect to database, handle connection , etc
     * @return array
     */
    public function getCourses() : array
    {
        $courses = [];

        //database connection info
        $dbHost = '127.0.0.1';
        $dbName = 'phptut';
        $dbUser = 'postgres';
        $dbPass = 'postgres';

        try {
            //new pdo object	
            $db = new PDO("pgsql:dbname=$dbName;host=$dbHost", $dbUser, $dbPass);
            $query = "SELECT course.id, course.name from enrollment, course where enrollment.course_id = course.id and " . 
                    "enrollment.student_id = " . $this->studentId;
            $result = $db->query($query);
            var_dump($result);
            foreach ($result as $row) {
                $courses[] = [
                        'id' => $row['id'],
                        'name' => $row['name'],
                ];
            }
        } catch (Exception $e) {
            throw $e;
        }
        
        return $courses;
    }
}

Trong ví dụ về class Student ở trên, bản thân class Student không cần phải quan tâm đến việc kết nối cơ sở dữ liệu thế nào, nó chỉ cần quan tâm đến các thông tin liên quan đến Student, những khóa học mà sinh viên đã đăng ký, hoặc đã học ( bảng enrollment ). Một lý do khác nữa, đó là giả sử sau này khi ta muốn thay đổi thông tin liên quan cơ sở dữ liệu (câu truy vấn, hệ quản trị csdl) ta cũng phải chạy vào class này chỉnh sửa. Do đó chúng ta cần phải re-factor để khớp với qui tắc Sờ (Lờ) như sau:

<?php

namespace LongKa;

use PDO;
use PDOException;

class Database
{
    /**
     * @var array $dbInfo
     */		
    protected $dbInfo;

    public function __construct()
    {
        $this->dbInfo = [
            'host' => '127.0.0.1',
            'db-name' => 'phptut',
            'user' => 'postgres',
            'pass' => 'postgres',	
        ];			
    }

    /**
     * Get postgres database connection
     *
     * @return PDO
     * @throws PDOException 
     */
    public function getPostgresDbConnection() : PDO
    {
        $db = null;
        $url= "pgsql:dbname=" . $this->dbInfo['db-name'] . ";host=" . $this->dbInfo['host'];	

        try {
            $db	= new PDO($url, $this->dbInfo['user'], $this->dbInfo['pass']);
        } catch (PDOException $e) {
            throw $e;			
        }

        return $db;
    }
}		

Soure code on github https://github.com/nguyentienlong/solid-tut/blob/s-principle/src/Database.php#L8

Lúc đó, bên Class Student, bạn không cần phải quan tâm đến thông tin của db, việc kết nối db thế nào, etc… Hàm getCourses trong class student bây giờ sẽ như thế này: https://github.com/nguyentienlong/solid-tut/blob/s-principle/src/Student.php#L38

 public function getCourses() : array
    {
        $courses = [];
        //database connection info
        //new pdo object	
        try {
            $db = new Database();
            $conn = $db->getPostgresDbConnection();
            //TODO: SHOULD MOVE THIS QUERY INTO Database class, this class should not fuking care about what query is ;)
            $query = "SELECT course.id, course.name from enrollment, course where enrollment.course_id = course.id and " .
                    "enrollment.student_id = " . $this->studentId;
            $result = $conn->query($query);
            foreach ($result as $row) {
                $courses[] = [
                        'id' => $row['id'],
                        'name' => $row['name'],
                ];
            }
        } catch (Exception $e) {
            throw $e;
        }
        
        return $courses;
    }

Tuy nhiên bản thân class **Student **cũng không nên quan tâm lắm đến việc sql truy vấn thế nào, với các hệ quản trị cơ sở dữ liệu khác nhau thì câu query lại khác nhau, khi muốn sửa code chúng ta lại fai vào class Student này để sửa, lý do này không liên quan đế trách nhiệm và bổn phận của class Student. Những thứ liên quan đến db hay move sang class Database.

Do đó, chúng ta refactor lại class Database cho phù hợp với nguyên tắc S sờ như sau

<?php

namespace LongKa;

use PDO;
use PDOException;

class Database
{
    /**
     * @var array $dbInfo
     */		
    protected $dbInfo;

    public function __construct()
    {
        $this->dbInfo = [
            'host' => '127.0.0.1',
            'db-name' => 'phptut',
            'user' => 'postgres',
            'pass' => 'postgres',	
        ];			
    }

    /**
     * Get postgres database connection
     *
     * @return PDO
     * @throws PDOException 
     */
    public function getPostgresDbConnection() : PDO
    {
        $db = null;
        $url= "pgsql:dbname=" . $this->dbInfo['db-name'] . ";host=" . $this->dbInfo['host'];	

        try {
            $db	= new PDO($url, $this->dbInfo['user'], $this->dbInfo['pass']);
        } catch (PDOException $e) {
            throw $e;			
        }

        return $db;
    }

    /**
     * Returns all courses that students enrolled
     *
     * @var int $studentId
     *
     * @return array
     * @throws Exception
     */ 
    public function getCourses($studentId) : array
    {
        $courses = [];	
        try {
            $db = new Database(); 	
            $conn = $this->getPostgresDbConnection();
            $query = "SELECT course.id, course.name from enrollment, course where enrollment.course_id = course.id and " .
                    "enrollment.student_id = " . $studentId;
            $result = $conn->query($query);
            foreach ($result as $row) {
                $courses[] = [
                    'id' => $row['id'],
                    'name' => $row['name'],
                ];
            }

        } catch (Exception $e) {
            throw $e;
        }
        return $courses;
    }			
}

Qua 2 lần refactor, chuẩn Sờ S ?

File database.sql https://github.com/nguyentienlong/solid-tut/blob/master/database.sql - mình dùng postgres sql làm demo.

References: [1] https://en.wikipedia.org/wiki/Single_responsibility_principle