本文部分内容来自 【PHP秒杀系统 高并发高性能的极致挑战】
目标
- 如何设计和实现秒杀系统?
- 实现系统的高并发、高性能
- 实现系统的安全可靠
特点
- 人多商品少
- 时间短流量高
- 外挂机器
技术分析
- 瞬时高并发的处理能力
- 多层次的分布式处理能力
- 人机交互与对抗
系统设计
基本功能
- 后台
- 活动管理
- 商品管理
- 订单管理
- 日志管理
- 前台
- 商品展示
- 秒杀
- 购物车
- 我的订单
- 安全
- 验证码
- 问答
流程
- 秒杀流程
- 验证用户登录状态
- 问答验证、问答信息
- 库存验证
- 生成订单
系统环境搭建
技术选型
- 基础服务: Linux + Nginx + PHP + Mysql + redis
- CDN
- 负载均衡LVS
开发环境准备
- 开发工具:PHPStorm
- 部署开发环境: Linux + Nginx + PHP + Mysql + redis
- 代码管理器:Git
创建项目
- 创建miaosha项目
- 创建app,app/Mysql,config, Common目录
调试封装类
封装资源类的调用,比如MySQL,Redis,curl等
-
功能及其作用如下:
1 time 性能探针,计算运行的步骤以及每一步的执行效率 2 log 日志记录,把每一个日志信息记录下来 3 http 接口调用的记录以及耗时的汇总统计 4 redis redis调用的记录以及耗时的汇总统计 5 mysql mysql调用的记录以及耗时的汇总统计 6 cache memcache调用的记录以及耗时的汇总统计
-
在Common目录下创建DebugLog.php
<?php /** * 调试日志操作类 * DEBUG_LEVEL=0的时候不会在后端运行, * DEBUG_LEVEL=1的时候会记录错误、警告信息以及资源调用的耗时汇总统计, * DEBUG_LEVEL=2的时候,会记录全部的数据 * 如果在参数列表中出现 __DEBUG_LEVEL ,则会强制覆盖 DEBUG_LEVEL 的值 * 功能列表如下: * 1 time 性能探针,计算运行的步骤以及每一步的执行效率 * 2 log 日志记录,把每一个日志信息记录下来 * 3 http 接口调用的记录以及耗时的汇总统计 * 4 redis redis调用的记录以及耗时的汇总统计 * 5 mysql mysql调用的记录以及耗时的汇总统计 * 6 cache memcache调用的记录以及耗时的汇总统计 * * **/ namespace App\Common; define('DEBUG_LOG_ERROR', 'ERROR'); define('DEBUG_LOG_WARNING', 'WARNING'); define('DEBUG_LOG_INFO', 'INFO'); if (!defined('DEBUG_LEVEL')) { define('DEBUG_LEVEL', 0); } /** * Created by PhpStorm. * User: wangxinhuang * Date: 2019-07-26 * Time: 20:07 * * @package App\Common */ class DebugLog { private $logId; private $timeList; private $logList; private $httpList; private $redisList; private $mysqlList; private $cacheList; /** * @var bool|DebugLog */ private static $instance = false; private function __construct() { } /** * 初始化调试日志操作类,没有经过初始化的后续调试代码都不会生效 */ public static function _init() { if (!self::$instance) { self::$instance = new DebugLog(); self::$instance->logId = microtime(); } } /** * 记录时间,方便调试程序执行逻辑和每一步的执行效率 * @param $label * @param bool $handler */ public static function _time($label, $handler=false) { if (self::$instance === false) return; self::$instance->timeList[] = array($label, microtime(), $handler); } /** * 记录运行时的调试信息,分为 DEBUG_LOG_INFO 和 DEBUG_LOG_ERROR,DEBUG_LOG_INFO 只有在全量输出调试信息的时候才会输出 * @param $label * @param $info * @param string $level * @param bool $handler */ public static function _log($label, $info, $level=DEBUG_LOG_INFO, $handler=false) { if (self::$instance === false || (DEBUG_LEVEL < 2 && $level == DEBUG_LOG_INFO)) return; self::$instance->logList[] = array($label, $info![image.png](https://upload-images.jianshu.io/upload_images/14506913-f2e6fa97c79834b8.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
, handler);
}
/**
* 记录运行时的http请求
* @param $label
* @param $params
* @param $config
* @param $mtime1
* @param $mtime2
* @param null $data
* @param bool $handler
*/
public static function _http($label, $params, $config, $mtime1, $mtime2, $data=null, $handler=false)
{
if (self::$instance === false) return;
if (DEBUG_LEVEL === 1) {
self::$instance->httpList[] = array($label, json_encode($params), json_encode($config), $mtime1, $mtime2, null, $handler);
} else {
self::$instance->httpList[] = array($label, json_encode($params), json_encode($config), $mtime1, $mtime2, $data, $handler);
}
}
/**
* 记录运行时的redis请求
* @param $label
* @param $params
* @param $config
* @param $mtime1
* @param $mtime2
* @param null $data
* @param bool $handler
*/
public static function _redis($label, $params, $config, $mtime1, $mtime2, $data = null, $handler = false)
{
if (self::$instance === false) return;
if (DEBUG_LEVEL === 1) {
if ('setex' == $label) {
// 过滤掉内容块,避免日志太多
$params[2] = null;
}
self::$instance->redisList[] = array($label, json_encode($params), json_encode($config), $mtime1, $mtime2, null, $handler);
} else {
self::$instance->redisList[] = array($label, json_encode($params), json_encode($config), $mtime1, $mtime2, $data, $handler);
}
}
/**
* 记录运行时的mysql请求
* @param $label
* @param $params
* @param $config
* @param $mtime1
* @param $mtime2
* @param null $data
* @param bool $handler
*/
public static function _mysql($label, $params, $config, $mtime1, $mtime2, $data = null, $handler = false)
{
if (self::$instance === false) return;
if (DEBUG_LEVEL === 1) {
self::$instance->mysqlList[] = array($label, json_encode($params), json_encode($config), $mtime1, $mtime2, null, $handler);
} else {
self::$instance->mysqlList[] = array($label, json_encode($params), json_encode($config), $mtime1, $mtime2, $data, $handler);
}
}
/**
* 记录运行时的memcache请求
* @param $label
* @param $params
* @param $config
* @param $mtime1
* @param $mtime2
* @param null $data
* @param bool $handler
*/
public static function _cache($label, $params, $config, $mtime1, $mtime2, $data = null, $handler = false)
{
if (self::$instance === false) return;
if (DEBUG_LEVEL === 1) {
self::$instance->cacheList[] = array($label, json_encode($params), json_encode($config), $mtime1, $mtime2, null, $handler);
} else {
self::$instance->cacheList[] = array($label, json_encode($params), json_encode($config), $mtime1, $mtime2, $data, $handler);
}
}
/**
* 输出日志
*/
public static function _show()
{
if (self::$instance === false) return;
if (isset($_SERVER['HTTP_USER_AGENT'])) {
// 界面上可视化模式输出内容
self::$instance->showViews();
} else {
self::$instance->writeLogs();
}
}
/**
* 是否有可视化界面输出,HTML代码直接返回到浏览器
* @return bool
*/
public static function _is_show_view()
{
if (self::$instance && isset($_SERVER['HTTP_USER_AGENT'])) return true;
return false;
}
/**
* 将microtime的时间字符串转换为float型的毫秒时间
* @param $mt mixed
* @return float|int
*/
private function _floatMicrotime($mt)
{
if (strpos($mt, ' ')) {
list($ms, $m) = explode(' ', $mt);
return ($m + $ms) * 1000;
}
return floatval($mt) * 1000;
}
/**
* 计算两个microtime时间的间隔时间
* @param $m1 mixed 开始时间
* @param $m2 mixed 结束时间
* @param int $round 保留小数位
* @return float
*/
private function _intervalTime($m1, $m2, $round = 3) {
return round(($this->_floatMicrotime($m2) - $this->_floatMicrotime($m1)), $round);
}
/**
* 将调试信息生成可视化的HTML代码
*/
private function showViews()
{
$showTime = microtime();
$output = array();
$output[] = "\n";
$output[] = '<ul>';
$output[] = '<li><strong style="font-size:18px;">DebugLog showViews.total process time is ' . $this->_intervalTime($this->logId, $showTime) . 'ms</strong></li>';
if ($this->timeList) {
$total_num = count($this->timeList);
$output[] = '<li><strong style="font-size:18px;">TimeList total count is ' . count($this->timeList) . ', log time is ' . $this->_intervalTime($this->logId, $this->timeList[$total_num - 1][1]) . '</strong></li>';
$lasttime = $this->logId;
$output[] = '<li>0.000 : start debug log ' . $lasttime . '</li>';
foreach ($this->timeList as $info) {
$lasttime2 = $info[1];
$output[] = '<li>'. $this->_intervalTime($lasttime, $lasttime2) . ' : ' . implode("\t", $info) . '</li>';
$lasttime = $lasttime2;
}
}
if ($this->logList) {
$output[] = '<li><strong style="font-size:18px;">LogList total count is ' . count($this->logList) . '</strong></li>';
foreach ($this->logList as $info) {
$output[] = '<li>' . implode("\t", $info) . '</li>';
}
}
if ($this->httpList) {
$current = count($output);
$total_time = 0;
$output[] = null;
$max_num = array();
$multi_num = array();
foreach ($this->httpList as $info) {
$intval = $this->_intervalTime($info[3], $info[4]);
$multi_flag = @json_decode($info[2],true);
if(isset($multi_flag) && isset($multi_flag['is_multi']) && $multi_flag['is_multi']==1)
{
$multi_str = strval($multi_flag['multi_num']);
if($intval > $max_num[$multi_str])
{
$max_num[$multi_str] = $intval;
if(!in_array($multi_str, $multi_num))
{
$multi_num[] = $multi_str;
}
}
}
else
{
$total_time += $intval;
}
if ($info[5] && is_array($info[5])) {
$info[5] = json_encode($info[5]);
}
$output[] = '<li>'. $intval .' : ' . implode("\t", $info) . '</li>';
}
if(!empty($multi_num ))
{
foreach($multi_num as $val)
{
$total_time += $max_num[$val];
}
}
$output[$current] = '<li><strong style="font-size:18px;">HttpList total count is ' . count($this->httpList) . ', total time is ' . $total_time . '</strong></li>';
}
if ($this->redisList) {
$current = count($output);
$total_time = 0;
$output[] = null;
foreach ($this->redisList as $info) {
$intval = $this->_intervalTime($info[3], $info[4]);
$total_time += $intval;
if ($info[5] && is_array($info[5])) {
$info[5] = json_encode($info[5]);
}
$output[] = '<li>'. $intval .' : ' . implode("\t", $info) . '</li>';
}
$output[$current] = '<li><strong style="font-size:18px;">RedisList total count is ' . count($this->redisList) . ', total time is ' . $total_time . '</strong></li>';
}
if ($this->mysqlList) {
$current = count($output);
$total_time = 0;
$output[] = null;
foreach ($this->mysqlList as $info) {
$intval = $this->_intervalTime($info[3], $info[4]);
$total_time += $intval;
if ($info[5] && is_array($info[5])) {
$info[5] = json_encode($info[5]);
} elseif (!$info[5]) {
$info[5] = '';
}
$output[] = '<li>'. $intval .' : ' . implode("\t", $info) . '</li>';
}
$output[$current] = '<li><strong style="font-size:18px;">MysqlList total count is ' . count($this->mysqlList) . ', total time is ' . $total_time . '</strong></li>';
}
if ($this->cacheList) {
$current = count($output);
$total_time = 0;
$output[] = null;
foreach ($this->cacheList as $info) {
$intval = $this->_intervalTime($info[3], $info[4]);
$total_time += $intval;
if ($info[5] && is_array($info[5])) {
$info[5] = json_encode($info[5]);
}
$output[] = '<li>'. $intval .' : ' . implode("\t", $info) . '</li>';
}
$output[$current] = '<li><strong style="font-size:18px;">CacheList total count is ' . count($this->cacheList) . ', total time is ' . $total_time . '</strong></li>';
}
$output[] = '</ul>';
echo implode("\n", $output);
}
/**
* 将调试日志写入到本地文件中,使用JSON格式保存为一行
*/
public function writeLogs()
{
$showTime = microtime();
if (!defined('DEBUG_LOG_PATH')) {
define('DEBUG_LOG_PATH', '/var/log/');
}
$serverList = array(
'SCRIPT_NAME' => $_SERVER['SCRIPT_NAME'],
'REQUEST_URI' => $_SERVER['REQUEST_URI'],
'REMOTE_ADDR:PORT' => $_SERVER['REMOTE_ADDR'] . ':' . $_SERVER['REMOTE_PORT'],
);
$datalist = array(
'logId'=>$this->logId,
'logTime'=>$showTime,
'timeList'=>$this->timeList,
'logList'=>$this->logList,
'httpList'=>$this->httpList,
'redisList'=>$this->redisList,
'mysqlList'=>$this->mysqlList,
'server'=>$serverList,
);
$str = json_encode($datalist);
$str = str_replace("\n", ' ', $str);
$str .= "\n";
$file_path = DEBUG_LOG_PATH . 'discuz_debug.log';
if($fd = @fopen($file_path, 'a')) {
fputs($fd, $str);
fclose($fd);
}
}
/**
* 将消息输出到指定的文件
* 默认 define('DEBUG_LOG_PATH', '~/log/php/today/')
* @param $msg
* @param string $file
*/
public static function writeDebugLog($msg, $file='discuz_php.log')
{
$dtime = date('Y-m-d H:i:s');
if (!defined('DEBUG_LOG_PATH')) {
$default_path = '/var/log/';
if (file_exists($default_path)) {
define('DEBUG_LOG_PATH', $default_path);
} else {
define('DEBUG_LOG_PATH', '');
}
}
$str_cookie = 'no cookie';
$str_server = json_encode(array($_SERVER['HTTP_X_FORWARDED_FOR'], $_SERVER['REMOTE_ADDR'], $_SERVER['HTTP_HOST'], $_SERVER['REQUEST_URI']));
$str = "[$dtime]||$msg||$str_cookie||$str_server\n";
$file_path = DEBUG_LOG_PATH . $file;
if($fd = @fopen($file_path, 'a')) {
fputs($fd, $str);
fclose($fd);
}
}
/**
* 通过PHP的 debug_backtrace 可以详细的查看到方法调用的细节情况
* @param int $deep
* @param bool $all
*/
public static function writeBacktrace($deep=3, $all=false)
{
$result = array();
$trace = debug_backtrace();
unset($trace[0]);
if ($deep < count($trace)) {
for ($i = 1; $i <= $deep; $i++) {
$info = $trace[$i];
if (isset($info['object']) && $all === false) {
unset($info['object']);
}
$result[] = $info;
}
} elseif ($all === false) {
foreach ($trace as $info) {
if (isset($info['object'])) {
unset($info['object']);
}
$result[] = $info;
}
} else {
$result = $trace;
}
self::writeDebugLog(json_encode($result), 'backtrace.log');
}
}
```
Rdis封装类
-
创建配置文件config/redis.ini.php
<?php /** * Created by PhpStorm. * User: wangxinhuang * Date: 2019-07-27 * Time: 10:19 */ $config['redis']['instance1'] = array( 'default' => array( 'host' => '127.0.0.1', 'port' => '6379', 'timeout' => 5, 'pconnect' => 1, 'password' => '' ) ); $config['redis']['instance2'] = array( 'default' => array( 'host' => '127.0.0.1', 'port' => '6379', 'timeout' => 5, 'pconnect' => 1, 'password' => '' ) );
-
创建Helpers目录和Helpers/RedisHelper.php
<?php namespace App\Helpers; use App\Common\DebugLog; /** * Created by PhpStorm. * User: wangxinhuang * Date: 2019-07-27 * Time: 10:24 * * @package App\Helpers */ class RedisHelper { /** * @var string */ private $_config_name = ''; /** * redis配置信息 * @var null */ private $_redis_config = null; /** * redis作用域 * @var null */ private $_server_region = null; /** * 超时 * @var int */ public $timeout = 1; /** * @var \Redis */ private $_redis = null; /** * @var array */ private static $instances = array(); /** * 计数器,连接重试 * @var int */ private static $connect_error = 0; /** * @var int */ private $call_error = 0; /** * RedisHelper constructor. * @param string $_config_name * @param null $_redis_config * @param null $_server_region */ public function __construct($config_name, $redis_config, $server_region) { if ($config_name && $redis_config && $server_region) { $this->_config_name = $config_name; $this->_redis_config = $redis_config; $this->_server_region = $server_region; $this->timeout = isset($this->_redis_config[$server_region]['timeout']) ? $this->_redis_config[$server_region]['timeout'] : $this->timeout; try { $this->_redis = new \Redis(); $this->_redis->connect($this->_redis_config[$server_region]['host'], $this->_redis_config[$server_region]['port'], $this->timeout); $password = $this->_redis_config[$server_region]['password']; if ($password && !$this->_redis->auth($password)) { $this->_redis = null; } } catch (\Exception $exception) { $this->_redis = null; } } else { $this->_redis = null; } } /** * Redis实例公开方法 * @param $config_name string 配置名称 * @param $redis_config array 配置信息 * @param $server_region string 作用域 * @return bool|mixed */ public static function instance($config_name, $redis_config, $server_region) { if (!$config_name || !$redis_config) { return false; } // 判断实例是否存在, // $starttime = microtime(); $only_key = $config_name . ':' . $server_region; if (!isset(self::$instances[$only_key])) { try { self::$instances[$only_key] = new RedisHelper($config_name, $redis_config, $server_region); self::$connect_error = 0; } catch (\Exception $exception) { if (self::$connect_error < 2) { self::$connect_error += 1; return self::instance($config_name, $redis_config, $server_region); } else { self::$connect_error = 0; self::$instances[$only_key] = new RedisHelper(false, false, false); } } } $redis_config_info = array(); if ($redis_config && isset($redis_config[$server_region]) && isset($redis_config[$server_region]['password'])) { $redis_config_info = $redis_config[$server_region]; unset($redis_config_info['password']); } DebugLog::_redis('redis_instance', $config_name, $redis_config_info, $starttime, microtime(), null); self::$connect_error = 0; return self::$instances[$only_key]; } /** * 魔术方法, 没有定义的方法都会走这边 * @param $name * @param $arguments * @return array|bool|mixed */ public function __call($name, $arguments) { if (!$this->_redis) { return false; } $starttime = microtime(); try { if ('scan' == $name) { $data = $this->_redis->scan($arguments[0]); } else { $data = call_user_func_array(array($this->_redis, $name), $arguments); } } catch (\Exception $exception) { if ($this->call_error < 2) { $this->call_error++; return call_user_func_array(array($this->_redis, $name), $arguments); } else { $this->call_error = 0; } $data = false; } $this->call_error = 0; $redis_config = $this->_redis_config[$this->_server_region]; if ($redis_config && isset($redis_config['password'])) { unset($redis_config['password']); } DebugLog::_redis($name, $arguments, $redis_config, $starttime, microtime(), (is_string($data) || is_array($data)) ? $data : null); return $data; } /** * 销毁 */ public function __destruct() { if ($this->_redis != NULL) { $this->_redis->close(); } } }
-
封装RedisHelper,创建Common/Datasource.php
<?php namespace App\Common; use App\Helpers\RedisHelper; /** * Created by PhpStorm. * User: wangxinhuang * Date: 2019-07-27 * Time: 10:56 * * @package App\Common */ class Datasource { /** * redis实例 * @var array */ public static $redises = array(); /** * Datasource constructor. */ public function __construct() { } /** * 获取redis实例 * @param null $config_name * @param string $server_region * @return mixed|null */ public static function getRedis($config_name = null, $server_region = 'default') { if ($config_name === null) { return null; } // 判断是否存在,防止重复创建,浪费资源 if (isset(self::$redises[$config_name]) && self::$redises[$config_name]) { return self::$redises[$config_name]; } // 获取全局配置信息 global $config; $redis_config = $config['redis'][$config_name]; // 创建Redis实例 try { self::$redises[$config_name] = RedisHelper::instance($config_name, $redis_config, $server_region); } catch (\Exception $exception) { self::$redises[$config_name] = null; } return self::$redises[$config_name]; } }
MySQL封装类
-
创建配置文件config/db.ini.php
<?php /** * Created by PhpStorm. * User: wangxinhuang * Date: 2019-07-26 * Time: 19:07 */ // 数据库配置文件 // 主库 $config['db']['master'] = array( 'host' => '127.0.0.1:3306', 'dbname' => 'miaosha', 'user' => 'root', 'password' => '', ); // 从库 $config['db']['slave'] = array( 'host' => '127.0.0.1:3306', 'dbname' => 'miaosha', 'user' => 'root', 'password' => '', );
在app/Mysql目录下创建Db.php
```
<?php
namespace App\Mysql;
use App\Common\DebugLog;
use PDO;
use PDOException;
/**
* DB - A simple database class
* PDO写入数据库
* @modify 黄旺鑫
* @author Author: Vivek Wicky Aswal. (https://twitter.com/#!/VivekWickyAswal)
* @git https://github.com/wickyaswal/php-my-sql-pdo-database-class
* @version 0.2ab
*
*/
class DB
{
# @object, The PDO object
/**
* @var PDO
*/
private $pdo;
# @object, PDO statement object
/**
* @var \PDOStatement
*/
private $sQuery;
# @array, The database settings
private $settings;
# @bool , Connected to the database
private $bConnected = false;
# @object, Object for logging exceptions
private $log;
# @array, The parameters of the SQL query
private $parameters;
private static $instances = array();
/**
* 获取单例
* @param string $name
* @return mixed
*/
public static function getInstance($name = 'master') {
// 判断是否已存在实例, 使用单例
if (isset(self::$instances[$name])) {
return self::$instances[$name];
}
self::$instances[$name] = new DB($name);
return self::$instances[$name];
}
/**
* Default Constructor
*
* 1. Instantiate Log class.
* 2. Connect to database.
* 3. Creates the parameter array.
*/
private function __construct($name = 'master')
{
$this->Connect($name);
$this->parameters = array();
}
/**
* This method makes connection to the database.
*
* 1. Reads the database settings from a ini file.
* 2. Puts the ini content into the settings array.
* 3. Tries to connect to the database.
* 4. If connection failed, exception is displayed and a log file gets created.
*/
private function Connect($name = 'master')
{
// 全局配置信息
global $config;
$mtime1 = microtime();
$this->settings = $config['db'][$name];
$dsn = 'mysql:dbname=' . $this->settings["dbname"] . ';host=' . $this->settings["host"] . '';
try {
# Read settings from INI file, set UTF8
$this->pdo = new PDO($dsn, $this->settings["user"], $this->settings["password"], array(
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8"
));
# We can now log any exceptions on Fatal error.
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
# Disable emulation of prepared statements, use REAL prepared statements instead.
$this->pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
# Connection succeeded, set the boolean to true.
$this->bConnected = true;
}
catch (PDOException $e) {
# Write into log
print_r($e);
echo $this->ExceptionLog($e->getMessage());
die();
}
$mtime2 = microtime();
// 输出日志
DebugLog::_mysql('connect', null, array('host' => $this->settings['host'],
'dbname' => $this->settings['dbname']), $mtime1, $mtime2, null);
}
/*
* You can use this little method if you want to close the PDO connection
*
*/
public function CloseConnection()
{
# Set the PDO object to null to close the connection
# http://www.php.net/manual/en/pdo.connections.php
$this->pdo = null;
}
/**
* Every method which needs to execute a SQL query uses this method.
*
* 1. If not connected, connect to the database.
* 2. Prepare Query.
* 3. Parameterize Query.
* 4. Execute Query.
* 5. On exception : Write Exception into the log + SQL query.
* 6. Reset the Parameters.
*/
private function Init($query, $parameters = "")
{
# Connect to database
if (!$this->bConnected) {
$this->Connect();
}
try {
# Prepare query
$this->sQuery = $this->pdo->prepare($query);
# Add parameters to the parameter array
if ($parameters && isset($parameters[0])) {
// ? 占位符形式
# Execute SQL
$this->sQuery->execute($this->parameters);
} else {
// : fieldname 字段名形式
$this->bindMore($parameters);
# Bind parameters
if (!empty($this->parameters)) {
foreach ($this->parameters as $param) {
$parameters = explode("\x7F", $param);
$this->sQuery->bindParam($parameters[0], $parameters[1]);
// if(is_int($value[1])) {
// $type = PDO::PARAM_INT;
// } else if(is_bool($value[1])) {
// $type = PDO::PARAM_BOOL;
// } else if(is_null($value[1])) {
// $type = PDO::PARAM_NULL;
// } else {
// $type = PDO::PARAM_STR;
// }
// // Add type when binding the values to the column
// $this->sQuery->bindValue($value[0], $value[1], $type);
}
}
# Execute SQL
$this->sQuery->execute();
}
}
catch (PDOException $e) {
# Write into log and display Exception
echo $this->ExceptionLog($e->getMessage(), $query);
die();
}
# Reset the parameters
$this->parameters = array();
}
/**
* @void
*
* Add the parameter to the parameter array
* @param string $para
* @param string $value
*/
public function bind($para, $value)
{
if (is_array($para)) {
$para = json_encode($para);
}
if (is_array($value)) {
$value = json_encode($value);
}
$this->parameters[sizeof($this->parameters)] = ":" . $para . "\x7F" . $value;
// $this->parameters[sizeof($this->parameters)] = [":" . $para , $value];
}
/**
* @void
*
* Add more parameters to the parameter array
* @param array $parray
*/
public function bindMore($parray)
{
if (empty($this->parameters) && is_array($parray)) {
$columns = array_keys($parray);
foreach ($columns as $i => &$column) {
$this->bind($column, $parray[$column]);
}
}
}
/**
* If the SQL query contains a SELECT or SHOW statement it returns an array containing all of the result set row
* If the SQL statement is a DELETE, INSERT, or UPDATE statement it returns the number of affected rows
*
* @param string $query
* @param array $params
* @param int $fetchmode
* @return mixed
*/
public function query($query, $params = null, $fetchmode = PDO::FETCH_ASSOC)
{
$mtime1 = microtime();
$query = trim($query);
// $query = trim(str_replace("\r", " ", $query));
$this->Init($query, $params);
$rawStatement = explode(" ", $query);
// $rawStatement = explode(" ", preg_replace("/\s+|\t+|\n+/", " ", $query));
# Which SQL statement is used
$statement = strtolower($rawStatement[0]);
$result = NULL;
if ($statement === 'select' || $statement === 'show') {
$result = $this->sQuery->fetchAll($fetchmode);
} elseif ($statement === 'insert' || $statement === 'update' || $statement === 'delete') {
$result = $this->sQuery->rowCount();
}
$mtime2 = microtime();
DebugLog::_mysql('query: ' . $query, $params, array('host' => $this->settings['host'],
'dbname' => $this->settings['dbname']), $mtime1, $mtime2, $result);
return $result;
}
/**
* Returns the last inserted id.
* @return string
*/
public function lastInsertId()
{
return $this->pdo->lastInsertId();
}
/**
* Starts the transaction
* @return boolean, true on success or false on failure
*/
public function beginTransaction()
{
return $this->pdo->beginTransaction();
}
/**
* Execute Transaction
* @return boolean, true on success or false on failure
*/
public function executeTransaction()
{
return $this->pdo->commit();
}
/**
* Rollback of Transaction
* @return boolean, true on success or false on failure
*/
public function rollBack()
{
return $this->pdo->rollBack();
}
/**
* Returns an array which represents a column from the result set
*
* @param string $query
* @param array $params
* @return array
*/
public function column($query, $params = null)
{
$mtime1 = microtime();
$this->Init($query, $params);
$Columns = $this->sQuery->fetchAll(PDO::FETCH_NUM);
$column = null;
foreach ($Columns as $cells) {
$column[] = $cells[0];
}
$mtime2 = microtime();
DebugLog::_mysql('column: ' . $query, $params, array('host' => $this->settings['host'],
'dbname' => $this->settings['dbname']), $mtime1, $mtime2, $column);
return $column;
}
/**
* Returns an array which represents a row from the result set
*
* @param string $query
* @param array $params
* @param int $fetchmode
* @return array
*/
public function row($query, $params = null, $fetchmode = PDO::FETCH_ASSOC)
{
$mtime1 = microtime();
$this->Init($query, $params);
$result = $this->sQuery->fetch($fetchmode);
// $this->sQuery->closeCursor(); // Frees up the connection to the server so that other SQL statements may be issued,
$mtime2 = microtime();
DebugLog::_mysql('row: ' . $query, $params, array('host' => $this->settings['host'],
'dbname' => $this->settings['dbname']), $mtime1, $mtime2, $result);
return $result;
}
/**
* Returns the value of one single field/column
*
* @param string $query
* @param array $params
* @return string
*/
public function single($query, $params = null)
{
$mtime1 = microtime();
$this->Init($query, $params);
$result = $this->sQuery->fetchColumn();
$mtime2 = microtime();
DebugLog::_mysql('single: ' . $query, $params, array('host' => $this->settings['host'],
'dbname' => $this->settings['dbname']), $mtime1, $mtime2, $result);
// $this->sQuery->closeCursor(); // Frees up the connection to the server so that other SQL statements may be issued
return $result;
}
/**
* Writes the log and returns the exception
*
* @param string $message
* @param string $sql
* @return string
*/
private function ExceptionLog($message, $sql = "")
{
$exception = 'Unhandled Exception. <br />';
if (!empty($sql)) {
# Add the Raw SQL to the Log
$message .= "\r\nRaw SQL : " . $sql;
$exception .= $message;
$exception .= "<br /> You can find the error back in the log.";
return $exception;
}
# Write into log
// $this->log->write($message);
return '';
}
}
?>
```
-
在app/Mysql目录下创建Crud.php
<?php namespace App\Mysql; /** * Easy Crud - This class kinda works like ORM. Just created for fun :) * @modify 王毅 * @author Author: Vivek Wicky Aswal. (https://twitter.com/#!/VivekWickyAswal) * @version 0.1a */ class Crud { private $db; protected $fields; public $variables; public function __construct($data = array()) { if ($this->fields && $data) { foreach ($data as $k => $d) { if (!in_array($k, $this->fields)) { unset($data[$k]); } } } $this->variables = $data; } public function setDb($db) { $this->db = $db; } public function getDb() { if (!$this->db) { $this->db = DB::getInstance('master'); } return $this->db; } public function __set($name,$value){ if(strtolower($name) === $this->pk) { $this->variables[$this->pk] = $value; } else { if (!$this->fields || in_array($name, $this->fields)) { $this->variables[$name] = $value; } } } public function __get($name) { if(is_array($this->variables)) { if(array_key_exists($name,$this->variables)) { return $this->variables[$name]; } } $trace = debug_backtrace(); trigger_error( 'Undefined property via __get(): ' . $name . ' in ' . $trace[0]['file'] . ' on line ' . $trace[0]['line'], E_USER_NOTICE); return null; } public function save($id = "0") { $this->variables[$this->pk] = $id ? $id : $this->variables[$this->pk]; $fieldsvals = ''; $columns = array_keys($this->variables); foreach($columns as $column) { if($column !== $this->pk) $fieldsvals .= "`{$column}` = :". $column . ","; } $fieldsvals = substr_replace($fieldsvals , '', -1); if(count($columns) > 1 ) { $sql = "UPDATE `" . $this->table . "` SET " . $fieldsvals . " WHERE `" . $this->pk . "`= :" . $this->pk; return $this->getDb()->query($sql,$this->variables); } } public function create() { $bindings = $this->variables; if(!empty($bindings)) { $fields = array_keys($bindings); $fieldsvals = array('`' . implode("`,`",$fields) . '`', ":" . implode(",:",$fields)); $sql = "INSERT INTO `".$this->table."` (".$fieldsvals[0].") VALUES (".$fieldsvals[1].")"; } else { $sql = "INSERT INTO `".$this->table."` () VALUES ()"; } $ok = $this->getDb()->query($sql,$bindings); if ($ok) { return $this->getDB()->lastInsertId(); } else { return $ok; } } public function delete($id = "") { $id = (empty($this->variables[$this->pk])) ? $id : $this->variables[$this->pk]; if(!empty($id)) { $sql = "DELETE FROM `" . $this->table . "` WHERE `" . $this->pk . "`= :" . $this->pk. " LIMIT 1" ; return $this->getDb()->query($sql,array($this->pk=>$id)); } } public function get($id = "") { $id = $id ? $id : $this->variables[$this->pk]; if(!empty($id)) { $sql = "SELECT * FROM `" . $this->table ."` WHERE `" . $this->pk . "`= :" . $this->pk . " LIMIT 1"; $this->variables = $this->getDb()->row($sql,array($this->pk=>$id)); } return $this->variables; } public function all(){ return $this->getDb()->query("SELECT * FROM `" . $this->table . '`'); } public function count(){ return $this->getDb()->query("SELECT COUNT(1) FROM `" . $this->table . '`'); } // public function min($field) { // if($field) // return $this->getDb()->single("SELECT min(" . $field . ")" . " FROM " . $this->table); // } // // public function max($field) { // if($field) // return $this->getDb()->single("SELECT max(" . $field . ")" . " FROM " . $this->table); // } // // public function avg($field) { // if($field) // return $this->getDb()->single("SELECT avg(" . $field . ")" . " FROM " . $this->table); // } // // public function sum($field) { // if($field) // return $this->getDb()->single("SELECT sum(" . $field . ")" . " FROM " . $this->table); // } }