Пример Менеджер Закладок

Это руководство проведет вас через создание простого приложения для создания закладок (Менеджер Закладок). Для начала мы установим CakePHP, создадим нашу базу данных, и используем инструменты CakePHP для быстрой развертки нашего приложения.

Вот что вам понадобится:

  1. Сервер БД. Мы собираемся использовать в этом руководстве сервер MySQL. Вы должны знать достаточно об SQL, чтобы суметь создать базу данных: в остальном можно положиться на CakePHP. Поскольку мы используем MySQL, убедитесь также, что у Вас включено расширение pdo_mysql в PHP.
  2. Базовые знания PHP.

Перед началом вы должны убедиться, что у вас установлена актуальная версия PHP:

php -v

Убедитесь, что Вы используете версию PHP не ниже 5.5.9 (CLI). Версия PHP вашего веб-сервера также должна быть не ниже 5.5.9 и лучше всего должна совпадать с версией CLI. Если вы хотите увидеть полностью рабочее приложение, проверьте cakephp/bookmarker. Давайте приступим!

Получение CakePHP

Простейший путь установить CakePHP - это использование Composer. С помощью Composer Вы с легкостью установите фреймворк через командную строку или терминал. Сначала скачайте и установите Composer если у Вас его еще нет. Если у Вас установлен cURL, можете использовать следующую команду:

curl -s https://getcomposer.org/installer | php

Или Вы можете скачать composer.phar с веб-сайта Composer.

После этого просто наберите следующую строку в вашем терминале из вашей установочной папки, чтобы развернуть базовую структуру приложения CakePHP в папку bookmarker:

php composer.phar create-project --prefer-dist cakephp/app bookmarker

Преимущество при использовании Composer заключается в том, что он автоматически произведет все необходимые настройки по правам доступа и создаст файл конфигурации приложения config/app.php для Вас.

Также существуют и другие способы установки, если Вас не устраивает Composer. Проверьте раздел документации Установка, чтобы узнать больше.

Независимо от того, каким способом установки Вы решите воспользоваться, по окончании процесса установки, структура папки Вашего приложения будет выглядеть следующим образом:

/cake_install
    /bin
    /config
    /logs
    /plugins
    /src
    /tests
    /tmp
    /vendor
    /webroot
    .editorconfig
    .gitignore
    .htaccess
    .travis.yml
    composer.json
    index.php
    phpunit.xml.dist
    README.md

Теперь настало самое подходящее время для ознакомления с файловой структурой приложения: проверьте раздел документации Структура папок CakePHP.

Проверка нашей установки

Мы можем быстро убедиться, что наша установка была выполнена корректно, проверив доступность домашней страницы по умолчанию:

bin/cake server

Примечание

Для Windows, команда должна быть bin\cake server (с обратным слешем).

Это запустит встроенный веб-сервер PHP на порте 8765. Откройте http://localhost:8765 в вашем веб-браузере, чтобы увидеть страницу приветствия. Все проверки на странице должны быть пройдены, за исключением проверки подключения к базе данных. Если же это не так, возможно вам следует установить еще какие-то расширения PHP или установить права доступа к папкам.

Создание Базы данных

Следующим шагом давайте настроим необходимую базу данных для нашего Менеджера Закладок. Если вы еще этого не сделали - создайте пустую базу данных для работы вашего приложения, с любым удобным именем, например cake_bookmarks. Вы можете выполнить следующий SQL-запрос, для создания необходимых таблиц:

CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    email VARCHAR(255) NOT NULL,
    password VARCHAR(255) NOT NULL,
    created DATETIME,
    modified DATETIME
);

CREATE TABLE bookmarks (
    id INT AUTO_INCREMENT PRIMARY KEY,
    user_id INT NOT NULL,
    title VARCHAR(50),
    description TEXT,
    url TEXT,
    created DATETIME,
    modified DATETIME,
    FOREIGN KEY user_key (user_id) REFERENCES users(id)
);

