Форум программистов, компьютерный форум, киберфорум
PHP: API, боты
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 4.63/16: Рейтинг темы: голосов - 16, средняя оценка - 4.63
2 / 2 / 0
Регистрация: 01.09.2012
Сообщений: 87

MVC: CRUD подобное API для моделей – best practices?

01.07.2013, 16:38. Показов 3300. Ответов 18
Метки нет (Все метки)

Студворк — интернет-сервис помощи студентам
Доброго времени суток!

[offtop]Долго думал куда постить – в "PHP и ООП" или "Для начинающих" ибо вопрос может быть очень простым и глупым. Прошу простить и помочь (:[/offtop]

Пишу простой интернет-магазин на своих велосипедах (для себя, в целях обучения и тренировки технологий/паттернов). В нём есть модели User, Product, Comment и так далее. Модели спроектированы по принципу Active Directory, без дополнительного слоя абстракций (пока что). Хочу привести интерфейс работы с ними к единому знаменателю (CRUD) чтобы стандартный интерфейс получался примерно таким:
PHP
1
2
3
4
5
6
7
interface iUser
{
    public function __construct($email);
    public function add($email, $password);
    public function edit($id, array $fields);
    public function delete($id);
}
В моём случае ссылки выглядят вот так: http://www.example.com/controller/action , при переходе по которым, роутер отдаёт управление простейшей реализации RBAC – таблица соответствий роли пользователя и ресурсу (пути перехода). Например, по "admin/*" ходить может только админ и модератор (остальным 404).
Теперь же я хочу разделить действия группы пользователей не только по контроллерам/моделям, но и по действиям с ними (например, админ может всё, а модератор – только редактировать, но не удалять).

Вопрос номер один: как быть с атрибутом action в формах? Какие пути лучше всего использовать?

Пробовал сделать пути вида "user/add", "user/edit" и так далее. Однако, получается, что мне нужно создавать дополнительный контроллер User, который будет тупо переадресовывать реквесты в модель User. Это плодит лишние сущности.
Затем решил в роутере сделать проверку на наличие отправленного запроса (`if (count($_POST)>0) { ... }`) и если true, то направлять сразу в модель. Однако, в таком случае, внутри модели уже появляются дополинтельные проверки, условия, не связанные напрямую с генерацией sql-запроса. Когда решал эту проблему, то словил себя на мысли, что заново изобретаю ещё один слой абстракции. Значит ли это, что пора переходить от Active Directory к Data Mapping?

Отсюда второй вопрос: например, если мне нужно изменить роль пользователю, то я вызываю метод
PHP
1
edit($id, array $fields)
, где
PHP
1
$fields = ['role' => 'moderator']
. А как быть, если нужно изменить роль нескольких пользователей сразу? Я догадываюсь, что лучше всего это реализовать внутри метода `edit()` и формировать sql чтобы послать его всего один раз, но как это реализовать? Сделать напрямую?
PHP
1
2
3
4
5
if (is_array($id)) {
    // генерация запроса для нескольких пользователей
} else {
    // работаем только с одним
}
Или всегда передавать $id как массив пусть даже и с одним значением?

И, наконец, более общий третий вопрос: быть может, я что-то делаю совсем криво? Хотелось бы узнать мнение опытных людей в этом вопросе.

Спасибо!
0
cpp_developer
Эксперт
20123 / 5690 / 1417
Регистрация: 09.04.2010
Сообщений: 22,546
Блог
01.07.2013, 16:38
Ответы с готовыми решениями:

Размещение авторизации в MVC с использованием ООП – best practices? где?
Доброго времени суток! Пишу свою MVC (обучения для), без фреймворков. И встал вопрос о регистрации/авторизации. Где и как её лучше...

WCF служба. Целесообразность CRUD сервиса, обобщенный API для нескольких сущностей
Здравствуйте Очередной раз вернулся к разбору WCF. Первый вопрос который меня волнует это на сколько целесообразно представлять ...

Реализация CRUD MVC
Здравствуйте. Извиняюсь за глупый вопрос, может не правильно формулирую и вообще не о том думаю, но все же прошу направить в нужное...

18
 Аватар для Василий Макогон
270 / 226 / 11
Регистрация: 20.04.2012
Сообщений: 817
01.07.2013, 22:22
как быть с атрибутом action в формах? Какие пути лучше всего использовать?
те, которые ведут на обработчики

Пробовал сделать пути вида "user/add", "user/edit" и так далее. Однако, получается, что мне нужно создавать дополнительный контроллер User, который будет тупо переадресовывать реквесты в модель User.
это правильно. каждая ссылка грубо говоря - свой контроллер, дергающий модель/модели

Цитата Сообщение от TrogWarZ Посмотреть сообщение
Модели спроектированы по принципу Active Directory
что такое Active Directory? первый раз слышу о таком паттерне


public function __construct($email);
public function add($email, $password);
public function edit($id, array $fields);
public function delete($id);
я не понимаю смысла этих методов. можно полностью код модели юзера?
0
2 / 2 / 0
Регистрация: 01.09.2012
Сообщений: 87
01.07.2013, 22:39  [ТС]
Цитата Сообщение от Василий Макогон Посмотреть сообщение
каждая ссылка грубо говоря - свой контроллер, дергающий модель/модели
Даже если в них будет две строки кода (одна дёргать модель, а вторая для переадресации)? Даже если он не будет расширяться в дальнейшем?

Цитата Сообщение от Василий Макогон Посмотреть сообщение
что такое Active Directory?
Виноват, очепятка, имел ввиду Active Record! Жаль, отредактировать пост уже нельзя ):

