# Пошагово: свой модуль с нуля ← [Назад к разделу «Модули»](README.md) Соберём минимальный, но рабочий модуль **«Закладки»** (`bookmarks`) — список личных ссылок администратора. Пройдём весь путь: папка → дескриптор → миграции → маршруты → контроллер/модель → шаблон → ассеты → проверка. > Разбор компонентов — в [module.php](module-php.md), [files.md](files.md), > [migrations.md](migrations.md), [contributions.md](contributions.md). --- ## 1. Папка ``` adminx/modules/Bookmarks/ ├── module.php ├── Controller.php ├── Model.php ├── migrations/ │ ├── 001_create_admin_bookmarks.sql │ └── uninstall.sql ├── view/ │ └── index.twig └── assets/ ├── bookmarks.less └── bookmarks.css (собирается из .less) ``` --- ## 2. Права и меню Статические права и пункт меню объявим прямо в `module.php` на шаге 7. Для них не нужны отдельные классы. Provider выносится в файл только при вычисляемой или переиспользуемой логике. --- ## 3. Миграции `migrations/001_create_admin_bookmarks.sql`: ```sql CREATE TABLE IF NOT EXISTS `{{prefix}}_admin_bookmarks` ( `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `user_id` INT UNSIGNED NOT NULL, `title` VARCHAR(200) NOT NULL DEFAULT '', `url` VARCHAR(500) NOT NULL DEFAULT '', `created_at` INT UNSIGNED NOT NULL DEFAULT 0, PRIMARY KEY (`id`), KEY `user_id` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ``` `migrations/uninstall.sql`: ```sql DROP TABLE IF EXISTS `{{prefix}}_admin_bookmarks`; ``` Права отдельно через SQL регистрировать не нужно: при установке менеджер синхронизирует массив `permissions.items` с системным реестром. --- ## 4. Модель — `Model.php` ```php getAll() ?: array(); } public static function create($userId, $title, $url) { DB::Insert(self::table(), array( 'user_id' => (int) $userId, 'title' => trim((string) $title), 'url' => trim((string) $url), 'created_at' => time(), )); return (int) DB::insertId(); } public static function delete($id, $userId) { DB::Delete(self::table(), 'id = %i AND user_id = %i', (int) $id, (int) $userId); return DB::affectedRows() > 0; } } ``` ## 5. Контроллер — `Controller.php` ```php render('@bookmarks/index.twig', array( 'bookmarks' => Model::all(Auth::id()), 'can_manage' => Permission::check('manage_bookmarks'), )); } public function store(array $params = array()) { if (($err = $this->guard()) !== null) { return $err; } $errors = Valid::check($_POST, array('title' => 'required', 'url' => 'required|url')); if ($errors) { return $this->error('Проверьте поля', $errors); } Model::create(Auth::id(), Request::postStr('title'), Request::postStr('url')); return $this->success('Закладка добавлена'); } public function delete(array $params = array()) { if (($err = $this->guard()) !== null) { return $err; } $id = isset($params['id']) ? (int) $params['id'] : 0; if (!Model::delete($id, Auth::id())) { return $this->error('Не найдено', array(), 404); } return $this->success('Удалено'); } protected function guard() { if (($err = $this->csrfGuard()) !== null) { return $err; } return Permission::check('manage_bookmarks') ? null : $this->error('Недостаточно прав', array(), 403); } } ``` --- ## 6. Шаблон — `view/index.twig` ```twig {% extends '@adminx/main.twig' %} {% block title %}Закладки{% endblock %} {% block content %}
Личные ссылки