From 145d30bf40e4269009fa16020e9cb7cef35c7d02 Mon Sep 17 00:00:00 2001
From: Igor V Belousov <igor@belousovv.ru>
Date: Fri, 8 Nov 2019 00:10:57 +0300
Subject: [PATCH] 191108 0010

---
 src/core/Model.php | 176 ++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 149 insertions(+), 27 deletions(-)

diff --git a/src/core/Model.php b/src/core/Model.php
index 55eb0f6..dc4c410 100644
--- a/src/core/Model.php
+++ b/src/core/Model.php
@@ -2,10 +2,11 @@
 
 namespace MyApp\Core;
 
-use PDOException;
 use ReflectionClass;
 use ReflectionProperty;
 use PDO;
+use PDOException;
+use Exception;
 
 class Model
 {
@@ -23,8 +24,10 @@ class Model
         global $app;
         $this->app = $app;
 
-        if ( ! is_array($tableMap)) {
+        if (!is_array($tableMap)) {
             $this->setInternalTableMap();
+        } else {
+            $this->internalTableMap = $tableMap;
         }
 
         if ('db' === $this->getModelType()) {
@@ -32,27 +35,27 @@ class Model
         }
     }
 
+    /**
+     * Create PDO Object.
+     *
+     * @throws PDOException
+     */
     private function initDb(): void
     {
         if (is_null($this->app->db)) {
-            try {
-                $this->app->db = new PDO(
-                    'mysql:host=mysql;dbname='
-                    . $this->app->config->getDbName()
-                    . ';port='
-                    . $this->app->config->getDbPort(),
-                    $this->app->config->getDbUser(),
-                    $this->app->config->getDbPassword(),
-                    array(
-                        PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES UTF8',
-                        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_OBJ,
-                        PDO::ATTR_ERRMODE            => true,
-                        PDO::ATTR_PERSISTENT         => true,
-                    ));
-            } catch (PDOException $e) {
-                echo 'Подключение не удалось: ' . $e->getMessage();
-            }
-
+            $this->app->db = new PDO(
+                'mysql:host=mysql;dbname='
+                .$this->app->config->getDbName()
+                .';port='
+                .$this->app->config->getDbPort(),
+                $this->app->config->getDbUser(),
+                $this->app->config->getDbPassword(),
+                array(
+                    PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES UTF8',
+                    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_OBJ,
+                    PDO::ATTR_ERRMODE => true,
+                    PDO::ATTR_PERSISTENT => true,
+                ));
         }
     }
 
@@ -64,7 +67,7 @@ class Model
         $tableMap = ['type' => 'db'];
 
         $refClass = new ReflectionClass(get_called_class());
-        $tags     = $app->parseTagsFromComment($refClass->getDocComment(),
+        $tags = $app->parseTagsFromComment($refClass->getDocComment(),
             'TableName');
         if ($tags) {
             if (array_key_exists('TableName', $tags)) {
@@ -93,17 +96,136 @@ class Model
             $this->internalTableMap = array_key_exists('id',
                 $tableMap) ? $tableMap : ['type' => 'other'];
         }
-        if ( ! is_array($this->internalTableMap)) {
+        if (!is_array($this->internalTableMap)) {
             $this->internalTableMap = ['type' => 'other'];
         }
     }
 
-    public static function find()
-    {
-    }
+    /**
+     * Find records from table.
+     *
+     * @param int[]|null  $records An array of records to search
+     * @param string|null $column  Column name for search. Default ColumnOption 'id'
+     * @param string|null $where   Selecting results from the table
+     *                             by condition. Use property name of object
+     *                             Example "{groups} = :groups_id AND {groups} IS NULL"
+     * @param array       $bind    Array of a values for bindParam. Work if
+     *                             $where not null.
+     *                             Example [ ":groups_id" => 0 ]
+     * @param string      $order
+     * @param string      $limit
+     * @param string      $mode    Mode of select records. 'in' or 'like'.
+     *                             Default is 'in'.
+     *
+     * @return array|null
+     *
+     * @throws Exception
+     */
+    public static function find(
+        array $records = null,
+        string $column = null,
+        string $where = null,
+        array $bind = [],
+        string $order = '',
+        string $limit = '',
+        string $mode = 'in'
+    ): ?array {
+        $model_name = get_called_class();
+        /* @var Model $model */
+        $model = new $model_name();
 
-    public static function findOne()
-    {
+        $internal_table_map = $model->internalTableMap;
+        if ('db' === $internal_table_map['type']) {
+            $select_fields = '`'.implode('`,`',
+                    array_values($internal_table_map['fields'])).'`';
+
+            $order_sql = '';
+            if (!empty($order)) {
+                $order_sql = "ORDER BY $order";
+            }
+
+            $limit_sql = '';
+            if (!empty($limit)) {
+                $limit_sql = "LIMIT $limit ";
+            }
+
+            $where_sql = '';
+            $mode_sql = '';
+            if (is_null($where)) {
+                if (is_null($records) || !count($records)) {
+                    throw new Exception('$records unspecified.');
+                }
+
+                if (is_null($column)) {
+                    $column_name = "`{$internal_table_map['id']}`";
+                } else {
+                    if (!array_key_exists($column,
+                        $internal_table_map['fields'])) {
+                        throw new Exception('$column not find in model');
+                    }
+                    $column_name = "`{$internal_table_map['fields'][$column]}`";
+                }
+
+                if ('in' === $mode) {
+                    $mode_sql = $column_name.' IN (:mode_param_'.implode(',:mode_param_',
+                            array_keys($records)).') ';
+                }
+                if ('like' === $mode) {
+                    $mode_sql = "{$column_name} LIKE :mode_param_".implode(" OR {$column_name} LIKE :mode_param_",
+                            array_keys($records));
+                }
+
+                if (empty($mode_sql)) {
+                    throw new Exception('Bad $mode');
+                }
+                $query = $model->app->db->prepare("SELECT $select_fields FROM `{$internal_table_map['name']}` WHERE $mode_sql $order_sql $limit_sql;");
+            } else {
+                $query = $model->app->db->prepare("SELECT $select_fields FROM `{$internal_table_map['name']}` ;");
+            }
+
+            $num = 0;
+            $params = [];
+
+            if (!empty($mode_sql) && 'in' === $mode) {
+                foreach ($records as $key => $variable) {
+                    $params[$num] = $variable;
+                    $query->bindParam(':mode_param_'.$key, $params[$num]);
+                    ++$num;
+                }
+            }
+
+            if (!empty($mode_sql) && 'like' === $mode) {
+                foreach ($records as $key => $variable) {
+                    $params[$num] = "%{$variable}%";
+                    $query->bindParam(':mode_param_'.$key, $params[$num]);
+                    ++$num;
+                }
+            }
+
+            $query->execute();
+
+            $data = $query->fetchAll(PDO::FETCH_ASSOC);
+            if (!empty($data)) {
+                $out = [];
+                foreach ($data as $data_single) {
+                    $out_model = new $model_name($internal_table_map);
+                    (function () use ($data_single, $internal_table_map) {
+                        foreach ($internal_table_map['fields'] as $name => $db_field) {
+                            $this->{$name} = $data_single[$db_field];
+                        }
+                    })->call($out_model);
+                    $out[] = $out_model;
+                }
+
+                unset($model);
+
+                return $out;
+            }
+        }
+
+        unset($model);
+
+        return null;
     }
 
     /**