Цитата Сообщение от Василий Макогон Посмотреть сообщение
можно полностью код модели юзера?
PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
<?php
namespace Core\Models;
 
use PDO;
 
class User
{
 
// ============================== CRUD =========================================
 
    public function __construct($email)
    {
        $sth = Db::$dbh->prepare('SELECT * FROM users WHERE email = :email LIMIT 1');
        $sth->execute(['email' => $email]);
        $data = $sth->fetch(PDO::FETCH_ASSOC);
 
        // recursive queries for some fields – they're MUST be objects!
        $propObjects = ['role', 'payment', 'delivery'];
 
        if (!empty($data)) {
            foreach ($data as $propName => $propValue) {
                if (in_array($propName, $propObjects)) {
                    $this->$propName = $this->getAttr($propName, $propValue);
                } else {
                    $this->$propName = $propValue;
                }
            }
        }
    }
 
    public function add(array $info)
    {
        $fields = array_keys($info);
 
        foreach ($fields as $f) {
            $v[] = ':' . $f;
        }
 
        $sql = 'INSERT INTO users (' . implode(', ', $fields) . ') VALUES (' . implode(', ', $v) . ')';
        $sth = Db::$dbh->prepare($sql);
        return $sth->execute($info);
    }
 
    public function edit(array $info)
    {
        $id = (!empty($info['id'])) ? $info['id'] : $this->id;
        unset($info['id'], $info['action']);
 
        $arr = [];
        foreach ($info as $f => $v) {
            $arr[] = $f . ' = :' . $f;
        }
        $sql = 'UPDATE users SET ' . implode(', ', $arr) . ' WHERE id = :id';
        $sth = Db::$dbh->prepare($sql);
 
        if (is_array($id)) {
            foreach ($id as $i) {
                $info['id'] = $i;
                $status[] = $sth->execute($info);
            }
        } else {
            $info['id'] = $id;
        }
        $status[] = $sth->execute($info);
 
        return !in_array(false, $status);
    }
 
    public function delete(array $ids)
    {
        $sql = 'DELETE FROM users WHERE (';
        foreach ($ids as $id) {
            $sql .= 'id = ? OR ';
        }
        $sql .= '1=0)';
 
        $sth = Db::$dbh->prepare($sql);
        return $sth->execute($ids);
    }
 
// ============================== OTHER ========================================
 
    public function getAll($role=null)
    {
        // filter by role
        if ($role==null) {
            $sth = Db::$dbh->prepare('SELECT * FROM users');
            $sth->execute();
        } else {
            $sth = Db::$dbh->prepare('SELECT * FROM users WHERE role = :role');
            $sth->execute(['role' => $role]);
        }
        $data = $sth->fetchAll(PDO::FETCH_OBJ);
 
        // recursive queries for some fields
        $propObjects = ['role', 'payment', 'delivery'];
 
        $users = [];
        foreach ($data as $user) {
            if (!empty($user)) {
                foreach ($user as $propName => $propValue) {
                    if (in_array($propName, $propObjects)) {
                        $user->$propName = $this->getAttr($propName, $propValue);
                    } else {
                        $user->$propName = $propValue;
                    }
                }
                $avatar = 'img/avatars/' . $user->id . '.jpg';
                $user->avatar = (is_readable($avatar)) ? $avatar : 'img/avatars/0.jpg';
            }
            $users[] = $user;
        }
        return $users;
    }
 
// ============================== PRIVATE ======================================
 
    private function getAttr($name, $id)
    {
        // add postfix "s" for multiply form of object == name of table
        $name .= 's';
        $sth = Db::$dbh->prepare('SELECT * FROM ' . $name . ' WHERE id = :id LIMIT 1');
        $sth->execute(['id' => $id]);
        return $sth->fetchObject();
    }
 
}
Добавлено через 7 минут
Цитата Сообщение от Василий Макогон Посмотреть сообщение
я не понимаю смысла этих методов
Это желаемый интерфейс – то, что я хочу получить после рефакторинга. Сейчас он отличается и не в лучшую сторону.
0
 Аватар для Василий Макогон