CREATE TABLE tags (
    id INT AUTO_INCREMENT PRIMARY KEY,
    title VARCHAR(255),
    created DATETIME,
    modified DATETIME,
    UNIQUE KEY (title)
);

CREATE TABLE bookmarks_tags (
    bookmark_id INT NOT NULL,
    tag_id INT NOT NULL,
    PRIMARY KEY (bookmark_id, tag_id),
    FOREIGN KEY tag_key(tag_id) REFERENCES tags(id),
    FOREIGN KEY bookmark_key(bookmark_id) REFERENCES bookmarks(id)
);

Вы возможно заметили, что таблица bookmarks_tags использует составной первичный ключ. CakePHP поддерживает составные первичные ключи почти везде, делая легче процесс создания мультиарендных приложений.

Имена таблиц и полей, которые мы выбрали не были случайны. Пользуясь соглашениями об именовании CakePHP, мы можем более полно использовать возможности фреймворка и избежать необходимости задания дополнительных настроек. CakePHP достаточно гибок, чтобы адаптироваться даже к довольно к противоречивым схемам баз данных, но следование соглашениям сэкономит вам кучу времени.

Конфигурация Базы данных

Давайте теперь скажем CakePHP где расположена наша База данных и как с ней соединиться. Для многих это будет первый и последний раз когда они увидят файл настроек.

Настройка должна показаться довольно легкой: просто замените значения в массиве Datasources.default в файле config/app.php на нужные вам. В результате у вас должно получиться что-то вроде этого:

return [
    // More configuration above.
    'Datasources' => [
        'default' => [
            'className' => 'Cake\Database\Connection',
            'driver' => 'Cake\Database\Driver\Mysql',
            'persistent' => false,
            'host' => 'localhost',
            'username' => 'cake_blog',
            'password' => 'AngelF00dC4k3~',
            'database' => 'cake_bookmarks',
            'encoding' => 'utf8',
            'timezone' => 'UTC',
            'cacheMetadata' => true,
        ],
    ],
    // More configuration below.
];

Как только вы сохраните ваш файл config/app.php, на приветственной странице CakePHP вы увидите сообщение, что База данных обнаружена и подключение к ней прошло успешно.

Примечание

Копия файла с настройками по умолчанию может быть найдена в config/app.default.php.

Генерирование шаблонного кода

Так как наша база данных следует соглашениям CakePHP, мы можем воспользоваться консолью Bake для генерирования шаблонного кода. В вашей командной строке введите следующие команды:

// В Windows пишите bin\cake.
bin/cake bake all users
bin/cake bake all bookmarks
bin/cake bake all tags

Это сгенерирует код Контроллеров, Моделей, Видов и т.д. для наших ресурсов users, bookmarks, tags. Если вы остановили работу вашего веб-сервера, перезапустите его и перейдите по адресу http://localhost:8765/bookmarks.

Вы должны увидеть простое но функциональное приложение, предоставляющее доступ к данным, хранящимся в Базе Данных. Как только вы окажетесь на странице списка закладок, добавьте несколько пользователей, закладок и тегов.

Примечание

Если у вас отображается 404 ошибка, убедитесь, что модуль Apache mod_rewrite загружен.

Хеширование паролей

