Skip to content
This repository
Browse code

Added new implementation DB layer with PDO

Added DB PDO and compatible dsql layer.
  • Loading branch information...
commit 3aa08b37fea406c6b7fbe520729281ed6b48389d 1 parent 2eb3471
Romans Malinovskis romaninsh authored

Showing 3 changed files with 624 additions and 0 deletions. Show diff stats Hide diff stats

  1. +100 0 lib/DB.php
  2. +518 0 lib/DB/dsql.php
  3. +6 0 lib/Exception/DB.php
100 lib/DB.php
... ... @@ -0,0 +1,100 @@
  1 +<?php
  2 +/***********************************************************
  3 + Implementation of PDO support in Agile Toolkit
  4 +
  5 + Reference:
  6 + http://agiletoolkit.org/doc/pdo
  7 +
  8 + **ATK4*****************************************************
  9 + This file is part of Agile Toolkit 4
  10 + http://agiletoolkit.org
  11 +
  12 + (c) 2008-2011 Agile Technologies Ireland Limited
  13 + Distributed under Affero General Public License v3
  14 +
  15 + If you are using this file in YOUR web software, you
  16 + must make your make source code for YOUR web software
  17 + public.
  18 +
  19 + See LICENSE.txt for more information
  20 +
  21 + You can obtain non-public copy of Agile Toolkit 4 at
  22 + http://agiletoolkit.org/commercial
  23 +
  24 + *****************************************************ATK4**/
  25 +class DB extends AbstractController {
  26 +
  27 + public $dbh=null;
  28 +
  29 + public $table_prefix=null;
  30 +
  31 + /* when queries are executed, their statements are cached for later re-use */
  32 + public $query_cache=array();
  33 +
  34 + /* Backticks will be added around all fields. Set this to '' if you prefer cleaner queries */
  35 + public $bt='`';
  36 + function bt($s){
  37 + if(is_array($s)){
  38 + $out=array();
  39 + foreach($s as $ss){
  40 + $out[]=$this->bt($ss);
  41 + }
  42 + return $out;
  43 + }
  44 +
  45 + if(!$this->bt
  46 + || is_object($s)
  47 + || $s=='*'
  48 + || strpos($s,'(')!==false
  49 + || strpos($s,$this->bt)!==false
  50 + )return $s;
  51 +
  52 + if(strpos($s,'.')!==false){
  53 + $s=explode('.',$s);
  54 + return implode('.',$this->bt($s));
  55 + }
  56 + return $this->bt.$s.$this->bt;
  57 + }
  58 + /* Initialization and connection to the database. Reads config from file. */
  59 + function init(){
  60 + parent::init();
  61 +
  62 + $this->dbh=new PDO($this->api->getConfig('pdo/dsn'),
  63 + $this->api->getConfig('pdo/user','root'),
  64 + $this->api->getConfig('pdo/password','root'),
  65 + $this->api->getConfig('pdo/options',array(PDO::ATTR_PERSISTENT => true))
  66 + );
  67 + $this->dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
  68 +
  69 + $this->table_prefix=$this->api->getConfig('pdo/table_prefix','');
  70 +
  71 + }
  72 + /* Returns Dynamic Query object compatible with this database driver (PDO) */
  73 + function dsql(){
  74 + return $this->add('DB_dsql');
  75 + }
  76 + /* Query database with SQL statement */
  77 + function query($query, $params=array()){
  78 +
  79 + if(is_object($query))$query=(string)$query;
  80 +
  81 + // Look in query cache
  82 + if(isset($this->query_cache[$query])){
  83 + $statement = $this->query_cache[$query];
  84 + }else{
  85 + $statement = $this->dbh->prepare($query);
  86 + $this->query_cache[$query]=$statement;
  87 + }
  88 + $statement -> execute($params);
  89 + return $statement;
  90 + }
  91 + function getOne($query, $params=array()){
  92 + $res=$this->query($query,$params)->fetch();
  93 + return $res[0];
  94 + }
  95 + /* Returns last ID after insert. Driver-dependant. Redefine if needed. */
  96 + function lastID($statement,$table){
  97 + // TODO: add support for postgreSQL and other databases
  98 + return $this->dbh->lastInsertId();
  99 + }
  100 +}