270 / 226 / 11
Регистрация: 20.04.2012
Сообщений: 817
03.07.2013, 05:31
Цитата Сообщение от TrogWarZ Посмотреть сообщение
Даже если в них будет две строки кода (одна дёргать модель, а вторая для переадресации)? Даже если он не будет расширяться в дальнейшем?
да.

Цитата Сообщение от TrogWarZ Посмотреть сообщение
public function __construct($email)
создание объекта по полю емейл - жуть.
Конструктор должен принимать массив с информацией о пользователе и тупо создавать лишь новый объект.
Если су Вас АР, то надо СRUD вынести в парент класс.
В АР методы СRUD одинаковы для ВСЕХ сущьностей.

Цитата Сообщение от TrogWarZ Посмотреть сообщение
public function delete(array $ids) { $sql = 'DELETE FROM users WHERE ('; foreach ($ids as $id) { $sql .= 'id = ? OR '; } $sql .= '1=0)'; $sth = Db::$dbh->prepare($sql); return $sth->execute($ids); }
вот этого НЕ должно быть. Должен быть в паренте метод delete такого содержания:

PHP
1
2
3
4
5
6
7
8
9
10
11
    public function delete(array $ids)
    {
        $sql = 'DELETE FROM '.$this->table.' WHERE (';
        foreach ($ids as $id) {
            $sql .= 'id = ? OR ';
        }
        $sql .= '1=0)';
 
        $sth = Db::$dbh->prepare($sql);
        return $sth->execute($ids);
    }
1
2 / 2 / 0
Регистрация: 01.09.2012
Сообщений: 87
03.07.2013, 05:58  [ТС]
Цитата Сообщение от Василий Макогон Посмотреть сообщение
да.
Хорошо, понял.
Тогда ещё вопрос: как-нибудь разделяют обычные переходы по страницам от переходов с отправкой запросов? И разделяют ли запросы post/get/ajax между собой чтобы выполнять различные действия? И, если различают, то где (в роутере?), каким образом (дробят контроллер на отдельные классы, дробят один метод на методы внутри одного контроллера или как-нибудь ещё)?

Цитата Сообщение от Василий Макогон Посмотреть сообщение
создание объекта по полю емейл - жуть.
Почему? Индекс данного поля UNIQUE в БД, а в запросе "LIMIT 1". Внешние ключи остальных таблиц привязаны к этому полю. Какие могут быть проблемы в дальнейшем из-за такого решения, в чём смысл использовать id?
Поясню причину. У меня авторизация/регистрация по связке "мыло-пароль". Чтобы не плодить лишних запросов только ради того, чтобы узнать id по email (и наоборот) я и решил сделать главным полем именно мыло.

Цитата Сообщение от Василий Макогон Посмотреть сообщение
Если су Вас АР, то надо СRUD вынести в парент класс.
Цитата Сообщение от Василий Макогон Посмотреть сообщение
вот этого НЕ должно быть. Должен быть в паренте метод delete такого содержания:
Спасибо, согласен, логичная идея – так и сделаю.
0
 Аватар для Василий Макогон
270 / 226 / 11
Регистрация: 20.04.2012
Сообщений: 817
03.07.2013, 10:44
Цитата Сообщение от TrogWarZ Посмотреть сообщение
Почему?
Потому, что не надо путать создание объекта пользователя из массива данных/объекта данных, и создание пользователя
на основе выборки данных из СУБД.

Вот принцип:

PHP
1
2
3
4
5
6
7
8
9
$user = new User($data); // $data - это массив с данными
echo $user->name; // вася
$user->save(); // есть свойство $this->id - UPDATE, нет - INSERT
$user->delete(); // удаление по $this->id
 
 // найти по емейл
$user = User::findByEmail($email);
// или по свзяке емейл-пароль
$user = User::findByEmailAndPass($email, $pass);
Цитата Сообщение от TrogWarZ Посмотреть сообщение
Спасибо, согласен, логичная идея – так и сделаю.
это для всех CRUD операций так сделайте. id в таблице у всех сущьностей есть? есть. у вас будет один большой парент и масса потомков с уже индивидуальными методами.

