SqlFiles.php

<?php

namespace Tlf\BigDb;

/**
 * Provides support for loading .sql files & executing queries within, via LilDb's LilSql class.
 */
trait SqlFiles {

    /**
     *
     * Execute a stored sql query & return the pdo statement
     *
     * @param $query_key the key that points to the stored query
     * @param $params EXPERIMENTAL key/value array to bind to the query, except it is done via `str_replace(':key', $pdo->quote($value));
     * @return Same as `PDO::exec($query)`;
     */
    public function exec(string $query_key, array $params = []): int|false {
        $query = $this->sql[$query_key];
        // echo "\n\n".$query."\n\n";
        foreach ($params as $key=>$value){
            $query = str_replace(":$key", $this->pdo->quote($value), $query);
        }

        return $this->pdo->exec($query);
    }

    /**
     * Execute a stored query, and get an array of orm objects from the results
     *
     * @param $which_query the key for the sql query. Typically it is a `table.query_name` style, where `table` is actually the name of the stored .sql file.
     * @param $binds array<string, mixed> EXPERIMENTAL key/value array to bind to the stored query. Uses pdo_quote & str_replace NOT the built-in `pdo->bind()`
     */
    public function query_rows(string $which_query, array $binds = []): array {
        $this->init_sql();
        if (!isset($this->sql[$which_query])){
            $class = get_class($this);
            throw new \Exception("Query '$which_query' is not stored in this dabase class of '$class'");
        }
        $sql = $this->sql[$which_query];

        foreach ($binds as $key=>$value){
            $sql = str_replace(":$key", $this->pdo->quote($value), $sql);
        }

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

    public function addSqlDir(string $dir, bool $force_recompile = false){
        $this->init_sql();
        if ($force_recompile)$this->check_serialized_file($dir);
        $this->load_queries($dir,'','');
    }

    /**
     * Initialize the sql statements from the compiled sql file, only if `$this->sql` is not set
     *
     * @return void
     *
     * @override to use a different sql storage system than LilDb's LilSql
     */
    public function init_sql(){
        if (isset($this->sql))return;
        $this->load_queries($this->get_root_dir().'/sql/');
        // print_r($this->sql);
    }

    /**
     * Re-generate the compiled sql file 
     * @return void
     */
    public function recompile_sql(){
        $this->check_serialized_file($this->get_root_dir().'/sql/');
        $this->init_sql();
    }

    /**
     *
     * Loads queries from a file containing a serialized array. If the serialized file does not exist, `$dir` will be scanned, queries built, and the file will be created. There is no cache invalidation, so delete the serialized file to regenerate.
     * @param $dir a directory containing `.sql` files with the LilSql formatting
     * @param $namespace file prefix for query files. Ex: pass `"form"` to load files like `"form.create.sql"` and `form.query.sql`
     */
    public function load_queries(string $dir, string $file_prefix = '', string $query_key_prefix = ''){
        if (!isset($this->sql))$this->sql = [];
        $serial_file = $this->get_serial_file_path($dir, $file_prefix);
        $prefix = $query_key_prefix == null ? '' : $query_key_prefix .'.';
        if (file_exists($serial_file)){
            $this->sql = array_merge(
                $this->sql,
                unserialize(file_get_contents($serial_file))
            );
            return;
        }

        // print_r($this->sql);
        // exit;
        
        $ls = new \Tlf\LilSql();
        $ls->load_files($dir, $prefix);
        $ls->serialize($serial_file);
        if (!file_exists($serial_file)){
            throw new \Exception("Failed to write serialized file '$serial_file'");
        }
        return $this->load_queries($dir, $file_prefix);
    }


    public function get_serial_file_path(string $dir, ?string $file_prefix): string {
        $file_prefix = ($file_prefix == null) ? '' : $file_prefix.'.';
        $serial_file = $dir.'/'.$file_prefix.'queries';
        return $serial_file;
    }

    /**
     * Check mtime of serialized file and delete it if
     * @param $dir
     */
    public function check_serialized_file(string $dir, ?string $file_prefix = ''){
        $file_path = $this->get_serial_file_path($dir, $file_prefix);

        if (!file_exists($file_path))return;
        $serial_mtime = filemtime($file_path);
        $dir = dirname($file_path);
        // var_dump($dir);
        // var_dump($serial_mtime);
        foreach (scandir($dir) as $file){
            if (is_dir($dir.'/'.$file))continue;
            // echo 'checked!';
            // var_dump($dir.'/'.$file);
            if (filemtime($dir.'/'.$file) > $serial_mtime) goto delete;
        }

        return;
        delete:
            unlink($file_path);
    }
}