設計模式_builder變種模式使用實例-SQL構造器

把一條基本的SQL語句(不包括子句)抽象成一個類,這裏的SQL類由select的`fields`, from的`table`, join`table`on`condition`, where的`condtion`, order by的`field desc, field asc`, limit `offset, length` 六部分組成(可支持擴展 `group by`\`having`\union等)

 代碼目錄如下:

/index.php     demo代碼

/autoload.php 加載類文件

/sour/Condition.php 條件類

/sour/HopeSQL.php SQL構造器實例

/sour/SQLCreator.php SQL構造器抽象類

/sour/StringHelper.php 字符串幫助類

 

/autoload.php

<?php

defined('PATH') or define('PATH', __DIR__);

function auto_loader($class)
{
    $path = str_replace(
        '\\',
        DIRECTORY_SEPARATOR,
        substr($class, strpos($class, '\\', 1)+1)
    );
    if(file_exists(PATH.DIRECTORY_SEPARATOR.$path.'.php')) {
        require PATH.DIRECTORY_SEPARATOR.$path.'.php';
    } else {
        throw new Exception(sprintf('not found `%s`', $class));
    }
    return class_exists($class);
}

 

/sour/Condition.php

<?php


namespace builder\sour;

use \Exception;

define('DEFAULT_RIGHT_TYPE', 0);
define('RIGHT_TYPE_INT', 0);
define('RIGHT_TYPE_STRING', 1);
define('RIGHT_TYPE_ARRAY_INT', 2);
define('RIGHT_TYPE_ARRAY_STRING', 3);
define('RIGHT_TYPE_COLUMN', 4);
define('RIGHT_TYPE_OBJ', 5);

define('OPERATOR_EQ', '=');
define('OPERATOR_NOT_EQ', '<>');
define('OPERATOR_LT', '>');
define('OPERATOR_RT', '<');
define('OPERATOR_LE', '<=');
define('OPERATOR_RE', '>=');
define('OPERATOR_IN', 'in');
define('OPERATOR_NOT_IN', 'not in');
define('OPERATOR_NOT_LIKE', 'like');


class Condition
{

    private $operator;

    private $left;

    private $right;

    private $rightType;

    public function __construct($left, $right, $operator = OPERATOR_EQ, $rightType = DEFAULT_RIGHT_TYPE)
    {
        $this->left = $left;
        $this->right = $right;
        $this->operator = $operator;
        $this->rightType = $rightType;
    }

    public function build()
    {
        if(false !== $this->rightType) {
            switch ($this->rightType) {
                case RIGHT_TYPE_INT:
                    $this->right = intval($this->right);
                    break;
                case RIGHT_TYPE_STRING:
                    $this->right = '\''.addslashes($this->right).'\'';
                    break;
                case RIGHT_TYPE_ARRAY_INT:
                    $this->right = implode(', ', $this->right);
                    break;
                case RIGHT_TYPE_ARRAY_STRING:
                    $this->right = '\''.implode('\', \'', $this->right).'\'';
                    break;
                case RIGHT_TYPE_OBJ:
                    return [
                        'field' => $this->right,
                        'operator' => $this->operator,
                        'obj' => $this->right
                    ];
                    break;
                case RIGHT_TYPE_COLUMN:
                    break;
                default:
                    throw new Exception('error right type');
            }
        }
        switch ($this->operator) {
            case OPERATOR_EQ:
            case OPERATOR_NOT_EQ:
            case OPERATOR_LT:
            case OPERATOR_RT:
            case OPERATOR_LE:
            case OPERATOR_RE:
            case OPERATOR_NOT_LIKE:
            case RIGHT_TYPE_COLUMN:
                $condition = implode(' ', [
                    $this->left,
                    $this->operator,
                    $this->right
                ]);
                break;
            case OPERATOR_IN:
            case OPERATOR_NOT_IN:
                $condition = implode(' ', [
                    $this->left,
                    $this->operator,
                    '('.$this->right.')'
                ]);
                break;
            default:
                $condition = '1 = 1';
        }
        return $condition;
    }


}

/sour/StringHelper.php

<?php


namespace builder\sour;


class StringHelper
{

    public static function joinField($alias, $field)
    {
        return $alias.'.'.$field;
    }

}

/sour/SQLCreator.php

<?php
namespace builder\sour;