Добавлено через 2 минуты
что бы было более ясно о чем я, почитайте про DataMapper
0
2 / 2 / 0
Регистрация: 01.09.2012
Сообщений: 87
03.07.2013, 12:18  [ТС]
Цитата Сообщение от Василий Макогон Посмотреть сообщение
не надо путать создание объекта пользователя из массива данных/объекта данных, и создание пользователя
на основе выборки данных из СУБД
Ага, теперь, кажется, понял о какой разнице речь. Но, поясните, пожалуйста, где практически это может потребоваться? Зачем? Ведь данные о пользователе корректны тогда и только тогда, когда они соответствуют записи в СУБД.
И как тогда решить возможную неполноту получаемых данных? У меня в БД, например, на поле `role` стоят флаги "NOT NULL" и дефолт "guest" поэтому при неполном запросе выпадет исключение. Значит, чтобы пользователь, созданный из других данных (в обход БД) имел необходимый минимальный обязательный набор полей, нужно закодить проверку внутрь конструктора. А это уже клонирование поведения – при любом изменении архитектуры пользователя в БД, нужно не забыть ещё поправить проверки в конструкторе.

PHP
1
$user = User::findByEmail($email);
Позволю себе не согласиться и уточнить: метод ещё несуществующего (он ведь не полностью статический) объекта User создаёт новый объект типа User (но не себя) и возвращает его в переменную $user типа User – разве это очевидное поведение? Перегрузка конструктора не будет ли более читабельна при решении данной задачи?
PHP
1
2
3
4
// Создать пользователя из рандомных данных
$user = new User((array)$data);
// Найти пользователя в БД и создать его объект
$user = new User((string)$email);
Ну или хотя бы так (но многословней, методы пишут значения в $this->foo):
PHP
1
2
3
4
// Найти в БД по полям и создать с данными оттуда
$user = (new User)->createFromDb($data);
// Создать из полученных данных (по сути, обычный геттер)
$user = (new User)->createWithData($data);
Цитата Сообщение от Василий Макогон Посмотреть сообщение
почитайте про DataMapper
Да, читал, DM мне ближе и интересней. AR показался быстрее в реализации и, пожалуй, именно поэтому решил начать с него. В следующей итерации рефакторинга однозначно реализую DM.

Добавлено через 7 минут
PHP
1
2
$user->save(); // есть свойство $this->id - UPDATE, нет - INSERT
$user->delete(); // удаление по $this->id
Согласен, логично в случае работы с только одним пользователем. Но такой метод delete(), как был написан Вами же комментом выше так уже не вызовешь – стоит добавить проверку на is_array($ids) и empty($id), поставив в сигнатуру метода delete(array $ids=null).
0
 Аватар для Василий Макогон
270 / 226 / 11
Регистрация: 20.04.2012
Сообщений: 817
03.07.2013, 12:47
Цитата Сообщение от TrogWarZ Посмотреть сообщение
Но, поясните, пожалуйста, где практически это может потребоваться? Зачем? Ведь данные о пользователе корректны тогда и только тогда, когда они соответствуют записи в СУБД.
а в субд пользователи как попадают? правильно. утрирую:
PHP
1
2
$user = new User($_POST);
$user->save();
Цитата Сообщение от TrogWarZ Посмотреть сообщение
И как тогда решить возможную неполноту получаемых данных? У меня в БД, например, на поле `role` стоят флаги "NOT NULL" и дефолт "guest" поэтому при неполном запросе выпадет исключение. Значит, чтобы пользователь, созданный из других данных (в обход БД) имел необходимый минимальный обязательный набор полей, нужно закодить проверку внутрь конструктора.
ну да. это называется валидация. и валидация должна быть на уровне модели, т.е. класса User

Цитата Сообщение от TrogWarZ Посмотреть сообщение
А это уже клонирование поведения – при любом изменении архитектуры пользователя в БД, нужно не забыть ещё поправить проверки в конструкторе.
это не проблема. СУБД вообще не гарантирует ничего. она лишь определяет тип данных. а вот валидация емейла или длинна имени пользователя и проверка на "плохие" символы в логине - это именно валидация на уровне модели и никуда от неё не деться. я вот так делал - https://sourceforge.net/p/krug... l/User.php
обратите внимание на массив $model_attributes и валидаторы, которые применяются к свойству при его впрыскивании в объект.

Цитата Сообщение от TrogWarZ Посмотреть сообщение
Позволю себе не согласиться и уточнить: метод ещё несуществующего (он ведь не полностью статический) объекта User создаёт новый объект типа User (но не себя) и возвращает его в переменную $user типа User – разве это очевидное поведение?
можешь не делать метод статическим. суть не меняется - это фабрика своего рода.

