Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
55.56% covered (warning)
55.56%
5 / 9
CRAP
86.08% covered (warning)
86.08%
68 / 79
FunctionAPI
0.00% covered (danger)
0.00%
0 / 1
55.56% covered (warning)
55.56%
5 / 9
40.70
86.08% covered (warning)
86.08%
68 / 79
 setFetchMode($fetchMode)
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 setFetchAssoc()
0.00% covered (danger)
0.00%
0 / 1
2
0.00% covered (danger)
0.00%
0 / 2
 resetFetchMode()
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 __construct(\PDO $pdo, $options = NULL)
100.00% covered (success)
100.00%
1 / 1
7
100.00% covered (success)
100.00%
5 / 5
 __call($functionName, $arguments)
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
3 / 3
 addEvalSubstitution($className, $callbackName)
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
2 / 2
 _performArgumentSubstitution($argument)
0.00% covered (danger)
0.00%
0 / 1
8.70
77.78% covered (warning)
77.78%
14 / 18
 _makeCall($functionName, $arguments, $callType)
0.00% covered (danger)
0.00%
0 / 1
6.01
94.12% covered (success)
94.12%
16 / 17
 _fetch($pdoStatement)
0.00% covered (danger)
0.00%
0 / 1
10.29
85.71% covered (warning)
85.71%
24 / 28
<?php
/**
 * All rights reserved Axel Ancona Esselmann
 */
namespace aae\db {
    /**
     * Maps function calls from application layer to stored function
     * and stored procedure calls in storage layer.
     *
     * @author Axel Ancona Esselmann
     * @package aae\db
     */
    class FunctionAPI /*implements StorageAPI*/ {
        private $_dbName, $_pdo, $_fetchMode = 0, $_debug = false, $_evalSubstitutions = array('aae\app\User' => "getId", 'DateTime' => ["format", 'Y-m-d H:i:s']);
        public $echoQuery = false;
        const RESET           = 0, //notmulrow  | is array | is array or []
              FETCH_NUM_ARRAY = 3, // 0         |  1       |   1
              FETCH_ASS_ARRAY = 1, // 0         |  0       |   1
              FETCH_ONE_ROW   = 4, // 1         |  0       |   0
              //---------------------------------------------------------\\
              IS_ARRAY        = 1, // 0         |  0       |   1
              IS_NUM_ARRAY    = 2; // 0         |  1       |   0
        /**
         * Accepts a bit flag. Combine class constants:
         *     RESET
         *     FETCH_NUM_ARRAY
         *     FETCH_ASS_ARRAY
         *     FETCH_ONE_ROW
         *
         * Make sure to reset the fetch mode when done.
         *
         * @param bitFlag $fetchMode
         */
        public function setFetchMode($fetchMode) {
            $this->_fetchMode = $fetchMode;
        }
        public function setFetchAssoc() {
            $this->_fetchMode = self::FETCH_ASS_ARRAY;
        }
        public function resetFetchMode() {
            $this->_fetchMode = self::RESET;
        }
        public function __construct(\PDO $pdo, $options = NULL) {
            $this->_pdo    = $pdo;
            $this->_dbName = (is_array($options) && array_key_exists("dbName", $options)) ? $options["dbName"]      : NULL;
            $this->_debug  = (is_array($options) && array_key_exists("debug",  $options)) ? (bool)$options["debug"] : false;
            $this->echoQuery = (is_array($options) && array_key_exists("echoQuery",  $options)) ? (bool)$options["echoQuery"] : false;
        }
        public function __call($functionName, $arguments) {
            $callString = ($this->_fetchMode & 1) ? "CALL" : "SELECT";
            $result     = $this->_makeCall($functionName, $arguments, $callString);
            return $result;
        }
        /**
         * An eval substitution allows for passing of objects to the function api. When it is time to pass the instance
         * to the database, an evaluation callback is called on the object to get database representation for the instance.
         * @param string $className    the class name for the object that supports eval substitution
         * @param string $callbackName the name of the function that is called on the instance
         */
        public function addEvalSubstitution($className, $callbackName) {
            $this->_evalSubstitutions[$className] = $callbackName;
        }
        private function _performArgumentSubstitution($argument) {
            if      (is_string($argument)) $argument = $this->_pdo->quote($argument);
            else if (is_null(  $argument)) $argument = "NULL";
            else if ($argument === true)   $argument = 'TRUE';
            else if ($argument === false)  $argument = 'FALSE';
            else if (is_object($argument)) {
                if (array_key_exists(get_class($argument), $this->_evalSubstitutions)) {
                    $evalFunctionName = $this->_evalSubstitutions[get_class($argument)];
                    $className        = $argument;
                    if (is_array($evalFunctionName)) {
                        $evalArguments    = $evalFunctionName;
                        $evalFunctionName = array_shift($evalArguments);
                        $argument         = call_user_func_array([$className, $evalFunctionName], $evalArguments);
                    } else {
                        $argument = $className->$evalFunctionName();
                    }
                    $argument = $this->_performArgumentSubstitution($argument);
                } else throw new StorageAPIException("FunctionAPI has no eval substitution for an object of type '".get_class($argument)."'.", 226151433);
            }
            return $argument;
        }
        private function _makeCall($functionName, $arguments, $callType) {
            $this->_pdo->beginTransaction();
            $dbName = (!is_null($this->_dbName)) ? $this->_dbName."." : "";
            $query  = $callType." ".$dbName.$functionName."(";
            for ($i=0; $i < count($arguments); $i++) {
                $arguments[$i] = $this->_performArgumentSubstitution($arguments[$i]);
            }
            $query .= implode(", ", $arguments).")";
            // @codeCoverageIgnoreStart
            if ($this->echoQuery) echo "Last query:<br /><span style='color:red;font-weight: bold;'>$query</span><br /><br />";
            // @codeCoverageIgnoreEnd
            try {
                $pdoStatement = $this->_pdo->query($query);
                $result       = $this->_fetch($pdoStatement);
            } catch (\Exception $e) {
                if ($this->_debug) {
                    $querySummary = "<br /><br />Last query:<br /><span style='color:red;font-weight: bold;'>$query</span><br />";
                } else $querySummary = "";
                throw new StorageAPIException("Database Error with message:\n".$e->getMessage().$querySummary, 1015141104);
            }
            $pdoStatement->closeCursor();
            $this->_pdo->commit();
            return $result;
        }
        private function _fetch($pdoStatement) {
            if (!$pdoStatement) {
                if ($this->_debug) {
                    $errorinfo = ": <br />\n".implode($this->_pdo->errorInfo(), "<br />\n");
                } else $errorinfo = "";
                throw new StorageAPIException("Database Error".$errorinfo, 1015141105);
            }
            if ($this->_fetchMode & self::IS_ARRAY) {
                $fetchMode = ($this->_fetchMode & self::IS_NUM_ARRAY)
                    ? \PDO::FETCH_NUM
                    : \PDO::FETCH_ASSOC;
                $pdoStatement->setFetchMode($fetchMode);
                $result = array();
                $row = $pdoStatement->fetch();
                if ($this->_fetchMode & self::FETCH_ONE_ROW) {
                    $result = $row;
                } else {
                    if (count($row) == 1 && ($this->_fetchMode & self::IS_NUM_ARRAY)) {
                        while ($row) {
                            $result[] = $row[0];
                            $row      = $pdoStatement->fetch();
                        }
                    } else {
                        while ($row) {
                            $result[] = $row;
                            $row      = $pdoStatement->fetch();
                        }
                    }
                }
            } else {
                $result = $pdoStatement->fetchColumn();
            }
            return $result;
        }
    }
}