518 lib/DB/dsql.php
... ... @@ -0,0 +1,518 @@
  1 +<?php // vim:ts=4:sw=4:et:fdm=marker
  2 +/***********************************************************
  3 + Implementation of PDO-compatible dynamic queries
  4 +
  5 + Reference:
  6 + http://agiletoolkit.org/doc/dq
  7 +
  8 + **ATK4*****************************************************
  9 + This file is part of Agile Toolkit 4
  10 + http://agiletoolkit.org
  11 +
  12 + (c) 2008-2011 Agile Technologies Ireland Limited
  13 + Distributed under Affero General Public License v3
  14 +
  15 + If you are using this file in YOUR web software, you
  16 + must make your make source code for YOUR web software
  17 + public.
  18 +
  19 + See LICENSE.txt for more information
  20 +
  21 + You can obtain non-public copy of Agile Toolkit 4 at
  22 + http://agiletoolkit.org/commercial
  23 +
  24 + *****************************************************ATK4**/
  25 +class DB_dsql extends AbstractModel {
  26 + /* Array with different type of data */
  27 + public $args=array();
  28 +
  29 + /* List of PDO parametical arguments for a query */
  30 + public $params=array();
  31 +
  32 + /* Statement, if query is done */
  33 + public $stmt;
  34 +
  35 + /* Expression to use when converting to string */
  36 + public $expr=null;
  37 +
  38 + public $main_table=null;
  39 +
  40 + public $main_query=null; // points to main query for subqueries
  41 + public $param_base='a'; // for un-linked subqueries, set a different param_base
  42 +
  43 +
  44 + public $default_exception='Exception_DB';
  45 +
  46 + // {{{ Generic stuff
  47 + function _unique(&$array,$desired=null){
  48 + $desired=preg_replace('/[^a-zA-Z0-9:]/','_',$desired);
  49 + $desired=parent::_unique($array,$desired);
  50 + return $desired;
  51 + }
  52 + function __toString(){
  53 + if($this->expr)return $this->parseTemplate($this->expr);
  54 + return $this->select();
  55 + }
  56 + /* Escapes value by using parameter */
  57 + function escape($val){
  58 + if(is_array($val)){
  59 + $out=array();
  60 + foreach($val as $v){
  61 + $out[]=$this->escape($v);
  62 + }
  63 + return $out;
  64 + }
  65 + $name=':'.$this->param_base;
  66 + $name=$this->_unique($this->params,$name);
  67 + $this->params[$name]=$val;
  68 + return $name;
  69 + }
  70 + function paramBase($param_base){
  71 + $this->param_base=$param_base;
  72 + return $this;
  73 + }
  74 + /* Creates subquery */
  75 + function dsql(){
  76 + $d=$this->owner->dsql();
  77 + $d->params = &$this->params;
  78 + $d->main_query=$this->main_query?$this->main_query:$this;
  79 + return $d;
  80 + }
  81 + /* Inserting one query into another and merging parameters */
  82 + function consume($dsql){
  83 + if((!$this->main_query && !$dsql->main_query) || ($dsql->main_query != $this && $dsql->main_query !=
  84 + $this->main_query)){
  85 + // are we using any parameters
  86 + if($dsql->params && $this->params
  87 + && $dsql->param_base == $this->param_base){
  88 + // ought to have param clash!
  89 + throw $this->exception('Subquery is not cloned from us, is using same param_base for parametrical variables,
  90 + therefore unable to consume');
  91 + }
  92 + $this->params=array_merge($this->params,$dsql->params);
  93 + }
  94 + $ret='('.$dsql.')';
  95 + return $ret;
  96 + }
  97 + /* Removes definition for argument type */
  98 + function del($param){
  99 + if($param=='limit'){
  100 + unset($this->args['limit']);
  101 + return $this;
  102 + }
  103 + $this->args[$param]=array();
  104 + return $this;
  105 + }
  106 + function init(){
  107 + parent::init();
  108 + $this->del('fields')->del('table')->del('options');
  109 + }
  110 + // }}}
  111 +
  112 + // {{{ Dynamic Query Definition methods
  113 +
  114 + /* Sets template for toString() function */
  115 + function expr($expr){
  116 + $c=clone $this;
  117 + return $c->useExpr($expr);
  118 + }
  119 + function useExpr($expr){
  120 + $this->expr=$expr;
  121 + return $this;
  122 + }
  123 + /* Specifies table to use for this dynamic query */
  124 + function table($table){
  125 + if(is_array($table)){
  126 + foreach($table as $t){
  127 + $this->table($t);
  128 + }
  129 + return $this;
  130 + }
  131 + $this->args['table'][]=
  132 + $this->owner->bt($this->owner->table_prefix.$table);
  133 +
  134 + if(!$this->main_table)$this->main_table=$this->owner->table_prefix.$table;
  135 +
  136 + return $this;
  137 + }
  138 + /* Defines query option */
  139 + function option($option){
  140 + if(!is_array($option))$option=array($option);
  141 + if(!isset($this->args['options']))$this->args['options']=array();
  142 + $this->args['options']=array_merge($this->args['options'],$option);
  143 + return $this;
  144 + }
  145 + /* Check if option was defined */
  146 + function hasOption($option){
  147 + return in_array($option,$this->args['options']);
  148 + }
  149 + /* Limit row result */
  150 + function limit($cnt,$shift=0){
  151 + $this->args['limit']=array(
  152 + 'cnt'=>$cnt,
  153 + 'shift'=>$shift
  154 + );
  155 + return $this;
  156 + }
  157 + /* Remove result limit */
  158 + function unLimit(){
  159 + $this->unset($this->args['limit']);
  160 + return $this;
  161 + }
  162 + function args($args){
  163 + if(is_array($args)){
  164 + foreach($args as $arg){
  165 + $this->args['args'][]=$this->escape($arg);
  166 + }
  167 + }else{
  168 + $this->args['args'][]=$this->escape($args);
  169 + }
  170 + return $this;
  171 + }
  172 + /* Adds one (string) or several fields (array) to the query. If $field is object, $table is alias */
  173 + function field($field,$table=null,$alias=null) {
  174 + if(is_array($field)){
  175 + foreach($field as $f){
  176 + $this->field($f,$table);
  177 + }
  178 + return $this;
  179 + }
  180 +
  181 + if(is_object($field)){
  182 + $field=$this->consume($field);
  183 + if($table)$field.=' as '.$this->owner->bt($table);
  184 + }elseif(isset($table)){
  185 + $field=
  186 + $this->owner->bt($this->owner->table_prefix.$table).
  187 + '.'.
  188 + $this->owner->bt($field);
  189 + }else{
  190 + $field=$this->owner->bt($field);
  191 + }
  192 +
  193 +
  194 + $this->args['fields'][]=$field;
  195 + return $this;
  196 + }
  197 + /* Never pass variable as $field! (esp if user can control it) */
  198 + function having($field,$cond=null,$value=null){
  199 + return $this->where($field,$cond,$value,'having');
  200 + }
  201 + function where($field,$cond=null,$value=null,$type='where'){
  202 + /*
  203 + 1. where('id',2); // equals
  204 + 2. where('id','>',2); // explicit condition
  205 + 3. where(array('id',2),('id',3)); // or
  206 + 4. where('id',array(2,3)); // in
  207 + 5. where($dsql,4); // subquery
  208 + 6. where('id',$dsql); // in subquery
  209 + 7. where('id=ord'); // full statement. avoid
  210 + */
  211 + $ors=array();
  212 + if(is_array($field)) foreach($field as $or){
  213 + if(!is_array($or))throw $this->exception('OR syntax invalid')->addMoreInfo('arg',$field);
  214 + $ors[]=$this->_where($or[0],@$or[1],@$or[2]);
  215 + }else{
  216 + $ors[]=$this->_where($field,$cond,$value);
  217 + }
  218 + $this->args[$type][]=implode(' or ',$ors);
  219 + return $this;
  220 + }
  221 + function _where($field,$cond=null,$value=null){
  222 + if(is_object($field)){
  223 + if(is_null($cond) && is_null($value)){
  224 + $this->args[$cond][]=$field;
  225 + return $this;
  226 + }
  227 + }else{
  228 + if(is_null($cond) && is_null($value)){
  229 + throw $this->exception('Use expression syntax with one-argument calls')
  230 + ->addMoreInfo('arg',$field);
  231 + }
  232 + }
  233 +
  234 +
  235 + if($value===null){
  236 + $value=$cond;$cond=null;
  237 + }
  238 +
  239 + /* guess condition as it might be in $field */
  240 + if($cond===null && !is_object($field)){
  241 +
  242 + preg_match('/^([^ <>!=]*)([><!=]*|( *(not|in|like))*) *$/',$field,$matches);
  243 + $field=$matches[1];
  244 + $cond=$matches[2];
  245 + }
  246 +
  247 + if(!$cond){
  248 + if(is_array($value)){
  249 + $cond='in';
  250 + }else{
  251 + $cond='=';
  252 + }
  253 + }
  254 +
  255 + if(is_object($field))$field=$this->consume($field);
  256 +
  257 + if(is_array($value)){
  258 + $value='('.implode(',',$this->escape($value)).')';
  259 + }elseif(is_object($value)){
  260 + $value='('.$value.')';
  261 + }else{
  262 + $value=$this->escape($value);
  263 + }
  264 +
  265 + $where=$this->owner->bt($field). ' '.trim($cond). ' '. $value;
  266 +
  267 + return $where;
  268 + }
  269 + function join($table,$on,$type='inner'){
  270 + if($type=='table'){
  271 + return $this->table($table)
  272 + ->where($this->expr($on));
  273 +
  274 +
  275 +
  276 + }
  277 + $this->args['join'][$table]="$type join ".DTP.$table." on $on";
  278 + return $this;
  279 + }
  280 +
  281 + // }}}
  282 +
  283 + // {{{ Statement templates and interfaces
  284 +
  285 + /* Generates and returns SELECT statement */
  286 + function select(){
  287 + return $this->parseTemplate("select [options] [fields] [from] [table] [join] [where] [group] [having] [order] [limit]");
  288 + }
  289 +
  290 + // }}}
  291 +
  292 + // {{{ More complex query generations and specific cases
  293 +
  294 + function do_select(){
  295 + try {
  296 + return $this->stmt=$this->owner->query($q=$this->select(),$this->params);
  297 + }catch(PDOException $e){
  298 + throw $this->exception('SELECT statement failed')
  299 + ->addPDOException($e)
  300 + ->addMoreInfo('params',$this->params)
  301 + ->addMoreInfo('query',$q);
  302 + }
  303 + }
  304 + function execute(){
  305 + try {
  306 + return $this->stmt=$this->owner->query($q=($this->expr?$this:$this->select()),$this->params);
  307 + }catch(PDOException $e){
  308 + throw $this->exception('custom statement failed')
  309 + ->addPDOException($e)
  310 + ->addMoreInfo('params',$this->params)
  311 + ->addMoreInfo('query',$q);
  312 + }
  313 + }
  314 + function get($mode){
  315 + return $this->execute()->fetch($mode);
  316 + }
  317 + function do_getOne(){ return $this->getOne(); }
  318 + function getOne(){
  319 + $res=$this->execute()->fetch();
  320 + return $res[0];
  321 + }
  322 + function do_getAll(){ return $this->getAll(); }
  323 + function getAll(){
  324 + return $this->execute()->fetchAll(PDO::FETCH_ASSOC);
  325 +
  326 + $data=array();
  327 + foreach($this->execute() as $row){
  328 + $data[]=$row;
  329 + }
  330 + return $data;
  331 + }
  332 +
  333 +
  334 + function do_getRow(){ return $this->get(PDO::FETCH_NUM); }
  335 + function getRow(){ return $this->get(PDO::FETCH_NUM); }
  336 + function do_getHash(){ return $this->get(PDO::FETCH_ASSOC); }
  337 + function getHash(){ return $this->get(PDO::FETCH_ASSOC); }
  338 +
  339 + function fetch(){
  340 + if($this->stmt)return $this->stmt->fetch();
  341 + return $this->execute()->fetch();
  342 + }
  343 + function calc_found_rows(){
  344 + // if not mysql return;
  345 + return $this->option('SQL_CALC_FOUND_ROWS');
  346 + }
  347 + function foundRows(){
  348 + if($this->hasOption('SQL_CALC_FOUND_ROWS')){
  349 + return $this->owner->getOne('select found_rows()');
  350 + }
  351 + /* db-compatibl way: */
  352 + $c=clone $this;
  353 + $c->del('limit');
  354 + $c->del('fields');
  355 + $c->field('count(*)');
  356 + return $c->do_getOne();
  357 + }
  358 + // }}}
  359 +
  360 + // {{{ Query Generation heavy-duty generation code
  361 +
  362 + /* [private] Generates values for different tokens in the template */
  363 + function getArgs($required){
  364 + $args=array();
  365 +
  366 + // {{{ table name
  367 + if(isset($required['table'])) {
  368 + $args['table']=join(',',$this->args['table']);
  369 + } // }}}
  370 +
  371 + // {{{ conditional "from"
  372 + if(isset($required['from'])) {
  373 + if($args['table']){
  374 + $args['from']='from';
  375 + } else {
  376 + $args['from']='';
  377 + }
  378 + } // }}}
  379 +
  380 + // {{{ fields data
  381 + if(isset($required['fields'])) {
  382 + // comma separated fields, such as for select
  383 + if(!$this->args['fields'])$this->args['fields']=array('*');
  384 + $args['fields']=join(', ', $this->args['fields']);
  385 + } // }}}
  386 +
  387 + // {{{ options (MySQL)
  388 + if(isset($required['options'])&&isset($this->args['options'])){
  389 + $args['options']=join(' ',$this->args['options']);
  390 + } // }}}
  391 +
  392 + // {{{ set (for updates)
  393 + if(isset($required['set'])) {
  394 + $set = array();
  395 + if(!$this->args['set']) {
  396 + return $this->fatal('You should call $dq->set() before requesting update');
  397 + }
  398 + /*
  399 + foreach($this->args['set'] as $key=>$val) {
  400 + if(is_int($key)) {
  401 + $set[]="$val";
  402 + }else{
  403 + $set[]="`$key`=$val";
  404 + }
  405 + }
  406 + */
  407 + $args['set']=join(', ', $args['set']);
  408 + } // }}}
  409 +
  410 + // {{{ set (for instert)
  411 + if(isset($required['set_fields']) || isset($required['set_value'])) {
  412 + $sf = $sv = array();
  413 + if(!$this->args['set']) {
  414 + return $this->fatal('You should call $dq->set() before requesting update',2);
  415 + }
  416 + foreach($this->args['set'] as $key=>$val) {
  417 + if(is_numeric($key)){
  418 + list($sf[],$sv[])=explode('=',$val,2);
  419 + continue;
  420 + }
  421 + $sf[]="`$key`";
  422 + $sv[]=$val;
  423 + }
  424 + $args['set_fields']=join(', ', $sf);
  425 + $args['set_value']=join(', ', $sv);
  426 + } // }}}
  427 +
  428 + // {{{ joins to other tables
  429 + if(isset($required['join'])&&isset($this->args['join'])) {
  430 + $args['join']=join(' ', $this->args['join']);
  431 + } // }}}
  432 +
  433 + // {{{ where clause
  434 + if(isset($required['where'])&&isset($this->args['where'])) {
  435 + $args['where'] = "where (".join(') and (', $this->args['where']).")";
  436 + } // }}}
  437 +
  438 + // {{{ having clause
  439 + if(isset($required['having'])&&isset($this->args['having'])) {
  440 + $args['having'] = "having (".join(') and (', $this->args['having']).")";
  441 + } // }}}
  442 +
  443 + // {{{ order clause
  444 + if(isset($required['order'])&&isset($this->args['order'])) {
  445 + $args['order'] = "order by ".join(', ', $this->args['order']);
  446 + } // }}}
  447 +
  448 + // {{{ grouping
  449 + if(isset($required['group'])&&isset($this->args['group'])) {
  450 + $args['group'] = "group by ".join(', ',$this->args['group']);
  451 + } // }}}
  452 +
  453 + // {{{ limit
  454 + if(isset($required['limit'])&&isset($this->args['limit'])) {
  455 + $args['limit'] = "limit ".$this->args['limit']['shift'].", ".$this->args['limit']['cnt'];
  456 + } // }}}
  457 +
  458 + foreach($required as $key=>$junk){
  459 + if(!$args[$key] && is_array($this->args[$key]))$args[$key]=join(', ',$this->args[$key]);
  460 + }
  461 +
  462 + return $args;
  463 + }
  464 + /* Generic query builder funciton. Provided with template it will fill-in the data */
  465 + function parseTemplate($template) {
  466 + $parts = explode('[', $template);
  467 + $required = array();
  468 +
  469 + // 1st part is not a variable
  470 + $result = array(array_shift($parts));
  471 + foreach($parts as $part) {
  472 + list($keyword, $rest)=explode(']', $part);
  473 + $result[] = array($keyword); $required[$keyword]=true;
  474 + $result[] = $rest;
  475 + }
  476 + // now parts array contains strings and array of string, let's request
  477 + // for required arguments
  478 +
  479 + $dd='';
  480 + $args = $this->getArgs($required);
  481 + $dd.='<ul class="atk-sqldump">';
  482 +
  483 + // now when we know all data, let's assemble resulting string
  484 + foreach($result as $key => $part) {
  485 + if(is_array($part)) {
  486 + $p=$part[0];
  487 + if(isset($args[$p])){
  488 + $result[$key]=$args[$p];
  489 +
  490 + if(isset($this->args[$p]) && $a=$this->args[$p]){
  491 + if($p=='set'){
  492 + foreach($a as $key=>&$val){
  493 + $val=$key.'='.$val;
  494 + }
  495 + }
  496 + if(is_array($a)){
  497 + sort($a);
  498 + $dd.="<li><b>".$part[0]."</b> <ul><li>"
  499 + .join('</li><li>',$a).'</li></ul>';
  500 + }else{
  501 + $dd.="<li><b>".$part[0]."</b> $a </li>";
  502 + }
  503 + }else $dd.="<li>".$args[$p]."</li>";
  504 + }else{
  505 + $result[$key]=null;
  506 + }
  507 + }elseif($part=trim($part,' ()'))$dd.='<li><b>'.$part.'</b></li>';
  508 + }
  509 + $dd.="</ul>";
  510 + /*
  511 + if($this->debug){
  512 + echo '<font color=blue>'.htmlentities(join('',$result)).'</font>'.$dd;
  513 + }
  514 + */
  515 + return join('', $result);
  516 + }
  517 + // }}}
  518 +}
6 lib/Exception/DB.php
... ... @@ -0,0 +1,6 @@
  1 +<?php
  2 +class Exception_DB extends BaseException {
  3 + function addPDOException($e){
  4 + return $this->addMoreInfo('pdo_error',$e->getMessage());
  5 + }
  6 +}

0 comments on commit 3aa08b3

Please sign in to comment.
Something went wrong with that request. Please try again.