Цитата Сообщение от TrogWarZ Посмотреть сообщение
Согласен, логично в случае работы с только одним пользователем. Но такой метод delete(), как был написан Вами же комментом выше так уже не вызовешь – стоит добавить проверку на is_array($ids) и empty($id), поставив в сигнатуру метода delete(array $ids=null).
этот метод, удаляющий записи по айдишникам - вы придумали.
его не должно быть вообще)
ИМХО в AR он не вписывается
AR - одна запись - один объект
foreach по списку пользователей и к каждому применяйте метод delete(), который по id и удаляет запись
0
2 / 2 / 0
Регистрация: 01.09.2012
Сообщений: 87
03.07.2013, 18:15  [ТС]
Цитата Сообщение от Василий Макогон Посмотреть сообщение
а в субд пользователи как попадают? правильно. утрирую:
Я рассматриваю конструктор как R из CRUD – у меня есть отдельный метод, внутри которого и посылаю INSERT (`$user->add($_POST);`), в конструкторе же только SELECT.
Вы же рассматриваете конструктор модели пользователя как C из CRUD – поэтому тоже добавлен специальный нехватающий метод, только уже для чтения (`$user = User::findByEmail($email);`).
Спасибо, реализую Вашим способом, нужно посмотреть в чём практическая разница в подходах!

Цитата Сообщение от Василий Макогон Посмотреть сообщение
валидация должна быть на уровне модели
Валидация на длину пароля, совпадение, "плохие символы" и т.п. есть, но в контроллере регистрации. Впрочем, сейчас уже переписываю это на DM и проверки перенеслись в модель.

Цитата Сообщение от Василий Макогон Посмотреть сообщение
СУБД вообще не гарантирует ничего.
Флаги полей "not null" и "default" гарантируют, что в поле будет записано стандартное значение (определённое мной), даже если в insert-запросе их не окажется вообще (например, пользователь не ввёл опциональное поле или там оказались "плохие" данные). СУБД это ведь не только тупое хранилище данных (так оно и от обычных текстовых файлов мало отличается), а отображение моделей логики.
Но идею сделать главной модель в коде (а не в архитектуре) из приведённого кода я понял, так и сделаю.

Цитата Сообщение от Василий Макогон Посмотреть сообщение
я вот так делал - https://sourceforge.net/p/krug... l/User.php
Кстати, спасибо и за идею сделать отдельные валидаторы – я проверки писал прямо внутри контроллера/модели, а остальное (например, необходимую длину пароля) держал в конфиге )-:
Ещё вопрос по приведённому коду: я правильно понимаю, что RBAC у Вас захардкожен и для расширения нужно изменять много кода (и модель в том числе)? И как тогда происходит проверка на разрешение выполнения запрошенного действия с указанным объектом?

Цитата Сообщение от Василий Макогон Посмотреть сообщение
foreach по списку пользователей и к каждому применяйте метод delete(), который по id и удаляет запись
Тогда если из админ-панели нужно удалить два десятка пользователей, то я отправлю в БД два десятка запросов вместо одного. А это явно дольше, чем один с перечислением id! Именно поэтому и добавил тот метод. Как тогда можно иначе решить проблему тучи одинаковых запросов сохряняя при этом принцип "одна запись – один объект"?
0
 Аватар для Василий Макогон
270 / 226 / 11
Регистрация: 20.04.2012
Сообщений: 817
04.07.2013, 11:27
Цитата Сообщение от TrogWarZ Посмотреть сообщение
Я рассматриваю конструктор как R из CRUD
это плохо. в конструкторе не должно быть никаких действий аля R. Конструктор - только для инициализации.

Цитата Сообщение от TrogWarZ Посмотреть сообщение
есть, но в контроллере регистрации
в контроллере ничего не должно быть. http://ru.wikipedia.org/wiki/M... 0.BA.D0.B8
http://zendframework.ru/anonses/model-with-mvc
вот как должен выглядеть контроллер:


а не так:


Цитата Сообщение от TrogWarZ Посмотреть сообщение
я правильно понимаю, что RBAC у Вас захардкожен и для расширения нужно изменять много кода
не понял вопроса

Тогда если из админ-панели нужно удалить два десятка пользователей, то я отправлю в БД два десятка запросов вместо одного. А это явно дольше, чем один с перечислением id! Именно поэтому и добавил тот метод. Как тогда можно иначе решить проблему тучи одинаковых запросов сохряняя при этом принцип "одна запись – один объект"?
ну я в свое время также отвечал людям, которые мне помогали. у меня был точно такой же ответ как у вас и точно такая же задача. это вопрос скорее теоретический, нежели практический. я как вы раньше заморачивался подобными вопросами, но сейчас для меня гораздо важнее ООПшность и красивость кода, чем попытка выиграть какие-то доли секунды. Будет если тормозить - тогда и надо решать, что делать.
1
2 / 2 / 0
Регистрация: 01.09.2012
Сообщений: 87
04.07.2013, 18:40  [ТС]
Цитата Сообщение от Василий Макогон Посмотреть сообщение
Конструктор - только для инициализации.
Ок, логично, так и сделаю.