define('SQL_FIELD', 'field');
define('SQL_TABLE', 'table');
define('SQL_WHERE', 'where');
define('SQL_LIMIT', 'limit');
define('SQL_ORDER_BY', 'order_by');
define('SQL_GROUP_BY', 'group_by');
define('SQL_HAVING', 'having');

abstract class SQLCreator
{
    public $table;      //表名

    public $alias;      //表別名

    public $params;     //參數

    public $lastSQL;

    public function __construct($table, $alias, $params)
    {
        $this->table = $table;
        $this->alias = $alias;
        $this->params = $params;
    }

    public abstract function build();       //根據規則生成SQL的方法

    public abstract function query($SQL);   //原生查詢方法

    public abstract function execute($SQL); //原生執行方法

    public abstract function explain();

}

/sour/HopeSQL.php

<?php


namespace builder\sour;

define('SQL_DEFAULT_ALIAS', '');
define('SQL_DEFAULT_PARAMS', []);
define('JOIN_CONTENT_NULL', null);
define('WHERE_OR', 1);
define('WHERE_AND', 2);
define('CONDITION_ABS_CLASS', 'builder\sour\Condition');
define('DEFAULT_ORDER_BY', 'ASC');

use \design\builder\sour\Condition;
use \Exception;
class HopeSQL extends SQLCreator
{

    private $joinIndex = 0;

    public function __construct($table, $alias = SQL_DEFAULT_ALIAS, $params = SQL_DEFAULT_PARAMS)
    {
        if(!$table) {
            throw new Exception('table name can not empty!');
        }
        if(!$alias) {
            $alias = $table;
        }
        parent::__construct($table, $alias, $params);
    }

    public function select($params)
    {
        foreach ($params as $param) {
            $this->params['fields'][$param] = $param;
        }
        return $this;
    }

    public function join($alias, $content = JOIN_CONTENT_NULL)
    {
        $this->joinIndex++;
        $this->params['join'][$this->joinIndex][$alias] = $content;
        return $this;
    }

    public function on(array $conditions, $class = CONDITION_ABS_CLASS) {
        foreach ($conditions as $condition) {
            if(!($condition instanceof $class)) {
                throw new Exception(sprintf('condition must is `%s`', $class));
            }
            $this->params['on'][$this->joinIndex][] = $condition;
        }

        return $this;
    }

    public function whereAnd($conditions, $class = CONDITION_ABS_CLASS)
    {
        foreach ($conditions as $condition) {
            if(!($condition instanceof $class)) {
                throw new Exception(sprintf('condition must is `%s`', $class));
            }
            $this->params['where'][] = [WHERE_AND => $condition];
        }
        return $this;
    }

    public function whereOr($conditions, $class = CONDITION_ABS_CLASS)
    {
        foreach ($conditions as $condition) {
            if(!($condition instanceof $class)) {
                throw new Exception(sprintf('condition must is `%s`', $class));
            }
            $this->params['where'][] = [WHERE_OR => $condition];
        }
        return $this;
    }

    public function whereAll(array $conditions, $class = CONDITION_ABS_CLASS)
    {

        foreach ($conditions as $condition) {
            $key = key($condition);
            $condition = current($condition);
            if(!($condition instanceof $class)) {
                throw new Exception(sprintf('condition must is `%s`', $class));
            }
            $this->params['where'][] = [$key => $condition];
        }
        return $this;
    }

    public function orderBy($field, $desc = DEFAULT_ORDER_BY)
    {
        $this->params['order_by'][] = $field.' '.$desc;
        return $this;
    }

    public function limit($offset, $length)
    {
        $this->params['offset'] = $offset;
        $this->params['length'] = $length;
        return $this;
    }

    public function get($name)
    {
        if(isset($this->params['fields'][$name])) {
            return StringHelper::joinField($this->alias, $this->params['fields'][$name]);
        }
        throw new Exception();
    }

    public function getAlias()
    {
        return $this->alias;
    }

