# ДЗ по MVC ## Роутер Сделать роутер, который будет запускать функции (контроллеры), по шаблону адресной строки в регулярках: ```php $routes = [ "\/" => function(){ echo 'index controller'; }, "\/chat\/(\d+)\/" => function($chatId){ echo "chat controller for chat Id " . $chatId; }, "\/user\/(\w+)\/(\d+)\/" => function($username, $someId){ echo "user with name " . $username . " and some id: $someId"; echo '
';print_r( $_GET );echo '
'; }, "" => "myController", "asdfasdf" => [$obj, "indexController"] ]; ``` Выше несколько роутов, каждый из которых запускается по тому или другому шаблону адреса. Используйте .htaccess для перенаправления всех запросов на один php файл, в котором соотносите `$_SERVER['REQUEST_URI']` с вашими шаблонами адресов используя `preg_match`. Параметры в строке адреса (то, что в скобочках в регулярках) должны попадать в контроллер в качестве параметров при вызове. продолжение ДЗ читайте тут: http://route.asmer.php.a-level.com.ua/. Тут же можно посмотреть как работают контроллеры из кода выше. ## ActiveRecord Разработайте класс `Model`, который будет обеспечивать следующую функциональность: ```php class Posts extends Model{ public static $__table__ = 'posts'; } class Comments extends Model{ public static $__table__ = 'comments'; } $post = new Posts(); $post->title = 'new post'; $post->text = 'new post text'; $post->save(); $comment = Comments::getByPk(5); echo ($comment->text) $comment->text = 'update comment'; $comment->save(); ``` Класс должен обеспечивать возможность работы с СУБД исходя из парадигмы **Active Record**, т. е. ### Класс Класс наследник (`Posts` или `Comments` в примере выше) является сущностью, связанной с таблицей в СУБД. Класс `Model` должен уметь: - узнать структуру таблицы (`DESC TABLE`), используя позднее статическое связывание (имя таблицы в поле `$__table__`) - исходя из структуры таблицы, узнать имя первичного ключа (может быть `id`, `post_id` и так далее) - статический метод `getByPk` должен исходя из информации о структуры таблицы сформировать правильный запрос `SELECT * FROM <имя таблицы> WHERE <имя первичного ключа> = <параметр функции getByPk>` ### Объект Каждый объект класса является сущностью, связанной с конкретной записью в таблице Объект `Model` должен уметь: - используя магические `__set` и `__get` считывать и задавать поля записи из СУБД (`$post->title = 'POST TITLE'`) - уметь сохраняться с помощью метода `save`. Метод `save` должен уметь исходя из известной структуры таблицы сформировать корректный `UPDATE` или `INSERT` для сохранения записи. ### Stage1 Считайте что первичный ключ у вас всегда называется `id`. ### Stage2 Сделайте так, что бы имя первичного ключа у вас извлекался из СУБД с помощью `DESC TABLE` и в дальнейшем используйте его как статическую переменную, каждую для своего класса-наследника `Model`. ### Stage3 - Добавьте флаг измененности записи (`dirty`) и не сохраняйте запись если она была изменена. - Добавьте **Identity Map**, для того, что бы объекты, полученные по одному и тому же первичному ключу не дублировались в памяти. **Identity Map** - механизм кэширования, который недопускает дублирования объектов, связанных с одной записью в СУБД в памяти. Это и более экономично по памяти, и уменьшает вероятность глюков рассинхронизации данных. Суть его проста: делаете в каждом классе `Model` статическое свойство - ассоциативный массив, ключами в котором являются idшник из таблицы, а значением будет объект. При запросах (`getByPk`, например) будет создаваться объект, он сохраняется в **Identity Map** и далее при повторных запросах *отдается уже существующий объект*. ### Пожелания и советы по реализации #### Структура таблицы и имя первичного ключа: - Заведите *статический метод* который *лениво* вычитывает структуру таблицы в какое-то статическое поле и возвращает эту структуру (массив с набором колонок). Например `getFields` и `public static $fields` - Заведите *статический метод* который *лениво* находит название поля с первичным ключем. Этот метод скорее всего будет связан с первым, так как нет смысла дважды делать `DESC TABLE`. Например `getPkName` и `public static $pkName` *Ленивость* значит, что при первой вычитке происходит реальное обращение к СУБД, а в дальнейшем методы просто возвращают статическое свойство. #### `getByPk` - Используте статический метод для получения названия первичного ключа. Сформируйте запрос `SELECT * FROM <имя таблицы> WHERE <имя первичного ключа> = <параметр функции getByPk>` - Если у вас есть **Identity Map**, проверяйте наличие объекта в ней, и возвращайте его если он уже там есть. Иначе вычитывайте из базы и сохраняйте объект в **Identity Map** на будущее. #### `__get` и `__set` - Проверяйте что имя магического свойства присутствует в колонках СУБД. (проверка наличия в вашем `public static $fields`) - заведите приватное поле (`$values`, например), которое будет ассоциативным массивом со значениями конкретной записи и ключами - именами колонок. `__get` будет доставать данные оттуда, `__set` будет туда их записывать, `save` - сохранять этот массив в СУБД. - при реальном изменени меняйте флаг изменения `$dirty`, если вы до него дошли #### save - Создайте статический метод (`buildSetValues`) например, который из массива ваших колонок (`getFields()`) будет создавать строку вида `title = :title, text = :text` и так далее для каждой конкретной таблицы. - используя этот метод, в зависимости от того, запись новая или получена из базы, создавайте запросы `INSERT` или `UPDATE` с тем или иным набором полей. Набор полей должен варьироваться - при первом добавлении первичный ключ обычно отсутствует. В таком случае после добавления новой записи его нужно вычитать используя `lastInsertId` - проверяйте флаг `dirty` #### Конструктор - Если конструктор без параметров, то предполагается, что объект сконструирован для новой записи. (т. е. при `save` будет `INSERT`) - Конструктор так же может быть вызыван из `getByPk`. В таком случае используйте `get_called_class` или `__class__` из **PHP7**, для того, что бы инстанциировать потомка. - Если конструктор вызывается из `getByPk`, то в него передаются данные из СУБД, которые сразу попадают в `$values`. Пусть это будет первым параметром конструктора со значением по умолчанию, которое позволит запускать его без параметров. # Делайте всё поэтапно - Stage1 - Stage2 - Stage3