Цитата Сообщение от Василий Макогон Посмотреть сообщение
в контроллере ничего не должно быть.
Википедии на разных языках реализуют MVC по-своему (и противоречат друг другу): раз, два, три. У Yii и у ZF ("Most MVC experts recommend keeping controllers as skinny as possible") реализации тоже отличаются. Поэтому хочу реализовать оба варианта и сравнить их. В прошлый раз сделал контроллеры толстыми, а модели пассивными. В этот раз сделаю иначе.

Самый непонятный момент в контроллере – как он должен выглядеть? Например, контроллер редактирования личных данных примерно такой? Я правильно понимаю?
PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
namespace Core\Controllers;
 
use Core\Models\User;
use Core\Views\View;
 
class Profile
{
    public function edit()
    {
        $user = new User($_POST);
        $user->save();
        $view = new View(['user' => $user]);
    }
}
Цитата Сообщение от Василий Макогон Посмотреть сообщение
важнее ООПшность и красивость кода, чем попытка выиграть какие-то доли секунды.
Согласен с тем, что читабельность кода важнее экономии на спичках (пишу "if (strlen($foo)<6)" вместо "if (!isset($foo{6}))" и т.п.). Однако, исходя из моих личных тестов (на шаред-хостинге), запросы к БД – самое медленное, что есть в системе (bottleneck). Поэтому стараюсь минимизировать количество запросов к ней на одно действие: изменение типа у сотни товаров одним или сотней запросов – разница не копеечная.

Добавлено через 43 минуты
Цитата Сообщение от Василий Макогон Посмотреть сообщение
не понял вопроса
Сейчас код не доступен по ссылке поэтому цитату не приведу. Я помню, что там были методы is_admin() и is_user() (и, если не ошибаюсь, одноимённые поля). А если в системе будет не только две роли "пользователь" и "администратор", а ещё и какие-нибудь другие в любом количестве? Например, "модератор уровня 1" и "модератор уровня 2" – потребуется создание дополнительных методов "is_moder_1"/"is_moder_2". А раз требуется изменение исходного кода, то в админке не сделаешь кнопку "изменить уровень доступа".

Чтобы не быть голословным приведу пример (как у меня сейчас).
Так выглядит часть роутера:
PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php
namespace Core;
 
use Core\Models\User;
use Core\Rbac;
 
class Router
{
    // some filelds && methods
 
    public function run(User $user)
    {
        $routes = explode('/', $_SERVER['REQUEST_URI']);
 
        $object = (isset($routes['1']) && mb_strlen($routes['1'])>1) ? $routes['1'] : 'main';
        $action = (isset($routes['2']) && mb_strlen($routes['2'])>1) ? $routes['2'] : 'index';
 
        if ((new Rbac)->isAllowed($user, $object, $action)) {
            $object = 'Core\\Controllers\\' . $object;
            if (class_exists($object) && method_exists($object, $action)) {
                call_user_func([(new $object), $action]);
            } else {
                // if not allowed – send to 404 error page
                $this->error404();
            }
        } else {
            // if controller || method doesn't exist – send to 404 error page
            $this->error404();
        }
    }
 
}
А вот так – класс, проверяющий разрешения на доступ:
PHP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
namespace Core;
 
use Core\Models\User;
use Core\Models\Db;
 
class Rbac
{
 
    public function isAllowed(User $user, $object, $action)
    {
        $resource = $object . '/' . $action;
        $sql = 'SELECT role FROM rbac WHERE resource = :resource';
        $options = [
            'resource' => $resource,
        ];
        $sth = Db::$dbh->prepare($sql);
        $sth->execute($options);
        return ($user->role->id <= $sth->fetchColumn()) ? true : false;
    }
 
}
Структура таблицы `rbac` в СУБД: поле `resource` – путь (например, "admin/users"), а второе поле `role` – id роли. Так же, в таблице `users` есть поле `role` с айдишником роли.

