Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Задача на клон rghost, ООП и MVC

Задание на ООП и MVC

Для выполнения задания нужно: иметь представление о HTML/CSS (так как мы делаем сайт на фреймворке, а страницы сайтов пишутся на HTML), иметь представление о SQL-запросах или желание в них разобраться (так как мы будем работать с базой данных), иметь представление об ООП (так как все популярные фреймворки используют ООП).

Нужно иметь установленные и настроенные Апач/PHP/MySQL (или денвер/XAMPP но лучше бы установить компоненты по отдельности, дает полезные навыки).

Если есть пробелы в знаниях, у меня есть паста про установку Апача и пара уроков по ООП.

Задание поможет: разобраться в MVC, научиться использовать один из фреймворков, использовать современные подходы к разработке, писать более правильный код.

Задание: сделать сайт uppu.ru, аналог rghost.ru, который позволяет загружать картинки и файлы и делиться ссылочкой. Насчет дизайна, можешь сделать как на rghost (там используется Twitter bootstrap), там должно быть 3 страницы:

  1. Главная страница, она же страница загрузки файла, содержит поле выбора файла для загрузки
  2. Страница просмотра файла. Если это картинка, выводится информация о файле (имя, размер, время зазрузки, комментарий автора), уменьшенная копия и ссылка «скачать». Если это не-картинка, то только информация и ссылка скачать.
  3. Страница со списком последних 100 загруженных файлов, дата, ссылка скачать, название и размер.

Информацию о загруженных файлах удобно хранить в базе данных. Если ты не работал с Бд и SQL, я могу дать краткие ссылочки, для этого задания каких-то крутых знаний не нужно, достаточно знать запросы SELECT, INSERT, CREATE TABLE и все.

В качестве фремйворка лучше всего брать Slim, так как это современный микрофреймворк, но по нему трудно найти информацию на русском (на английском доккументация подробная), или Silex, он тоже современный да еще и основан на компонентах Symfony, а если ты не дружишь с английским, то придется взять F3. Но лучше бы взять Slim или Silex.

F3, Silex и Slim — микрофреймворки, то есть простые фреймворки с небольшим числом классов и функций.

Ознакомительная статья:

В качестве шаблонизатора стоит использовать twig, он современный, удобный и интегрируется с Slim.

Естественно, тебя никто не бросает один на один с фреймворком. Ты всегда можешь задать вопрос, а также периодически постишь свой код, а я его буду смотреть и давать советы и замечания и предупреждать о подвохах. Код удобнее всего постить на гитхаб (умение пользоваться гитом тебе все равно пригодится, если ты хочешь работать программистом, так как сейчас везде его используют), но для начала можно постить сюда: https://gist.github.com/ или на какой-нибудь pastebin или zip-архивом.

Если ты быстро справишься с заданием, то я могу придумать дополнительные задачи: поиск по файлам, организация альбомов, загрузка перетаскиванием с рабочего стола, модерирование, комментарии.

Алсо, если ты не умрешь в процессе изготовления uppu.ru, вот что планируется сделать:

  • загрузка файлов на сервер
  • страница скачивания
  • для картинок, выводить информацию и превьюшку
  • для медиафайлов (аудио/видео), выводить информацию (ее можно получить с помощью библиотеки вроде gitId3, не руками же файлы расковыривать)
  • добавить плеер для аудио/видео
  • прикрутить поиск по файлам с помощью sphinx (чтобы это работало, нужно хранить данные в базе данных — ну я надеюсь, к этому моменту ты это осилишь)
  • прикрутить загрузку файлов простым перетаскиванием на страницу с помощью jQuery плагина (конечно по хоршему надо бы заставить написать тебя загрузчик с нуля, чтобы разобраться в яваскрипте и DOM, но это наверно сложовато)
  • комментарии к файлам
  • древовидные комментарии к файлам

Делать в принципе можно в любом порядке.

Про Slim