    public function build()
    {
        $fields = '*';
        if(isset($this->params['fields']) && !empty($this->params['fields'])) {
            $fields = [];
            foreach ($this->params['fields'] as $field) {
                $fields[] = StringHelper::joinField($this->alias, $field);
            }
            $fields = implode(', ', $fields);
        }
        $join = '';
        if(isset($this->params['join']) && !empty($this->params['join'])) {
            $joins = [];
            foreach ($this->params['join'] as $index => $content) {
                $item = 'join ';
                $key = key($content);
                $content = current($content);
                if($content instanceof $this) {
                    $item .= '('.$content->build().')'.' as '.$key;
                } else {
                    $item .= $key.' as '.$key;
                }
                if(!empty($this->params['on'][$index])) {
                    $item .= ' on 1 = 1';
                    foreach ($this->params['on'][$index] as $condition) {
                        $varCondition = $condition->build();
                        if(is_array($varCondition)) {
                            if($varCondition['obj'] instanceof $this) {
                                $item .= ' AND '.$varCondition['field'].$varCondition['operator'].'('.$varCondition['obj']->build().')';
                            } else {
                                throw new Exception(sprintf('don\'t export class `%s`', get_class($varCondition['obj'])));
                            }
                        } else {
                            $item .= ' AND '.$varCondition;
                        }
                    }
                }
                $joins[] = $item;
            }
            $join .= implode(' ', $joins);
        }
        $where = 'where 1 = 1 ';
        if(isset($this->params['where']) && !empty($this->params['where'])) {
            $conditions = [];
            foreach ($this->params['where'] as $condition) {
                $key = key($condition);
                $condition = current($condition);
                if ($key === WHERE_AND) {
                    if ($condition instanceof $this) {
                        $conditions[] .= 'AND ' . $condition['field'] . $condition['operator'] . '(' . $condition['obj']->build() . ')';
                    } else {
                        $conditions[] .= 'AND ' . $condition->build();
                    }
                } elseif ($key === WHERE_OR) {
                    if ($condition instanceof $this) {
                        $conditions[] .= 'OR ' . $condition['field'] . $condition['operator'] . '(' . $condition['obj']->build() . ')';
                    } else {
                        $conditions[] .= 'OR ' . $condition->build();
                    }
                }
            }
            $where .= implode(' ', $conditions);
        }
        $order_by = '';
        if(isset($this->params['order_by']) && !empty($this->params['order_by'])) {
            $order_by = 'order by '.implode(', ', $this->params['order_by']);
        }
        $limit = '';
        if(isset($this->params['offset'])) {
            $limit .= 'limit '.$this->params['offset'];
            if(isset($this->params['length'])) {
                $limit .= ', '.$this->params['length'];
            }
        }
        $this->lastSQL = sprintf(
            'select %s from %s as %s %s %s %s %s',
            $fields,
            $this->table,
            $this->alias,
            $join,
            $where,
            $order_by,
            $limit
        );
        return $this->lastSQL;
        //todo order by
        //todo limit

    }

    public function query($SQL)
    {
        // TODO: Implement query() method.
    }

    public function execute($SQL)
    {
        // TODO: Implement execute() method.
    }

    public function explain()
    {
        $this->query('explain '.$this->build());
    }
}

/index.php

<?php

namespace design;

use \builder\sour as BS;

require 'autoload.php';
spl_autoload_register('auto_loader');

$objSQL = new BS\HopeSQL('user', 'us');
$SQL = $objSQL->select([
    'id',
    'name',
    'age',
    'email'
])->join(
    'password'
)->on([
    new BS\Condition($objSQL->get('id'), 'password.id', OPERATOR_EQ, RIGHT_TYPE_COLUMN)
])->join(
    'email'
)->on([
    new BS\Condition($objSQL->get('email'), 'email.id', OPERATOR_EQ, RIGHT_TYPE_COLUMN)
])->whereAnd([
    new BS\Condition($objSQL->get('id'), '1'),
    new BS\Condition($objSQL->get('name'), 'jack', OPERATOR_NOT_EQ, RIGHT_TYPE_STRING)
])->orderBy(
    $objSQL->get('id'),
    DEFAULT_ORDER_BY
)->orderBy(
    $objSQL->get('name'),
    DEFAULT_ORDER_BY
)->limit(
    0,
    10
)->build();

echo $SQL . PHP_EOL;

 使用結果:

select us.id, us.name, us.age, us.email from user as us join password as password on 1 = 1 AND us.id = password.id join email as email on 1 = 1 AND us.email = email.id where 1 = 1 AND us.id = 1 AND us.name <> 'jack' order by us.id ASC, us.name ASC limit 0, 10

呵呵~ 菜鳥之碼,還望見笑

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章