Если есть пользователи, которые могут добавлять комментарии, но не могут их удалять, то доступ к "comment/update" выполняется уже на этапе роутинга и отсеивается. При этом, пользователи с той же самой ролью доступ к "comment/create" имеют и могут постить ответы. А изменение этого (и любого другого) доступа к любому ресурсу – простое редактирование записи в СУБД. То есть, страница админки для этой задачи реализуется феерически просто.
0
508 / 358 / 13
Регистрация: 12.03.2012
Сообщений: 1,896
04.07.2013, 22:25
Цитата Сообщение от TrogWarZ Посмотреть сообщение
В прошлый раз сделал контроллеры толстыми, а модели пассивными.
А где лежала бизнес-логика?
0
2 / 2 / 0
Регистрация: 01.09.2012
Сообщений: 87
04.07.2013, 22:39  [ТС]
OnYourLips, наверное, в контроллере? Проверка входящих данных и обработка некорректных – в контроллерах. Затем эти (уже готовые и корректные на уровне логики) данные отправляются в модель ("$order->add($data)"), которая отправляет запрос в базу.
0
508 / 358 / 13
Регистрация: 12.03.2012
Сообщений: 1,896
04.07.2013, 22:58
Цитата Сообщение от TrogWarZ Посмотреть сообщение
OnYourLips, наверное, в контроллере? Проверка входящих данных и обработка некорректных – в контроллерах. Затем эти (уже готовые и корректные на уровне логики) данные отправляются в модель ("$order->add($data)"), которая отправляет запрос в базу.
Если над объектами производились какие-то действия (расчеты чего-либо, а не простое хранение), то контроллером уже называть это нельзя по определению.
0
2 / 2 / 0
Регистрация: 01.09.2012
Сообщений: 87
05.07.2013, 00:40  [ТС]
OnYourLips, а какие действия "разрешены" контроллеру чтобы им оставаться?
Например, в форме оформления заказа есть поле ввода email. Контроллер проверяет данные на корректность, если введён email, то пишет в массив его, если пользователь залогинен, но ничего не введено – пишет его email в массив, если ни то и ни другое – переадресация на страницу с ошибкой. И только затем уже отправляет эти данные в модель Order для записи в БД.
Это всё ещё контроллер?

Кстати, пока писал комментарий, возник вопрос – как правильно делать переадресацию из контроллера? Например, в зависимости от результата проверки данных на разные страницы (возможно даже с get-запросом).
Я сейчас пишу что-то вроде
PHP
1
header('Location: http://' . $_SERVER['HTTP_HOST'] . '/cart/confirm/?order=' . $idOrder);
Пытался вызывать для этого метод роутера, но получается обращение через несколько слоёв "снизу вверх", а это плохая архитектура. И тут я застрял ):
0
508 / 358 / 13
Регистрация: 12.03.2012
Сообщений: 1,896
05.07.2013, 00:46
Цитата Сообщение от TrogWarZ Посмотреть сообщение
OnYourLips, а какие действия "разрешены" контроллеру чтобы им оставаться?
Не связанные с бизнес-логикой и отображением.

Цитата Сообщение от TrogWarZ Посмотреть сообщение
Я сейчас пишу что-то вроде
Нужен хелпер для ссылок.
path($routeName, array $params, $absolute = false)
0
2 / 2 / 0
Регистрация: 01.09.2012
Сообщений: 87
05.07.2013, 01:55  [ТС]
Цитата Сообщение от OnYourLips Посмотреть сообщение
Не связанные с бизнес-логикой
Например, в контроллере добавления комментариев должен быть только вызов?
PHP
1
2
$comment = new Comment($_POST);
$comment->save();
Контроллер имеет право сделать, например, вот так? Или это уже относится к бизнес-логике и должно быть в модели комментария?
PHP
1
2
3
4
$user = new User($_POST['user_email']);
$data = $_POST;
$data['user'] = (!empty($user->email)) ? $user->email : null;
$comment = new Comment($data);
Цитата Сообщение от OnYourLips Посмотреть сообщение
Нужен хелпер для ссылок.
Этот хелпер всё-равно ведь должен быть глобальным? Я пробовал добавить эту фичу в роутер (он ведь должен заниматься адресацией) посредством такого метода (внутри Core\Router):
PHP
1
2
3
4
5
6
7
8
9
10
public static function redirect(
    $object='main',
    $action='index',
    array $options=null,
    $type='GET',
    $sendHeader=false
)
{
    // Код для переадресации
}
Что не понравилось:
– Статический метод в нестатическом классе
– Вызов через несколько ступеней иерархии
– "use Core\Router" во всех классах приложения
Можно ли избавиться от данных минусов или такое решение – ок?

Добавлено через 21 минуту
Я догадываюсь, что нужно как-то раскрутить стек вызовов обратно до роутера или точки входа, но как именно это реализовать – ума не приложу.
0
508 / 358 / 13
Регистрация: 12.03.2012
Сообщений: 1,896
05.07.2013, 08:28
Цитата Сообщение от TrogWarZ Посмотреть сообщение
Контроллер имеет право сделать, например, вот так? Или это уже относится к бизнес-логике и должно быть в модели комментария?
Должно: относится к HTTP. Никаких $_POST при этом не должно быть в бизнес-логике.