Там изучать-то нечего, это микрофреймворк, весь его исходный код наверно можно за пару часов прочесть.

Небольшой подвох в том, что на русском документации нету.

Документация тут: http://docs.slimframework.com/ — не знаешь английского, посмотри хотя бы примеры кода или в гуглотранслейт попробуй засунуть.

Еще вот ссылка http://hashcode.ru/questions/234843/php-slim-framework-как-подключать

Слим из коробки дает тут роутер (штуку, которая разбирает URL на части и вызывает нужную функцию). Вот пример кода:

$app = new \Slim\Slim();

// Define a HTTP GET route:
$app->get('/hello/:name', function ($name) {
    echo "Hello, $name";
});

// Run the Slim application:
$app->run();

При обращении по удресу http://mysite/hello/lalala выведет нужный текст.

С чего начать

Для работы с ним нужно установить Апач + Php, у меня есть ободряющая (инфы-то там не очень много) паста на тему установки Апача: http://gist.github.com/anonymous/946f4f1830be3955fe17

Затем скачай slim и добейся, чтобы пример с hello world работал.

Затем можешь попробовать сделать страничку загрузки файла например.

Куда постить код

Удобнее всего завести себе аккаунт на гитхабе, изучить основы git (книга на русском: http://git-scm.com/book/ru ) и коммитить туда. Там можно будет не только посмотреть сам код, но и историю изменений — очень удобно.

Composer

В ходе разработки ты будешь ставить дополнительные библиотеки (ну как минимум сам Slim, а может что-то еще). Ставить их руками. скачивая с сайта и распаковывая — каменный век. Есть гораздо более удобная штука — менеджер пакетов composer. Подробнее: http://habrahabr.ru/post/145946/

Ты всего лишь пишешь имена и версии пакетов, которые надо установить, в файлик — а он сам их скачивает и ставит. Соответственно, ты не занимаешься ручной работой, и не забиваешь репозиторий библиотеками (папку композера под названием vendor, как ты надеюсь догадался, надо добавить в .gitignore — что это за файл, описано в книге по git).

Композер ставит пакеты из репозитория. Основной репозиторий называется packagist.org — ты можешь открыть его и сам посмотреть сколько там разных пакетов собрано.

Композер также умеет генерировать код для автозагрузки классов — что опять же, облегчает тебе жизнь. Подробнее про автозагрузку:

Безопасность

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

  • злоумышленник загружает свой php-скрипт, вызывает его через браузер (http://example.com/uploads/script.php) и получает контроль над сервером
  • если загрузка PHP-файлов запрещена или не работает, злоумышленник загружает свой .htaccess файл на сервер. Этот файл меняет настройки веб-сервера Апач в текущей папке и позволяет обойти например запрет на выполнение php-кода.
  • (гораздо менее опасно) злоумышленник загружает тебе на сервер свой HTML-файл с яваскрипт-кодом. Яваскрипт выполняется в браузере, так что серверу ничего не грозит. Но так как файл загружается с твоего домена (т.е. имеет адрес вроде http://files.example.com/uploads/file.html) то яваскрипт на нем имеет доступ к кукам пользователей, а также может отправлять запросы от их имени. Злоумышленник уговаривает пользователей открыть эту ссылку, они открывают, скрипт на странице ворует их куки, загружает от их имени файлы, пишет от их имени спамные комментарии на твоем сайте.

Какие есть средства борьбы?

  • ограничение на расширения загружаемых файлов (например только .jpg, .png, .gif). Это не годится для файлообменника, так как мы хотим иметь возможность передавать любые файлы
  • запрет на выполнение php-скриптов в папке загруженных файлов: http://habrahabr.ru/post/61842/ . Подвохи: на некоторых хостингах опция php_flag отключена и игнорируется (запрет не сработает), злоумышленник может попытаться загрузить .htaccess файл, отменяющий действие этой опции, если вместо Апача использовать другой веб-сервер (например nginx), то он не читает файлы .htaccess и не выполняет записанные там команды
  • загружать файлы на отдельный сервер, где нет или отключен интерпретатор php (подходит для тех, у кого много серверов)
  • борьба с загрузкой HTML-файла: можно загружать файлы на отдельный от основного домен, можно при скачивании выставлять правильные заголовки, которые заставят браузер скачать файл, а не открыть его как веб-страницу (Content-Disposition: attachment, Content-Type не содержащий text/html)
  • переименовывание файлов при загрузке с безопасным расширением вроде .txt, так что файл не будет выполняться как php-код. Пожалуй один из самых надежных вариантов.

Также, если ты складываешь файлы в одну папку, то 2 файла с одинаковыми именами могут перезаписать друг друга. Метод борьбы - переименование или добавление в имя какого-то уникального не повторяющегося идентификатора, например id файла из базы данных.

Порядок сохранения или удаления файла

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

  • при сохранении сначала надо поместить файл на диск, потом вставить запись о нем в БД (как вариант можно начать транзакцию, но не коммитить ее пока файл не сохранен)
  • при удалении мы сначала удаляем файл из базы, а только потом с диска

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

Длина и символы в имени файла

Во многих ОС есть ограничение на длину полного пути к файлу. Специально или намеренно тебе могут загрузить файл с очень длинным именем так, что например после сохранения с ним не смогут работать какие-то программы. Стоит сделать ограничение на длину имени файла. При этом надо «обрезать» имя так, чтобы в итоге не получился файл с расширением вроде .php.

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

Удобство администрирования сервера

Если ты складываешь все файлы в одну папку, со временем их там будет очень много. Современные линуксовые файловые системы могут хранить миллионы файлов в одной папке, но удобно ли будет администратору просматривать такую папку? Некоторые программы также не смогут отобразить такое число файлов. Потому стоит раскладывать файлы по папкам из расчета не более 500-1000 файлов на папку. Можно это делать по первым цифрам id файла (uploads/13/13012-file.txt), можно по дате (год + месяц, например uploads/2015/11/13012-file.txt).

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

Реализация скачивания файла с нужным именем

Из соображений безопасности нам, может быть, придется переименовывать файлы при сохранении. Но как сделать, чтобы при скачивании они сохранялись бы под исходным именем? Браузер при скачивании берет имя файла из последнего сегмента URL (то, что идет после последнего слеша). Ну например, при скачивании по URL http://example.com/files/123/example.txt?a=1 браузер сохранит файл под именем example.txt?a=1.

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

Во-первых, напишу подход, который не работает. В англоязычном интернете можно увидеть совет указывать имя файла в заголовке Content-Disposition вроде такого: Content-Disposition: attachment; filename=file.txt. Дело в том, что в HTTP-заголовках можно использовать только кодировку ASCII (то есть латиницу), а если использовать другие символы, то разные браузеры могут интерпретировать их по-своему. Точнее, не так давно сделали стандарт для кодирования нелатинских символов, но он пока не везде поддерживается и довольно неуклюжий.

Первый подход - это использовать правила переписывания в .htaccess. Этот файл позволяет менять настройки Апача в отдельно взятой папке. С помощью модуля Апача mod_rewrite мы можем определить, что при обращении по определенному URL мы должны сделать замены в этом URL, чтобы получить реальное имя файла. При таком подходе мы можем сделать так, чтобы URL

http://example.com/download/stored-name.txt/имя%20для%20скачивания.php

Переписывался бы на /download/stored-name.txt. Таким образом, в нашем URL заложено как реальное имя файла на диске, так и дополнительное, которое предназначено для браузера. %20 - это символ пробела, закодированный процентной кодировкой.

Этот подход будет работать только с сервером Апач. Если захочется перевести проект на другой веб-сервер (например nginx), то там не будет ни .htaccess, ни mod_rewrite и придется писать другие правила замены.

Преимущество - для отдачи файла не надо запускать PHP, это делает сам веб-сервер. Это снижает нагрузку на процессор при скачивании файл (при большом числе пользователей еще большего эффекта можно добиться, используя nginx для отдачи файлов).

Ссылки:

Второй подход - использовать расширение Апача mod_xsendfile. Этот модуль позволяет отдавать без участия PHP произвольный файл, в том числе и не из публичной папки. Оно может использовать например для скачивания файла с проверкой доступа, учетом числа скачиваний или каких-то других условий.

При этом подходе сначала запускается PHP скрипт, который анализирует URL, определяет по нему id файла и находит имя, под которым он сохранен на диске. А затем он выдает специальный заголовок вроде X-SendFile: /download/real-name.txt и завершается. Модуль mod_xsendfile видит этот заголовок и без участия PHP отдает пользователю указанный файл.

Разумеется, URL в этом случае должен содержать id файла, а также имя для браузера в конце, например, http://example.com/123/имя%20для%20сохранения.txt.

Этот модуль позволяет отдавать файлы из непубличных папок, и таким образом, скачать файл в обход php-скрипта, по прямой ссылке, нельзя. Он будет работать только под Апачом, однако под nginx есть аналогичный модуль https://www.nginx.com/resources/wiki/start/topics/examples/xsendfile/ . Он дает чуть большую нагрузку, чем предыдущий метод, но зато позволяет реализовать учет числа скачиваний и ограничение доступа к файлу по произвольным условиям.

Проверить наличие модуля в Апаче можно функцией apache_get_modules().

Ссылки:

Наконец, есть еще один вариант - отдавать файлы приложением на PHP. При этом подходе мы закладываем в URL id файла и имя для браузера (например http://example.com/123/имя%20для%20сохранения.txt), а запрос обрабатывается целиком PHP приложением. Оно выдает нужные заголовки (рекомендую выдавать Content-Type, Content-Length и Disposition), определяет реальное имя файла на диске и отдает его содержимое. Для этого в PHP есть готовая функция readfile. Она читает файл небольшими кусочками и выводит его. Если не читать файл кусочками, а целиком, то на больших файлах скрипт будет потреблять больше памяти.

Если используется фреймворк (например, Slim), то желательно вместо отдачи файла в обход фреймворка отдавать файл его средствами, как-то через объект response.

При этом подходе мы можем отдавать файлы из непубличной папки и контролировать скачивание, но он наименее производительный, так как во время скачивания (которое может длиться долго) PHP скрипт продолжает занимать память. Однако плюс этого подхода в том, что он работает с любым веб-сервером (включая встроенный в PHP).

Лучше всего предусмотреть 2 варианта: один оптимизированный вариант, и на случай, если он не поддерживается, использовать вариант с отдачей файла из PHP.

HTTP-заголовки, связанные со скачиванием

Content-Length

Этот заголовок сообщает браузеру размер тела HTTP-ответа (то есть скачиваемого файла) в байтах. Без него при разрыве соединения браузер не сможет понять, закончена ли передача файла или произошла ошибка и сохранит недокачанный файл. Также, знание размера файла позволяет браузеру расчитать прогресс и оставшееся время скачивания.

Content-Type

Указывает MIME-тип скачиваемого файла. Список MIME-типов есть например в вики. Веб-серверы обычно имеют список, указывающий типы для разных расширений и умеют ставить этот заголовок, однако при отдаче из PHP заголовок надо выставлять самостоятельно, так как по умолчанию PHP ставит text/html. Можно для скачиваемых файлов указывать application/octet-stream, но конечно указывать правильный тип было бы красивее.

Content-Disposition

Заголовок Content-Disposition: attachment указывает браузеру, что надо сохранить файл на диск, а не отобразить на экране, даже если это текст, HTML или картинка. Без него браузер попытается отобразить файл, если это возможно.

Комментарии

Для хранения древовидных комментариев в базе есть специальные способы хранения, о них рассказано в моем уроке: https://github.com/codedokode/pasta/blob/master/db/trees.md . При правильном выборе способа можно выбирать из базы уже отсортированные комментарии.

Так как комментарии содержат текст от пользователя, важно выводить их корректно. Обязательно надо протестировать, что любые спецсимволы выводятся корректно (<>&'"\), HTML-теги вроде <script> выводятся текстом (и не срабатывают) и что сохраняются переводы строк. Если хочется дать возможность пользователям использовать разметку, то есть 2 варианта: либо специальная разметка вроде markdown (не стоит придумывать свой новый язык разметки), либо HTML-редактор. В любом случае надо фильтровать HTML, оставляя в нем только разрешенные теги и атрибуты, иначе пользователь сможет внедрить свой яваскрипт в страницу. Ни в коем случае не стоит использовать фильтры на основе регулярных выражений - они почти все содержат ошибки - надо использовать фильтры, разбирающие HTML-код в дерево DOM. Примером такой хорошей библиотеки может быть HTML Purifier.

Отправку комментариев можно сделать без яваскрипта, с перезагрузкой страницы, а можно - с помощью аякс-запроса (а лучше всего, чтобы работали оба способа в зависимости от того, включен ли JS в браузере). Прежде чем писать аякс-запрос, советую прочесть про распространенные ошибки в реализации аякс-запросов: https://github.com/codedokode/pasta/blob/master/js/ajax.md

Функция "ответить на комментарий" при наличии яваскрипта проще всего реализуется перемещением узла с формой ответа в дереве DOM под нужный комментарий. При отсутствии яваскрипта можно сделать радиокнопки перед каждым комментарием, и средствами CSS, например, подсвечивать выбранный комментарий. Также, можно связать кнопку "Ответить" с радиокнопкой с помощью элемента label.

При использовании AJAX также можно реализовать автоматическую (по таймеру или после отправки своего комментария) или ручную подгрузку новых комментариев. На файлообменнике это наверно не очень нужно, но в популярном блоге или сайте вроде reddit, где идет активное обсуждение, загружать новые комментарии через аякс гораздо удобнее, чем перезагружать всю страницу.

Автоматическая загрузка имеет тот недостаток, что если пользователь оставит незакрытую вкладку в фоне, она так и будет слать запросы на сервер (и грузить компьютер пользователя), хотя они не нужны. В современных браузерах есть способы определить, что вкладка неактивна ( Page Visibility API, англ. ), в старых это определяется по отстутствию событий прокрутки, движения мыши или нажатия клавиш в течение определенного времени. Есть так же библиотеки для работы с упомянутым API: https://github.com/serkanyersen/ifvisible.js/

Информация о файле

Для получения данных о картинках, аудио- и видеофайлах есть библиотека getId3. Она доступна через композер: getId3 в packagist.org.

Выгоднее всего получать данные о файле при загрузке файла (или при первом их запросе), а не каждый раз при просмотре страницы файла. Вообще, если файлообменник будет работать под большой нагрузкой, то вполне возможно, что будет отдельный сервер для PHP-кода и отдельный сервер (сервера) для хранения и раздачи файлов. И в таких условиях каждый раз при просмотре страницы лезть за файлом на другой сервер невыгодно.

Метаданные о файле можно хранить в БД. Так как там большое число полей, разное для разных типов файлов, выгодно сохранить их в формате вроде JSON (формат вроде serialize() плох тем, что он никак не стандартизован, может меняться между версиями PHP и недоступен для других языков), тем более что Postgresql и новая MySQL умеет работать с JSON. Чтобы в коде не работать со сложными массивами, я бы советовал спрятать метаданные и функции для работы с ними внутри объекта вроде MediaInfo.

Генерировать превьюшки для картинок желательно при загрузке или при первом обращении, не стоит генерировать одну превьюшку много раз. Проще генерировать превьюшку в момент загрузки файла и класть ее в публичную папку, чтобы она отдавалась сервером без вызова PHP кода. При генерации превьюшек надо протестировать разные форматы файлов (как минимум GIF, PNG, JPEG, а также корректную поддержку прозрачных, полупрозрачных PNG и генерации превьюшки из анимированного GIF).

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

Надо понимать, что существует много форматов аудио/видео файлов, и внутри формата могут использоваться разные кодеки с разными настройками. Браузеры поддерживают всего лишь небольшой набор форматов. Он зависит от многих факторов:

  • модели и версии браузера
  • версии ОС и дополнительных установленных на компьютере кодеков (некоторые браузеры умеют подключать имеющиеся в ОС кодеки для воспроизведения)

Если взять какой-то случайный видео/аудиофайл, высока вероятность, что он почти нигде не воспроизведется.

Вот информация о поддерживаемых форматах:

Совсем древние браузеры могут играть видео только через flash, но не факт, что он в них будет установлен. Вроде как HTML5 video уже давно поддерживается и flash не нужен.

В тегах <audio> и <video> с помощью тега <source> можно указать несколько источников данных. В DOM так же есть метод (canPlayType()), которым можно проверить, поддерживается ли тот или иной формат, но надо понимать, что браузер может поддерживать не все опции кодирования и фактически проверить наличие поддержки можно только попытавшись воспроизвести файл.

По умолчанию браузерами воспроизволится лишь небольшое число форматов. Если хочется, чтобы файлы по возможности воспроизводились везде и из любых исходных форматов, то нужно делать перекодирование (как это делают сервисы вроде Ютуба). Нужно выбрать несколько форматов, которые бы покрывали максимальное число браузеров, и настроить очередь перекодирования. Для реализации фоновых процессов есть библиотека Gearman, для перекодировки есть утилита ffmpeg. Придется изучить такие вещи, как контейнеры, кодеки, битрейт, и другие особенности кодирования видеофайлов.

Заодно можно сгенерировать картинку-превью (или даже гифку-превью).

При вызове внешних утилит вроде ffmpeg надо понимать, что она может не отработать корректно или зависнуть, и надо придумать, что делать в этом случае (пометить файл как ошибочный или запланировать новую попытку обработки). Надо проверять код возврата утилиты и сохранять выводимые на stdout и stderr данные, чтобы при ошибке была бы возможность изучить логи. Я не советую запускать внешние утилиты встроенными функциями вроде exec(), а использовать библиотеку вроде Symfony Process.

Если перекодирование реализовывать не хочется, можно сделать так: определить формат видеофайла и в теге <source> указать этот формат. Тогда браузеры, которые не могут воспроизвести файл, не будут даже пытаться его скачивать.

Примеры работ

Полезно иногда смотреть чужой код. Можно подметить какие-то интересные вещи, которые ты не знал. Однако, я советую тебе сначала попробовать написать код самому (или хотя бы спроектировать), а только потом смотреть чужие работы, чтобы сравнить результат. Иначе может получиться, что вместо того, чтобы придумать что-то свое, ты будешь подсознательно копировать ранее увиденное. Примеры разных файлообменников на PHP (не обязательно имеющих отношение к этой задаче) можно увидеть, сделав поиск на гитхабе: https://github.com/search?l=PHP&q=file+sharing+&ref=searchresults&type=Repositories&utf8=%E2%9C%93

(Если ты хочешь чтобы твоя работа тоже попала в список, добавь в описание репозитория слова file sharing app).

Ты заметишь, что некоторые приложения по ссылке устроены намного проще: например, они не используют ООП, или даже состоят из единственного файла. Если приложение маленькое (до 500-1000 строк), его действительно можно сделать и без ООП. Но в реальности все приложения, с которыми тебе придется работать, будут большие, и тебе понадобится и ООП, и MVC.

А если тебе хочется собрать все в один файл, то незачем делать это руками. Напиши или найди готовый склейщик, например https://github.com/codeless/jugglecode

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment