MVCHW.md 10 KB

ДЗ по MVC

Роутер

Сделать роутер, который будет запускать функции (контроллеры), по шаблону адресной строки в регулярках:

    $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 '<pre>';print_r( $_GET );echo '</pre>';
        },
        "" => "myController",
        "asdfasdf" => [$obj, "indexController"]
    ];

Выше несколько роутов, каждый из которых запускается по тому или другому шаблону адреса. Используйте .htaccess для перенаправления всех запросов на один php файл, в котором соотносите $_SERVER['REQUEST_URI'] с вашими шаблонами адресов используя preg_match. Параметры в строке адреса (то, что в скобочках в регулярках) должны попадать в контроллер в качестве параметров при вызове.

продолжение ДЗ читайте тут: http://route.asmer.php.a-level.com.ua/. Тут же можно посмотреть как работают контроллеры из кода выше.

ActiveRecord

Разработайте класс Model, который будет обеспечивать следующую функциональность:

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