Цитата Сообщение от TrogWarZ Посмотреть сообщение
Можно ли избавиться от данных минусов или такое решение – ок?
Вот так делай: https://github.com/symfony/sym... er.php#L36
Больше читай чужого кода. Это дает гораздо больше знаний, чем написание своего.
1
2 / 2 / 0
Регистрация: 01.09.2012
Сообщений: 87
06.07.2013, 22:21  [ТС]
Посоветуйте, пожалуйста, фреймворк, для разбора кода которого нужен не настолько высокий уровень знаний? Я, видимо, под него пока ещё не подхожу.

Потратил два дня на то, чтобы покопаться в исходниках Симфони2 на Гитхабе, включая статьи, глоссарий и прочие описания на их офсайте. Большинство просто не смог понять.
Например, хотел посмотреть код точки входа, определил, что она называется "FrontController" (да и то случайно, пока читал глоссарий) и всё – поиск в github по этому сочетанию возвращает нулевой результат. Файлов типа "index.php" и директорий типа "wwwroot" и подобных не нашёл.
Слишком часто возникают три вопроса:"Что это?", "Куда оно это возвращает?" и "Зачем оно вообще нужно?". Не покидает ощущение дикого оверкилла, который имеет смысл разве что для корпоративных crm или чего-то ещё более сложного. Большинство материала (в том числе и книга) посвящено тому, как использовать фреймворк, а я хочу понять как он работает внутри.
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
raxper
Эксперт
30234 / 6612 / 1498
Регистрация: 28.12.2010
Сообщений: 21,154
Блог
06.07.2013, 22:21
Помогаю со студенческими работами здесь

Best practices и worst practices в написании sql запросов
Может кто-нибудь поделится опытом (может, знаете полезные статейки) по тому, как правильно создавать запросы к БД? Что нибудь типа...

MVC, несколько моделей на одной view
Всем привет! Столкнулся с такой проблемой. Делаю веб-приложение - что-то типа веб-интерфейса для бд (вывод всех записей, одной по id,...

Ссылки в CRUD-таблице на другие CRUD-таблицы
Здравствуйте! Прошу у вас помощи :help: У меня есть список групп: Так выглядит представление таблицы `groupp` в PhpMyAdmin: ...

Можно ли сгенерировать view для регистрации в asp net mvc +wep api?
Почему если я использую шаблон asp net mvc +web api у меня нет готовых view для регистрации и так далее? Их создавать вручную?

asp.net mvc 2 проект, работает без контроллеров и моделей. Такое возможно?
Здравствуйте. Я новичек в MVC. Есть чужой проект, в нем не было файла проекта (sln, csproj). Создал новый проект, добавил туда все...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
19
Ответ Создать тему
Новые блоги и статьи
Символьное дифференцирование
igorrr37 13.02.2026
/ * Логарифм записывается как: (x-2)log(x^2+2) - означает логарифм (x^2+2) по основанию (x-2). Унарный минус обозначается как ! */ #include <iostream> #include <stack> #include <cctype>. . .
Камера Toupcam IUA500KMA
Eddy_Em 12.02.2026
Т. к. у всяких "хикроботов" слишком уж мелкий пиксель, для подсмотра в ESPriF они вообще плохо годятся: уже 14 величину можно рассмотреть еле-еле лишь на экспозициях под 3 секунды (а то и больше),. . .
И ясному Солнцу
zbw 12.02.2026
И ясному Солнцу, и светлой Луне. В мире покоя нет и люди не могут жить в тишине. А жить им немного лет.
«Знание-Сила»
zbw 12.02.2026
«Знание-Сила» «Время-Деньги» «Деньги -Пуля»
SDL3 для Web (WebAssembly): Подключение Box2D v3, физика и отрисовка коллайдеров
8Observer8 12.02.2026
Содержание блога Box2D - это библиотека для 2D физики для анимаций и игр. С её помощью можно определять были ли коллизии между конкретными объектами и вызывать обработчики событий столкновения. . . .
SDL3 для Web (WebAssembly): Загрузка PNG с прозрачным фоном с помощью SDL_LoadPNG (без SDL3_image)
8Observer8 11.02.2026
Содержание блога Библиотека SDL3 содержит встроенные инструменты для базовой работы с изображениями - без использования библиотеки SDL3_image. Пошагово создадим проект для загрузки изображения. . .
SDL3 для Web (WebAssembly): Загрузка PNG с прозрачным фоном с помощью SDL3_image
8Observer8 10.02.2026
Содержание блога Библиотека SDL3_image содержит инструменты для расширенной работы с изображениями. Пошагово создадим проект для загрузки изображения формата PNG с альфа-каналом (с прозрачным. . .
Установка Qt-версии Lazarus IDE в Debian Trixie Xfce
volvo 10.02.2026
В общем, достали меня глюки IDE Лазаруса, собранной с использованием набора виджетов Gtk2 (конкретно: если набирать текст в редакторе и вызвать подсказку через Ctrl+Space, то после закрытия окошка. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru