Home  >  Article  >  Backend Development  >  php pdo database operation encapsulation class code

php pdo database operation encapsulation class code

WBOY
WBOYOriginal
2016-07-25 08:51:521077browse
  1. /**

  2.  * Database PDO operation
  3.  */
  4. class MysqlPdo {
  5. public static $PDOStatement = null;
  6. /**
  7. * Database connection parameter configuration
  8. * @var array
  9. * @access public
  10. */
  11. public static $config = array();
  12. /**
  13. * Whether to use permanent connection
  14. * @var bool
  15. * @access public
  16. */
  17. public static $pconnect = false;
  18. /**
  19. * Error message
  20. * @var string
  21. * @access public
  22. */
  23. public static $error = '';
  24. /**
  25. * Singleton mode, saves the only instance of the Pdo class and the connection resource of the database
  26. * @var object
  27. * @access public
  28. */
  29. protected static $link;
  30. /**
  31. * Whether the database has been connected
  32. * @var bool
  33. * @access public
  34. */
  35. public static $connected = false;
  36. /**
  37. * Database version
  38. * @var string
  39. * @access public
  40. */
  41. public static $dbVersion = null;
  42. /**
  43. * Current SQL statement
  44. * @var string
  45. * @access public
  46. */
  47. public static $queryStr = '';
  48. /**
  49. * The ID of the last inserted record
  50. * @var integer
  51. * @access public
  52. */
  53. public static $lastInsertId = null;
  54. /**
  55. * Returns the number of affected records
  56. * @var integer
  57. * @access public
  58. */
  59. public static $numRows = 0;
  60. // Number of transaction instructions
  61. public static $ transTimes = 0;
  62. /**
  63. * Constructor,
  64. * @param $dbconfig Database connection related information, array('ServerName', 'UserName', 'Password', 'DefaultDb', 'DB_Port', 'DB_TYPE')
  65. */
  66. public function __construct($dbConfig=''){
  67. if (!class_exists('PDO')) self::throw_exception("Not supported: PDO");
  68. //If no parameters are transmitted, the default data definition is used
  69. if (!is_array($dbConfig)) {
  70. $dbConfig = array(
  71. 'hostname' => DB_HOST,
  72. 'username' => DB_USER,
  73. 'password' => DB_PWD,
  74. 'database' => DB_NAME,
  75. 'hostport' => DB_PORT,
  76. 'dbms' => DB_TYPE,
  77. 'dsn' => DB_TYPE.":host=". DB_HOST.";dbname=".DB_NAME
  78. );
  79. }
  80. if(empty($dbConfig['hostname'])) self::throw_exception("No database configuration defined");
  81. self::$config = $dbConfig ;
  82. if(empty(self::$config['params'])) self::$config['params'] = array();
  83. /**************************************GORGEOUS DIVIDERS************ *****************************/
  84. if (!isset(self: :$link) ) {
  85. $configs = self::$config;
  86. if(self::$pconnect) {
  87. $configs['params'][constant('PDO::ATTR_PERSISTENT')] = true;
  88. }
  89. try {
  90. self::$link = new PDO( $configs['dsn'], $configs['username'], $configs['password'],$configs['params']);
  91. } catch ( PDOException $e) {
  92. self::throw_exception($e->getMessage());
  93. }
  94. if(!self::$link) {
  95. self::throw_exception('PDO CONNECT ERROR');
  96. return false ;
  97. }
  98. self::$link->exec('SET NAMES '.DB_CHARSET);
  99. self::$dbVersion = self::$link->getAttribute(constant("PDO::ATTR_SERVER_INFO"));
  100. // Mark the connection successfully
  101. self::$connected = true;
  102. // Log out the database connection configuration information
  103. unset($configs);
  104. }
  105. return self::$link;
  106. }
  107. /**
  108. * Release query results
  109. * @access function
  110. * /
  111. static function free() {
  112. self::$PDOStatement = null;
  113. }
  114. /************************/
  115. /* Database operation*/
  116. /************************/
  117. /**
  118. * Get all query data
  119. * @access function
  120. * @return array
  121. */
  122. static function getAll($sql=null) {
  123. if($sql != null)
  124. {
  125. self::query($sql);
  126. }
  127. //Return the data set
  128. $result = self:: $PDOStatement->fetchAll(constant('PDO::FETCH_ASSOC'));
  129. return $result;
  130. }
  131. /**
  132. * Obtain a query result
  133. * @access function
  134. * @param string $sql SQL command
  135. * @param integer $seek pointer position
  136. * @return array
  137. */
  138. static function getRow($sql=null) {
  139. if($sql != null)
  140. {
  141. self::query($sql);
  142. }
  143. // Return array set
  144. $result = self::$PDOStatement->fetch(constant('PDO::FETCH_ASSOC'),constant( 'PDO::FETCH_ORI_NEXT'));
  145. return $result;
  146. }
  147. /**
  148. * Execute sql statement, automatically judge to query or perform operations
  149. * @access function
  150. * @param string $sql SQL command
  151. * @return mixed
  152. */
  153. static function doSql($sql='') {
  154. if(self::isMainIps($sql)) {
  155. return self::execute($sql);
  156. }else {
  157. return self::getAll($sql);
  158. }
  159. }
  160. /**
  161. * Find records in the table based on the specified ID (only for single table operations)
  162. * @access function
  163. * @param integer $priId primary key ID
  164. * @param string $tables data table name
  165. * @param string $fields field name
  166. * @return ArrayObject table record
  167. */
  168. static function findById($tabName,$priId, $fields='*'){
  169. $sql = 'SELECT %s FROM %s WHERE id=%d';
  170. return self::getRow(sprintf($sql, self::parseFields($fields), $tabName, $priId));
  171. }
  172. /**
  173. * Search records
  174. * @access function
  175. * @param string $tables Data table name
  176. * @param mixed $where Query conditions
  177. * @param string $fields Field name
  178. * @param string $order Sort
  179. * @param string $limit How many pieces of data to take
  180. * @param string $group Grouping
  181. * @param string $having
  182. * @param boolean $lock Whether to lock or not
  183. * @return ArrayObject
  184. */
  185. static function find($tables,$where="",$fields='*',$order=null,$limit=null,$group=null,$having=null) {
  186. $sql = 'SELECT '.self::parseFields($fields)
  187. .' FROM '.$tables
  188. .self::parseWhere($where)
  189. .self::parseGroup($group)
  190. .self::parseHaving($having)
  191. .self::parseOrder($order)
  192. .self::parseLimit($limit);
  193. $dataAll = self::getAll($sql);
  194. if(count($dataAll)==1){$rlt=$dataAll[0];}else{$rlt=$dataAll;}
  195. return $rlt;
  196. }
  197. /**
  198. * Insert (single) record
  199. * @access function
  200. * @param mixed $data data
  201. * @param string $table data table name
  202. * @return false | integer
  203. */
  204. function add($data,$table) {
  205. //过滤提交数据
  206. $data=self::filterPost($table,$data);
  207. foreach ($data as $key=>$val){
  208. if(is_array($val) && strtolower($val[0]) == 'exp') {
  209. $val = $val[1]; // 使用表达式 ???
  210. }elseif (is_scalar($val)){
  211. $val = self::fieldFormat($val);
  212. }else{
  213. // 去掉复合对象
  214. continue;
  215. }
  216. $data[$key] = $val;
  217. }
  218. $fields = array_keys($data);
  219. array_walk($fields, array($this, 'addSpecialChar'));
  220. $fieldsStr = implode(',', $fields);
  221. $values = array_values($data);
  222. $valuesStr = implode(',', $values);
  223. $sql = 'INSERT INTO '.$table.' ('.$fieldsStr.') VALUES ('.$valuesStr.')';
  224. return self::execute($sql);
  225. }
  226. /**
  227. * Update record
  228. * @access function
  229. * @param mixed $sets data
  230. * @param string $table data table name
  231. * @param string $where update condition
  232. * @param string $limit
  233. * @param string $order
  234. * @return false | integer
  235. */
  236. static function update($sets,$table,$where,$limit=0,$order='') {
  237. $sets = self::filterPost($table,$sets);
  238. $sql = 'UPDATE '.$table.' SET '.self::parseSets($sets).self::parseWhere($where).self::parseOrder($order).self::parseLimit($limit);
  239. return self::execute($sql);
  240. }
  241. /**
  242. * Save the value of a certain field
  243. * @access function
  244. * @param string $field The name of the field to be saved
  245. * @param string $value Field value
  246. * @param string $table Data table
  247. * @param string $where Saving conditions
  248. * @param boolean $asString Whether the field value is a string
  249. * @return void
  250. */
  251. static function setField($field, $value, $table, $condition="", $asString=false) {
  252. // 如果有'(' 视为 SQL指令更新 否则 更新字段内容为纯字符串
  253. if(false === strpos($value,'(') || $asString) $value = '"'.$value.'"';
  254. $sql = 'UPDATE '.$table.' SET '.$field.'='.$value.self::parseWhere($condition);
  255. return self::execute($sql);
  256. }
  257. /**
  258. * Delete records
  259. * @access function
  260. * @param mixed $where is conditional Map, Array or String
  261. * @param string $table data table name
  262. * @param string $limit
  263. * @param string $order
  264. * @ return false | integer
  265. */
  266. static function remove($where,$table,$limit='',$order='') {
  267. $sql = 'DELETE FROM '.$table.self::parseWhere($where).self::parseOrder($order).self::parseLimit($limit);
  268. return self::execute($sql);
  269. }
  270. /**
  271. +------------------------------------------------ ----------
  272. * Modify or save data (only for single table operation)
  273. * If there is a primary key ID, it is modified, if there is no primary key ID, it is added
  274. * Modification record:
  275. +---- -------------------------------------------------- ----
  276. * @access function
  277. +--------------------------------------- -------------------
  278. * @param $tabName table name
  279. * @param $aPost $_POST to submit the form
  280. * @param $priId primary key ID
  281. * @param $aNot A field or array to be excluded
  282. * @param $aCustom A customized array, appended to the database and saved
  283. * @param $isExits Whether it already exists exists: true, does not exist: false
  284. +----- -------------------------------------------------- ---
  285. * @return Boolean Whether the modification or saving was successful
  286. +----------------------------------- -----------------------
  287. */
  288. function saveOrUpdate($tabName, $aPost, $priId="", $aNot="", $aCustom="", $isExits=false) {
  289. if(empty($tabName) || !is_array($aPost) || is_int($aNot)) return false;
  290. if(is_string($aNot) && !empty($aNot)) $aNot = array($aNot);
  291. if(is_array($aNot) && is_int(key($aNot))) $aPost = array_diff_key($aPost, array_flip($aNot));
  292. if(is_array($aCustom) && is_string(key($aCustom))) $aPost = array_merge($aPost,$aCustom);
  293. if (empty($priId) && !$isExits) { //新增
  294. $aPost = array_filter($aPost, array($this, 'removeEmpty'));
  295. return self::add($aPost, $tabName);
  296. }else { //修改
  297. return self::update($aPost, $tabName, "id=".$priId);
  298. }
  299. }
  300. /**
  301. * Get the sql statement of the latest query
  302. * @access function
  303. * @param
  304. * @return String The executed SQL
  305. */
  306. static function getLastSql() {
  307. $link = self::$link;
  308. if ( !$link ) return false;
  309. return self::$queryStr;
  310. }
  311. /**
  312. * Get the last inserted ID
  313. * @access function
  314. * @param
  315. * @return integer The last inserted data ID
  316. */
  317. static function getLastInsId(){
  318. $link = self::$link;
  319. if ( !$link ) return false;
  320. return self::$lastInsertId;
  321. }
  322. /**
  323. * Get DB version
  324. * @access function
  325. * @param
  326. * @return string
  327. */
  328. static function getDbVersion(){
  329. $link = self::$link;
  330. if ( !$link ) return false;
  331. return self::$dbVersion;
  332. }
  333. /**
  334. * Get database table information
  335. * @access function
  336. * @return array
  337. */
  338. static function getTables() {
  339. $info = array();
  340. if(self::query("SHOW TABLES")) {
  341. $result = self::getAll();
  342. foreach ($result as $key => $val) {
  343. $info[$key] = current($val);
  344. }
  345. }
  346. return $info;
  347. }
  348. /**
  349. * Get the field information of the data table
  350. * @access function
  351. * @return array
  352. */
  353. static function getFields($tableName) {
  354. // 获取数据库联接
  355. $link = self::$link;
  356. $sql = "SELECT
  357. ORDINAL_POSITION ,COLUMN_NAME, COLUMN_TYPE, DATA_TYPE,
  358. IF(ISNULL(CHARACTER_MAXIMUM_LENGTH), (NUMERIC_PRECISION + NUMERIC_SCALE), CHARACTER_MAXIMUM_LENGTH) AS MAXCHAR,
  359. IS_NULLABLE, COLUMN_DEFAULT, COLUMN_KEY, EXTRA, COLUMN_COMMENT
  360. FROM
  361. INFORMATION_SCHEMA.COLUMNS
  362. WHERE
  363. TABLE_NAME = :tabName AND TABLE_SCHEMA='".DB_NAME."'";
  364. self::$queryStr = sprintf($sql, $tableName);
  365. $sth = $link->prepare($sql);
  366. $sth->bindParam(':tabName', $tableName);
  367. $sth->execute();
  368. $result = $sth->fetchAll(constant('PDO::FETCH_ASSOC'));
  369. $info = array();
  370. foreach ($result as $key => $val) {
  371. $info[$val['COLUMN_NAME']] = array(
  372. 'postion' => $val['ORDINAL_POSITION'],
  373. 'name' => $val['COLUMN_NAME'],
  374. 'type' => $val['COLUMN_TYPE'],
  375. 'd_type' => $val['DATA_TYPE'],
  376. 'length' => $val['MAXCHAR'],
  377. 'notnull' => (strtolower($val['IS_NULLABLE']) == "no"),
  378. 'default' => $val['COLUMN_DEFAULT'],
  379. 'primary' => (strtolower($val['COLUMN_KEY']) == 'pri'),
  380. 'autoInc' => (strtolower($val['EXTRA']) == 'auto_increment'),
  381. 'comment' => $val['COLUMN_COMMENT']
  382. );
  383. }
  384. // 有错误则抛出异常
  385. self::haveErrorThrowException();
  386. return $info;
  387. }
  388. /**
  389. * Close database
  390. * @access function
  391. */
  392. static function close() {
  393. self::$link = null;
  394. }
  395. /**
  396. * SQL command security filtering
  397. * @access function
  398. * @param string $str SQL command
  399. * @return string
  400. */
  401. static function escape_string($str) {
  402. return addslashes($str);
  403. }
  404. /************************/
  405. /* 内部操作方法 */
  406. /************************/
  407. /**
  408. * An exception is thrown if there is an error
  409. * @access function
  410. * @return
  411. */
  412. static function haveErrorThrowException() {
  413. $obj = empty(self::$PDOStatement) ? self::$link : self::$PDOStatement;
  414. $arrError = $obj->errorInfo();
  415. if($arrError[0] !== '00000') { // 有错误信息
  416. self::$error = $arrError[0]."|".$arrError[2]. "
    [ SQL ] : ".self::$queryStr."
    ";
  417. self::throw_exception(self::$error);
  418. return false;
  419. }
  420. //主要针对execute()方法抛出异常
  421. if(self::$queryStr=='')self::throw_exception('Query was empty

    [ SQL语句 ] :');
  422. }
  423. /**
  424. * where analysis
  425. * @access function
  426. * @param mixed $where query conditions
  427. * @return string
  428. */
  429. static function parseWhere($where) {
  430. $whereStr = '';
  431. if(is_string($where) || is_null($where)) {
  432. $whereStr = $where;
  433. }
  434. return empty($whereStr)?'':' WHERE '.$whereStr;
  435. }
  436. /**
  437. * order analysis
  438. * @access function
  439. * @param mixed $order sorting
  440. * @return string
  441. */
  442. static function parseOrder($order) {
  443. $orderStr = '';
  444. if(is_array($order))
  445. $orderStr .= ' ORDER BY '.implode(',', $order);
  446. else if(is_string($order) && !empty($order))
  447. $orderStr .= ' ORDER BY '.$order;
  448. return $orderStr;
  449. }
  450. /**
  451. * limit分析
  452. * @access function
  453. * @param string $limit
  454. * @return string
  455. */
  456. static function parseLimit($limit) {
  457. $limitStr = '';
  458. if(is_array($limit)) {
  459. if(count($limit)>1)
  460. $limitStr .= ' LIMIT '.$limit[0].' , '.$limit[1].' ';
  461. else
  462. $limitStr .= ' LIMIT '.$limit[0].' ';
  463. } else if(is_string($limit) && !empty($limit)) {
  464. $limitStr .= ' LIMIT '.$limit.' ';
  465. }
  466. return $limitStr;
  467. }
  468. /**
  469. * group分析
  470. * @access function
  471. * @param mixed $group
  472. * @return string
  473. */
  474. static function parseGroup($group) {
  475. $groupStr = '';
  476. if(is_array($group))
  477. $groupStr .= ' GROUP BY '.implode(',', $group);
  478. else if(is_string($group) && !empty($group))
  479. $groupStr .= ' GROUP BY '.$group;
  480. return empty($groupStr)?'':$groupStr;
  481. }
  482. /**
  483. * having分析
  484. * @access function
  485. * @param string $having
  486. * @return string
  487. */
  488. static function parseHaving($having) {
  489. $havingStr = '';
  490. if(is_string($having) && !empty($having))
  491. $havingStr .= ' HAVING '.$having;
  492. return $havingStr;
  493. }
  494. /**
  495. * fields分析
  496. * @access function
  497. * @param mixed $fields
  498. * @return string
  499. */
  500. function parseFields($fields) {
  501. if(is_array($fields)) {
  502. array_walk($fields, array($this, 'addSpecialChar'));
  503. $fieldsStr = implode(',', $fields);
  504. }else if(is_string($fields) && !empty($fields)) {
  505. if( false === strpos($fields,'`') ) {
  506. $fields = explode(',',$fields);
  507. array_walk($fields, array($this, 'addSpecialChar'));
  508. $fieldsStr = implode(',', $fields);
  509. }else {
  510. $fieldsStr = $fields;
  511. }
  512. }else $fieldsStr = '*';
  513. return $fieldsStr;
  514. }
  515. /**
  516. * sets analysis, called when updating data
  517. * @access function
  518. * @param mixed $values ​​
  519. * @return string
  520. */
  521. private function parseSets($sets) {
  522. $setsStr = '';
  523. if(is_array($sets)){
  524. foreach ($sets as $key=>$val){
  525. $key = self::addSpecialChar($key);
  526. $val = self::fieldFormat($val);
  527. $setsStr .= "$key = ".$val.",";
  528. }
  529. $setsStr = substr($setsStr,0,-1);
  530. }else if(is_string($sets)) {
  531. $setsStr = $sets;
  532. }
  533. return $setsStr;
  534. }
  535. /**
  536. * Field formatting
  537. * @access function
  538. * @param mixed $value
  539. * @return mixed
  540. */
  541. static function fieldFormat(&$value) {
  542. if(is_int($value)) {
  543. $value = intval($value);
  544. } else if(is_float($value)) {
  545. $value = floatval($value);
  546. } elseif(preg_match('/^(w*(+|-|*|/)?w*)$/i',$value)){
  547. // 支持在字段的值里面直接使用其它字段
  548. // 例如 (score+1) (name) 必须包含括号
  549. $value = $value;
  550. }else if(is_string($value)) {
  551. $value = '''.self::escape_string($value).''';
  552. }
  553. return $value;
  554. }
  555. /**
  556. * Adding ` to field and table names complies with
  557. * to ensure that there are no errors when using keywords in instructions. For mysql
  558. * @access function
  559. * @param mixed $value
  560. * @return mixed
  561. */
  562. static function addSpecialChar(&$value) {
  563. if( '*' == $value || false !== strpos($value,'(') || false !== strpos($value,'.') || false !== strpos($value,'`')) {
  564. //如果包含* 或者 使用了sql方法 则不作处理
  565. } elseif(false === strpos($value,'`') ) {
  566. $value = '`'.trim($value).'`';
  567. }
  568. return $value;
  569. }
  570. /**
  571. +------------------------------------------------ ----------
  572. * Remove empty elements
  573. +--------------------------------- --------------------------
  574. * @access function
  575. +------------------ ----------------------------------------
  576. * @param mixed $value
  577. +- -------------------------------------------------- -------
  578. * @return mixed
  579. +------------------------------------ -----------------------
  580. */
  581. static function removeEmpty($value){
  582. return !empty($value);
  583. }
  584. /**
  585. * Execute query mainly for SELECT, SHOW and other instructions
  586. * @access function
  587. * @param string $sql sql instruction
  588. * @return mixed
  589. */
  590. static function query($sql='') {
  591. // 获取数据库联接
  592. $link = self::$link;
  593. if ( !$link ) return false;
  594. self::$queryStr = $sql;
  595. //释放前次的查询结果
  596. if ( !empty(self::$PDOStatement) ) self::free();
  597. self::$PDOStatement = $link->prepare(self::$queryStr);
  598. $bol = self::$PDOStatement->execute();
  599. // 有错误则抛出异常
  600. self::haveErrorThrowException();
  601. return $bol;
  602. }
  603. /**
  604. * Database operation method
  605. * @access function
  606. * @param string $sql execution statement
  607. * @param boolean $lock whether to lock (default is not locked)
  608. * @return void
  609. public function execute($sql='',$ lock=false) {
  610. if(empty($sql)) $sql = $this->queryStr;
  611. return $this->_execute($sql);
  612. }*/
  613. /**
  614. * Execution statements for INSERT, UPDATE and DELETE
  615. * @access function
  616. * @param string $sql sql command
  617. * @return integer
  618. */
  619. static function execute($sql='') {
  620. // 获取数据库联接
  621. $link = self::$link;
  622. if ( !$link ) return false;
  623. self::$queryStr = $sql;
  624. //释放前次的查询结果
  625. if ( !empty(self::$PDOStatement) ) self::free();
  626. $result = $link->exec(self::$queryStr);
  627. // 有错误则抛出异常
  628. self::haveErrorThrowException();
  629. if ( false === $result) {
  630. return false;
  631. } else {
  632. self::$numRows = $result;
  633. self::$lastInsertId = $link->lastInsertId();
  634. return self::$numRows;
  635. }
  636. }
  637. /**
  638. * Whether it is a database change operation
  639. * @access private
  640. * @param string $query SQL command
  641. * @return boolen If it is a query operation, return false
  642. */
  643. static function isMainIps($query) {
  644. $queryIps = 'INSERT|UPDATE|DELETE|REPLACE|CREATE|DROP|LOAD DATA|SELECT .* INTO|COPY|ALTER|GRANT|REVOKE|LOCK|UNLOCK';
  645. if (preg_match('/^s*"?(' . $queryIps . ')s+/i', $query)) {
  646. return true;
  647. }
  648. return false;
  649. }
  650. /**
  651. * Filter POST submission data
  652. * @access private
  653. * @param mixed $data POST submission data
  654. * @param string $table data table name
  655. * @return mixed $newdata
  656. */
  657. static function filterPost($table,$data) {
  658. $table_column = self::getFields($table);
  659. $newdata=array();
  660. foreach ($table_column as $key=>$val){
  661. if(array_key_exists($key,$data) && ($data[$key])!==''){
  662. $newdata[$key] = $data[$key];
  663. }
  664. }
  665. return $newdata;
  666. }
  667. /**
  668. * Start transaction
  669. * @access function
  670. * @return void
  671. */
  672. static function startTrans() {
  673. //数据rollback 支持
  674. $link = self::$link;
  675. if ( !$link ) return false;
  676. if (self::$transTimes == 0) {
  677. $link->beginTransaction();
  678. }
  679. self::$transTimes++;
  680. return ;
  681. }
  682. /**
  683. * Used for query submission under non-automatic submission status
  684. * @access function
  685. * @return boolen
  686. */
  687. static function commit() {
  688. $link = self::$link;
  689. if ( !$link ) return false;
  690. if (self::$transTimes > 0) {
  691. $result = $link->commit();
  692. self::$transTimes = 0;
  693. if(!$result){
  694. self::throw_exception(self::$error());
  695. return false;
  696. }
  697. }
  698. return true;
  699. }
  700. /**
  701. * Transaction rollback
  702. * @access function
  703. * @return boolen
  704. */
  705. public function rollback() {
  706. $link = self::$link;
  707. if ( !$link ) return false;
  708. if (self::$transTimes > 0) {
  709. $result = $link->rollback();
  710. self::$transTimes = 0;
  711. if(!$result){
  712. self::throw_exception(self::$error());
  713. return false;
  714. }
  715. }
  716. return true;
  717. }

  718. /**

  719. * Error handling
  720. * @access function
  721. * @return void
  722. */
  723. static function throw_exception($err){
  724. echo '
    ERROR:'.$err.'
    ';
  725. }
  726. }

复制代码


Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn