DataReader.php

<?php

namespace Phad;

/**
 * Grants access to an item's node & loads item list
 */
class DataReader {


    protected ?\PDO $pdo;
    protected object $ItemInfo;
    protected array $data_node_info;
    protected ?\Phad $phad;
    
    public function __construct(array $data_node_info, object $ItemInfo, ?\PDO $pdo=null, ?\Phad $phad=null){
        $this->ItemInfo = $ItemInfo;
        $this->data_node_info = $data_node_info;
        $this->pdo = $pdo;
        $this->phad = $phad;
    }

    /**
     * Query for a row with id matching `$_GET['id']`, return a `Phad\BlackHole` object if no id passed, or return `$_POST` if mode is `FORM_SUBMIT`.
     *
     * @return array containing rows. Each row is an array.
     */
    public function get_default_form_data(){

        if ($this->ItemInfo->mode==\Phad\Blocks::FORM_SUBMIT){
            return [$_POST];
        } else if (!isset($_GET['id'])){
            return [ [ '_object'=> new \Phad\BlackHole() ] ];
        }

        $table = strtolower($this->ItemInfo->name);
        $stmt = $this->pdo->prepare("SELECT * FROM `{$table}` WHERE `id` = :id");
        $stmt->bindParam(':id', $_GET['id'], \PDO::PARAM_INT);
        $stmt->execute();
        $rows = $stmt->fetchAll(\PDO::FETCH_ASSOC);
        return $rows;
    }

    /**
     * Execute the data loader declared on the data node, and return the result.
     */
    public function get_data_loader_rows(){
        $key = $this->data_node_info['data_loader'];
        if (!isset($this->phad->data_loaders[$key])){
            throw new \Exception("There is no data loader for key '$key'. Set `\$phad->data_loaders['$key']` to a callable that accepts (array \$data_node_info, object \$ItemInfo, \Phad \$phad) & returns an array of rows.");
        }
        $rows = ($this->phad->data_loaders[$key])($this->data_node_info, $this->ItemInfo, $this->phad);
        return $rows;
    }

    /**
     * Build a query based on the data node, execute it, and return the rows
     *
     */
    public function get_queried_rows(){
        $binds = null;
        $sql = static::build_sql(strtolower($this->ItemInfo->name), $this->data_node_info, $this->ItemInfo->args, $binds);

        $stmt = $this->pdo->prepare($sql);
        $stmt->execute($binds);
        $rows = $stmt->fetchAll(\PDO::FETCH_ASSOC);
        return $rows;
    }

    /**
     * Process the ItemInfo and Data Node, and return the appropriate data.
     *
     * @return array of rows. (*each row is an array*)
     */
    public function get_rows(): array {
        $item_name = $this->ItemInfo->name;

        $args = $this->ItemInfo->args;

        // Return data explicitly passed in
        if (isset($args[$item_name.'List'])){
            return $args[$item_name.'List'];
        } else if (isset($args[$item_name])){
            return [ $args[$item_name] ];
        } else if (isset($args['_object'])){
            return [ ['_object' => $args['_object']] ];
        }

        // type is one of null, 'default', 'black_hole'

        
        $data_type = $this->data_node_info['type'];

        if ($data_type == null){
            if (isset($this->data_node_info['data_loader'])){
                $data_type = 'data_loader';
            } else {
                $data_type = 'mysql';
            }
        }

        $item_type = $this->ItemInfo->type;

        switch ($data_type){
            case \Phad\enum\DataTypes::DEFAULT->value:
               if ($item_type=='form'){
                   return $this->get_default_form_data();
               }
               return [];
            case \Phad\enum\DataTypes::DATA_LOADER->value:
                return $this->get_data_loader_rows();
            case \Phad\enum\DataTypes::BLACK_HOLE->value:
                return [ [ '_object'=> new \Phad\BlackHole() ] ];
            case \Phad\enum\DataTypes::MYSQL->value:
                return $this->get_queried_rows();
            default:
                throw new \Exception(sprintf(\Phad\Errors::DATA_TYPE_NOT_SUPPORTED, $data_type));
        }


    }

    /**
     * Build an SQL query and an array of bindable key/value pairs to pass to `PDOSatement::execute($binds)`
     *
     * If your SQL contains bindable params like `:get.id`, then this will be filled by `$_GET['id']`. Bindable params like `:some_name` are filled by `$args['some_name']`
     *
     * @param $table_name string the SQL Table name
     * @param $data_node_info array an array representation of a data node listing 'sql' as a raw query or SQL features 'cols', 'where', 'limit', 'orderby', and 'join'.
     * @param $args array of arguments to bind to the query.
     * @param &$binds Creates an array you can pass to `PDOStatement::execute($binds)` Values are filled from `$args` or set `null` if not present in `$args`. 
     */
    static public function build_sql(string $table_name, array $data_node_info, array $args, &$binds){
        $table = "`$table_name`";
        $binds = [];

        if (isset($data_node_info['sql'])){
            $sql = $data_node_info['sql'];
        } else {
            // $cols = '*';
            $cols = empty($data_node_info['cols']) ? '*' : $data_node_info['cols'];
            $where = empty($data_node_info['where']) ? ' ' : "\nWHERE ".$data_node_info['where'].' ';
            $orderby = empty($data_node_info['orderby']) ? '' : "\nORDER BY ".$data_node_info['orderby'].' ';

            $join = empty($data_node_info['join']) ? '' : "\nJOIN ".$data_node_info['join'].' ';

            if (isset($data_node_info['paginate'])){
                $size = intval($data_node_info['paginate']);
                $current_page = intval($_GET['page']??0);
                $limit_start = $size * $current_page;
                $limit = "\nLIMIT ".$limit_start.", ".$size." ";
            } else if (isset($data_node_info['limit'])){
                $limit =  empty($data_node_info['limit']) ? '' : "\nLIMIT ".$data_node_info['limit'].' ';
            } else {
                $limit = '';
            }


            $sql = "SELECT {$cols} FROM {$table} {$join}{$where}{$orderby}{$limit}";
        }

        preg_match_all('/ \:([a-zA-Z\_\.]+)(\r|\n|\s|$)/', $sql.' ', $matches);
        foreach ($matches[1] as $col){
            $parts = explode('.', $col);
            if (count($parts)==2&&$parts[0]=='get'){
                $key = str_replace('.','_', $col);
                $sql = str_replace($col, $key, $sql);
                $col = $key;
                $args[$key] = $_GET[$parts[1]] ?? null;
            }
            $binds[':'.$col] = $args[$col] ?? null;
        }
        return $sql;
    }

}