Когда вы создали ваших пользователей (посетив адрес http://localhost:8765/users), вы вероятно были оповещены, что пароли сохранены в виде простого текста. Это очень плохо с точки зрения безопасности, давайте исправим это.

Это также довольно подходящий момент, чтобы упомянуть о Моделях в CakePHP. В CakePHP мы разделяем методы, оперирующие с коллекциями объектов и с отдельными объектами, размещая их в отдельных классах. Методы, работающие с коллекцией сущностей, мы размещаем в классе Table, в то время как функции, относящиеся к отдельным записям - в классе Entity.

К примеру, хеширование паролей выполняется индивидуально для каждой записи, таким образом внедрим это поведение в объект сущности (entity). Так как мы хотим хешировать пароль каждый раз, когда он устанавливается, мы будем использовать метод мутатор/сеттер. CakePHP будет вызывать основанные на соглашениях методы-сеттеры каждый раз, когда свойство будет установлено на одной из сущностей. Давайте добавим сеттер для пароля. В файле src/Model/Entity/User.php добавьте следующий код:

namespace App\Model\Entity;

use Cake\Auth\DefaultPasswordHasher; //добавьте эту строку
use Cake\ORM\Entity;

class User extends Entity
{

    // Код от bake.

    protected function _setPassword($value)
    {
        $hasher = new DefaultPasswordHasher();
        return $hasher->hash($value);
    }
}

Теперь обновите одного из пользователей, созданных ранее, если вы измените его пароль, вы должны увидеть хэшированный пароль вместо исходного значения в списке или на страницах Вида. CakePHP хеширует пароли с помощью bcrypt по умолчанию. Вы также можете использовать алгоритмы sha1 или md5, если вы работаете с уже существующей базой данных.

Примечание

Если пароль не хешируется, убедитесь, что вы указываете в правильным регистре имя экземпляром класса пароля при именовании метода-сеттера.

Получение закладок с определенным тегом

Теперь, когда мы надежно храним пароли, мы можем создать некоторые более интересные возможности в нашем приложении. Как только у вас накопится множество закладок, будет очень полезным иметь возможность искать в них что-либо по определенным тегам. Следующим шагом мы реализуем маршрут, экшен Контроллера, поисковый метод для отбора закладок по тегу.

В идеале у нас должны быть адреса наподобие такого http://localhost:8765/bookmarks/tagged/funny/cat/gifs. Подобный адрес даст нам возможность найти все закладки с тегами ‘funny’, ‘cat’ или ‘gifs’. Прежде чем начать, нам нужно добавить новые маршруты. Ваш файл config/routes.php должен выглядеть примерно так:

<?php
use Cake\Routing\Route\DashedRoute;
use Cake\Routing\Router;

Router::defaultRouteClass(DashedRoute::class);

// Новый маршрут добавляемый нами для нашего экшена для тегов.
// Символ `*` в конце говорит CakePHP что этот экшен
// принимает параметры.
Router::scope(
    '/bookmarks',
    ['controller' => 'Bookmarks'],
    function ($routes) {
        $routes->connect('/tagged/*', ['action' => 'tags']);
    }
);

Router::scope('/', function ($routes) {
    // Connect the default home and /pages/* routes.
    $routes->connect('/', [
        'controller' => 'Pages',
        'action' => 'display', 'home'
    ]);
    $routes->connect('/pages/*', [
        'controller' => 'Pages',
        'action' => 'display'
    ]);

    // Connect the conventions based default routes.
    $routes->fallbacks();
});

Приведенный выше код определяет новый ‘маршрут’, соединяющий путь /bookmarks/tagged/ с экшеном BookmarksController::tags(). Объявляя маршруты, мы можем отделить то, как URL-адреса выглядят, от того, как они формируются. Если бы мы попытались перейти по адресу http://localhost:8765/bookmarks/tagged, мы бы увидели сообщение об ошибке от CakePHP, говорящее о том, что нужный экшен отсутствует в Контроллере. Давайте теперь добавим нужный метод. В файле src/Controller/BookmarksController.php добавьте следующее:

public function tags()
{
    // Ключ 'pass' предоставляется CakePHP и содержит все
    // передаваемые в URL сегменты пути в запросе.
    $tags = $this->request->getParam('pass');

    // Используем класс BookmarksTable для поиска закладок с тегами.
    $bookmarks = $this->Bookmarks->find('tagged', [
        'tags' => $tags
    ]);

    // Передаем переменные в Вид.
    $this->set([
        'bookmarks' => $bookmarks,
        'tags' => $tags
    ]);
}

Для получения более подробной информации о запросах посмотрите раздел Request.

Создание поискового метода

В CakePHP мы любим сохранять экшены наших Контроллеров компактными и помещать большую часть логики нашего приложения в Моделях. Если вы пытались посетить URL /bookmarks/tagged, то наверняка увидели ошибку, что метод findTagged() отсутствует, давайте это исправим. В файле src/Model/Table/BookmarksTable.php добавьте следующее:

// Аргумент $query это экземпляр класса конструктора запросов.
// Массив $options будет содержать опцию 'tags' переданную нами
// в find('tagged') в экшене нашего Контроллера.
public function findTagged(Query $query, array $options)
{
    $bookmarks = $this->find()
        ->select(['id', 'url', 'title', 'description']);

    if (empty($options['tags'])) {
        $bookmarks
            ->leftJoinWith('Tags')
            ->where(['Tags.title IS' => null]);
    } else {
        $bookmarks
            ->innerJoinWith('Tags')
            ->where(['Tags.title IN ' => $options['tags']]);
    }

    return $bookmarks->group(['Bookmarks.id']);
}

Мы только что реализовали пользовательский метод поиска. Это очень мощная концепция в CakePHP, которая позволяет вам легко использовать повторяющиеся запросы. Поисковые методы всегда получают объект Query Builder и массив, в котором передаются параметры. Поисквые методы могут манипулировать запросом и добавлять любые необходимые условия и критерии поиска. По завершении работы поисковые методы должны возвращать измененный объект запроса. В нашем поисковом методе мы пользуемся возможностями методов distinct() и matching(), которые позволяют нам находить строго те закладки, которые имеют совпадающий тег. Метод matching() принимает в качестве параметра анонимную функцию, которая получает в качестве аргумента конструктор запросов. Внутри коллбека мы используем конструктор запросов, чтобы определить условия фильтрации закладок, имеющих определенный тег.

Создание Вида

Теперь, если вы посетите URL /bookmarks/tagged, CakePHP выведет ошибку, дающую вам знать, что вы ещё не создали файл Вида. Давайте создадим его для нашего экшена tags(). В файле src/Template/Bookmarks/tags.ctp поместите следующий код:

<h1>
    Закладки с тегами
    <?= $this->Text->toList(h($tags)) ?>
</h1>

<section>
<?php foreach ($bookmarks as $bookmark): ?>
    <article>
        <!-- Используем HtmlHelper для создания ссылок -->
        <h4><?= $this->Html->link($bookmark->title, $bookmark->url) ?></h4>
        <small><?= h($bookmark->url) ?></small>

        <!-- Используем TextHelper для форматирования текста -->
        <?= $this->Text->autoParagraph(h($bookmark->description)) ?>
    </article>
<?php endforeach; ?>
</section>

В приведённом выше коде мы использовали хелперы Html и Text для автоматического генерирования нужной разметки. Также мы использовали функцию h для кодирования в HTML выводимых данных. Вы всегда должны использовать функцию h() для обработки полученных от пользователя данных, чтобы предотвратить угрозу SQL-инъекций.

Файл tags.ctp, который мы только что создали, следует соглашениям CakePHP для файлов шаблонов Вида. Имя шаблона совпадает с именем экшена Контроллера, написано в нижнем регистре с использованием знаков подчеркивания в качестве разделителей слов.

Вы можете заметить, что мы могли использовать переменные $tags и $bookmarks в нашем Виде. Используя метод set() в нашем Контроллере, мы определяем переменные, которые должны быть доступны в Виде. Вид сделает все переданные переменные доступными в качестве локальных.

Теперь вы можете например перейти по URL-адресу /bookmarks/tagged/funny, и увидеть все закладки с тегом ‘funny’.

Таким образом мы создали простое приложение для управления закладками, тегами и пользователями. Но пока что каждый пользователь может видеть не только свои закладки и теги, но и соответствующие данные относящиеся к другим пользователям. В следующей части мы реализуем аутентификацию и ограничим видимость закладок, чтобы каждый пользователь мог видеть только свои закладки.

Теперь вы можете продолжить перейдя во вторую часть руководства Пример Менеджер Закладок Часть 2 для дальнейшей разработки приложения, или же погрузиться в изучение документации чтобы узнать больше о том, что CakePHP может сделать для вас.