Growing Crystals vol 15. Веб-сайт и Yii

Блог проекта Growing Crystals продолжает рассказывать об инди-разработке браузерной многопользовательской он-лайн игры Growing Crystals и сегодня мы вплотную подойдём к очень важному и ответственному моменту в жизненном цикле разработки любого проекта, запуску альфа-версии проекта. Это последняя запись в блоге проекта Growing Crystals до запуска официальной альфа-версии. Я честно верил хотя никому ничего и не обещал, верил в то, что 10-го августа у нас будет готова альфа-версия с регистрацией на сайте, но, увы, надо быть реалистом, и почаще смотреть на майнд-карту, особенно на блок «Задачи первой очереди», в которой висел такой замечательный пункт как Yii, и, который неожиданно съел несколько полных дней. Конечно, сама игра уже цветет и пахнет, строительство, крафтинг, main, апгрейды, уже даже самому интересно играть и строить свои базы, продумывая защиту от потенциальных врагов, параллельно брат подбирает визуальные эффекты для разрушений и системы боёв. Но, очень не хочется выпускать игру с большой картой без возможности сохранения прогресса. Поэтому и пришлось посвятить себя Yii, который, вопреки многообещающим дайджестам «простой», «гибкий», MVC, оказался еще и объёмный и сложный для взятия нахрапом за 1 день, с относительно малым количеством готовых примеров с исходниками, да что и говорить, готовые рецепты по ЧПУ, которые мне удалось найти, сводились к решениям типа yourdomain.com/site/your_url и пришлось делать что-то вроде маленьких костыликов чтобы всё работало в URL как я люблю: yourdomain.com/your_url. Но надо отдать должное, Yii — самое лучшее из того, что мне приходилось встречать в разработке на PHP, но чтобы достичь этого понимания понадобилось 3-е суток.

Перейти к получившемуся веб-сайту игры.

Разработка веб-сайта на Yii

Представьте себе, практически ни чем не отличается от разработки веб-сайта на любом малознакомом фреймворке. Но, думаю, что стоит рассказать в подробностях, чтобы проникшиеся моим опытом инди-игроделы знали, что не стоит оставлять сайт на последний момент, или, по крайней мере, не стоит рассчитывать на то, что удастся его сделать за несколько дней. Я откровенно недооценил свои силы, полагаясь на свой большой опыт ООП и не очень большой опыт PHP. У меня в голове была иллюзия «Раз я разобрался с WP за один день, то и с Yii разберусь», но обо всём по порядку. Для профессионалов или опытных Yii разработчиков эта статья может выглядеть нелепо и вызывать улыбку, ну что ж, повысьте своё ЧСВ наслаждаясь попытками новичка в Yii. Я написал эту статью также и для того, чтобы получить замечания и отзывы относительно использования Yii. Писал я её больше недели в процессе разработки и знакомства с Yii.

0. Идеи и прототип

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

Сайт майнд карта

Затем, потратив день-два на просмотр игровых сайтов, был быстренько составлен прототип главной страницы. Вы его видели, вот он.

Прототип веб-сайта

1. Установка Yii (1-2 день)

Скачал и установил yii-1.1.15.022a51.zip. Установил значит распаковал в новый созданный виртуальный домен gropet в Денвере. Согласно документации, любой проект на yii лучше начинать с автоматически сгенерированного утилитой yiic каркаса(демо проекта). Для этого нужно «всего лишь запустить yiic». Для Windows даже имеется yiic.bat. Но под Денвером пришлось сделать небольшой финт ушами с запуском yiic, хотя это оказалось достаточно просто. Дело в том, что bat файл yiic подразумевает что путь к PHP прописан в системе, что, конечно, не так в моём случае, да и в большинстве случаев у тех, кто работает с Денвером и не любит засорять систему. В итоге я запустил скрипт следующим образом.

Microsoft Windows [Version 6.1.7601]
(c) Корпорация Майкрософт (Microsoft Corp.), 2009. Все права защищены.

w:\home\gropet\www\framework>w:\usr\bin\php5.exe w:\home\gropet\www\framework\yiic.php webapp test
X-Powered-By: PHP/5.3.13
Content-type: text/html

Create a Web application under 'W:\home\gropet\www\framework\test'? (yes|no) [no]:Yes
      mkdir W:/home/gropet/www/framework/test/assets
      mkdir W:/home/gropet/www/framework/test/css
   generate css/bg.gif
   generate css/form.css
   generate css/ie.css
   generate css/main.css
   generate css/print.css
   generate css/screen.css
      mkdir W:/home/gropet/www/framework/test/images
   generate index-test.php
   generate index.php
      mkdir W:/home/gropet/www/framework/test/protected
   generate protected/.htaccess
      mkdir W:/home/gropet/www/framework/test/protected/commands
      mkdir W:/home/gropet/www/framework/test/protected/commands/shell
      mkdir W:/home/gropet/www/framework/test/protected/components
   generate protected/components/Controller.php
   generate protected/components/UserIdentity.php
      mkdir W:/home/gropet/www/framework/test/protected/config
   generate protected/config/console.php
   generate protected/config/main.php
   generate protected/config/test.php
      mkdir W:/home/gropet/www/framework/test/protected/controllers
   generate protected/controllers/SiteController.php
      mkdir W:/home/gropet/www/framework/test/protected/data
   generate protected/data/schema.mysql.sql
   generate protected/data/schema.sqlite.sql
   generate protected/data/testdrive.db
      mkdir W:/home/gropet/www/framework/test/protected/extensions
      mkdir W:/home/gropet/www/framework/test/protected/messages
      mkdir W:/home/gropet/www/framework/test/protected/migrations
      mkdir W:/home/gropet/www/framework/test/protected/models
   generate protected/models/ContactForm.php
   generate protected/models/LoginForm.php
      mkdir W:/home/gropet/www/framework/test/protected/runtime
      mkdir W:/home/gropet/www/framework/test/protected/tests
   generate protected/tests/bootstrap.php
      mkdir W:/home/gropet/www/framework/test/protected/tests/fixtures
      mkdir W:/home/gropet/www/framework/test/protected/tests/functional
   generate protected/tests/functional/SiteTest.php
   generate protected/tests/phpunit.xml
      mkdir W:/home/gropet/www/framework/test/protected/tests/report
      mkdir W:/home/gropet/www/framework/test/protected/tests/unit
   generate protected/tests/WebTestCase.php
      mkdir W:/home/gropet/www/framework/test/protected/vendor
      mkdir W:/home/gropet/www/framework/test/protected/views
      mkdir W:/home/gropet/www/framework/test/protected/views/layouts
   generate protected/views/layouts/column1.php
   generate protected/views/layouts/column2.php
   generate protected/views/layouts/main.php
      mkdir W:/home/gropet/www/framework/test/protected/views/site
   generate protected/views/site/contact.php
   generate protected/views/site/error.php
   generate protected/views/site/index.php
   generate protected/views/site/login.php
      mkdir W:/home/gropet/www/framework/test/protected/views/site/pages
   generate protected/views/site/pages/about.php
   generate protected/yiic
   generate protected/yiic.bat
   generate protected/yiic.php
      mkdir W:/home/gropet/www/framework/test/themes
      mkdir W:/home/gropet/www/framework/test/themes/classic
      mkdir W:/home/gropet/www/framework/test/themes/classic/views
   generate themes/classic/views/.htaccess
      mkdir W:/home/gropet/www/framework/test/themes/classic/views/layouts
      mkdir W:/home/gropet/www/framework/test/themes/classic/views/site
      mkdir W:/home/gropet/www/framework/test/themes/classic/views/system

Your application has been created successfully under W:\home\gropet\www\framework\test.

Всё заработало, даже при том, что у Денвера иногда случаются чудеса с невозможностью подключения модулей php, я это описывал в последней статье о веб-сокетах, но так до самого конца и не разобрался с причинами.
Upd 2015.09.27: чудеса с модулями случаются если приглашение в консоль не установлено на диске Денвера W:\>

Пытаясь понять почему http://gropet/framework/test не запускается и почему фреймворк не генерирует проект в WebRoot, я не нашел ответа. Вручную вынес проект в корень т.е. в www, изменив пути подключения фреймворка в index.php.
Посмотрев базовый проект, убедился в его хорошей структуре, однако, оказался совершенно недоволен системой url. Прочитав несколько статей и еще раз перечитав инструкцию, прописал правильный .htaccess раскоментировал urlManager и уже надеялся насладиться красивыми url, но меня ждало разочарование — все пути остались http://gropet/site/about формата. Терпеть свою беспомощность больше было нельзя и пришлось с большим вниманием отнестись к urlManager. Добавив в config/main.php костыли, которые, если будут грамотные люди в комментариях, когда-нибудь исправлю.

...
'urlManager'=>array(
'urlFormat'=>'path',
'rules'=>array(
'' => 'site/index',
'<controller:\w+>/<id:\d+>'=>'<controller>/view',
'<controller:\w+>/<action:\w+>/<id:\d+>'=>'<controller>/<action>',
'<controller:\w+>/<action:\w+>'=>'<controller>/<action>',
'/<action:\w+>'=>'site/<action>', //Yo, Petukhovsky is become yii master (selfsarcasm)
),
'showScriptName'=>false,
),
...

Почитав еще один день документацию и статьи якобы обучающие написанию своих проектов на Yii, я погрузился в изучение исходного кода проекта, что заставило меня в первый раз порадоваться за свой выбор Yii — MVC и ООП оказались как в лучших мечтах. Хотя, конечно, Eric Allman coding style и некоторые другие стилевые вещи меня огорчили, не смог найти ответ почему Yii не использует стиль на базе K&R (Керниган и Ричи). Но в yii 2 уже анонсировано использование стилей PSR-1 и PSR-2, что не может не радовать поклонников Yii.

2. Выбор шаблона (2 день)

Конечно, искать шаблон для yii, как это принято делать для wordpress или Joomla занятие бессмысленное, поскольку прикручивание любого шаблона к проекту на базе yii задача абсолютно типовая, которую проходят разработчики с самого 0, используя чистые html-шаблоны без логики виджетов и вывода статей наподобие WP. И мне удалось найти несколько шаблонов на html5 соответствующих или очень похожих на мой прототип в первом приближении. Я решил собрать готовый дизайн из нескольких шаблонов, отредактировав их самих (html+css+JavaScript), т.е. пришлось сделать новый шаблон и дизайн, просто для экономии времени взяв за основу несколько шаблонов с хорошими css каркасами для адаптивной верстки. Первым шагом было размещение ресурсов в корневой директории yii. Я специально вынес все папки ресурсов вроде css, img и js в каталог res, чтобы не захламлять корневую директорию. Затем довольно быстро удалось заменить views/layouts чему я был несказанно рад. На простую вставку и запуск шаблона ушло не более 30 минут.

3. Внедрение шаблона в Yii — фаза A (3 день)

Дальше началось самое интересное — я начал внедрять мой шаблон, который по пути в значительной мере был переверстан. Первое, с чем я столкнулся при внедрении шаблона это с виджетом zii.widgets.CMenu. Всё, казалось, идёт хорошо — я определил свои пункты меню с короткими URL как я люблю, но тут началось. Сразу удалось выявить 2 проблемы:

  • отличающаяся от вёрстки шаблона подсветка активного пункта меню (шаблон требовал li a class=active, а виджет на базе CMenu генерировал код li class=active a, конечно, можно было бы решить css-ом, но я решил глубже разобраться в yii и отредактировать render;
  • меню не определяло активные пункты для моих коротких url-адресов не в формате /, а в формате /.

Буквально за пол часа обе проблемы удалось решить. Для этого, уже обладая достаточным багажом прочитанных за первые дни, материалов, я догадался создать свой виджет ext.mywidgets.CMyMenu на базе zii.widgets.CMenu, перегрузив функцию рендера и определения активного пункта меню. К сожалению, я до сих пор не уверен правильный ли это путь с точки зрения философии yii, но, однозначно, возможность такого решения придала мне сил и уверенности в том, что yii станет моим любимым фреймворком.

4. Реализация логики хранения учётных записей пользователей — фаза A (3 день)

В этом моменте тоже всё оказалось весьма просто. Я нашел в блоге у Alexandr Semko готовый модуль yii-user (GitHub). Развернул его в папку modules, подключил в конфиге базу данных MySQL, и, фантастика, всё заработало! На всё про всё было затрачено не больше 1го часа. Но на этом лёгкая часть подошла к концу.

5. Внедрение шаблона в Yii — фаза B (4-6 день)

Ощутимые трудности я испытал при увязывании шаблонов с логикой умных форм регистрации модуля user. Дело в том, что формы, которые использовал yii, и модуль users не отличались оформленностью и адаптивностью, я решил немного улучшить их, применив к ним красивое решение из шаблона с адаптивным css и вспомогательным JavaScript для красоты обработки, наверное, это было необходимое, весьма трудоёмкое, хотя и не лучшее решение?

Сначала начались конфликты jQuery https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js шаблона и jQuery встроенного Yii. При чем, очень неприятные моменты связанные с тем, что yii использует устаревшую версию jQuery. Пришлось заменить её внутри фреймворка на актуальную 1.11.1, банальной заменой файла. Почистил assets, отлично, актуальная версия заработала. Теперь осталось заставить jQuery загружаться всегда, а не только при CActiveForm. И решение этой проблемы нашлось достаточно быстро — нужно было включить строку:

<?php Yii::app()->getClientScript()->registerCoreScript('jquery'); ?>

в layout/main.php. Я это пишу в надежде получить замечания и рекомендации относительно изящного использования Yii. Я сделал достаточно «православно»?

Затем не удавалось заменить CHtml::submitButton на CHtml::linkButton не порушив всю логику работы JavaScript. Закончилось это тем, что я развернул вторую копию Yii с оригинальной вёрсткой чтобы окончательно убедиться в не самой лучшей (актуальной) системе генерации html компонентами CHtml. Но в итоге разобрался, что я утащил форму с шаблона использующего JavaScript не только для красивостей, но и для валидации и вступающего в коллизию с JavaScript-ами валидаторов yii. Проблему удалось решить ручным переписыванием JavaScript отвечающего за подсветку и красивости формы. Да, это жестоко, но пришлось конкретно переписывать css форм Yii (form.css) и писать отдельный JS обработчик действий с формами, чтобы обеспечить красивую работу полей ввода, например, плавное исчезновение placeholder’ов.

В итоге, на регистрацию пользователей (без личного кабинета) ушло, без малого, 3 дня и из них 2,8 на оформление.

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

Казалось бы, всё получилось, но спустя некоторое время обнаружилась ошибка — сбрасывается заполненность полей при попытке регистрации и введении неверного кода CAPTCHA. Опять пришлось править JavaScript ответственный за работу с формами — наконце у меня есть решение красивого адаптивного оформления CActiveForm из yii.

Вёрстку личного кабинета пользователя я поставил в очередь после доработки логики (Model-Controller) модуля yii-users, необходимой для работы игрового клиента.

6. Реализация логики хранения учётных записей пользователей — фаза B (7-8 день)

Спустя неделю я вполне уверенно почувствовал себя в Yii. Разобрал достаточное количество документации и примеров, смог даже нормально и в полной мере интегрировать модуль users, но, какова была моя досада, когда наткнулся на отзывы об этом модуле и рекомендации к использованию yii-account. Но, что сделано, то будет переделано, но потом. Передо мной встала задача интеграции модуля users с игровым приложением Growing Crystals.

В качестве первого шага я просто слил весь код в один каталог. На странице /play вставил JavaScript библиотеку игрового клиента. На этом первый шаг интеграции игры и фреймворка был закончен — клиент загружался когда игрок был авторизован. Пришлось немного поколдовать над клиентом, чтобы он не спрашивал имя пользователя, а сразу при загрузке отправлял серверу хеш-код идентификатор игрока. Казалось дело за малым, создаем в users новое поле, отвечающее за хеш каждого игрока и проверяем его при подключении к ws серверу, но всё оказалось несколько сложнее чем мне показалось на первый взгляд.

Я глубже изучил как работает model в yii. Действительно хорошая вещь работа с БД как с объектами, хотя до конца и не понятно её внутреннее устройство и как она будет вести себя со сложными экземплярами классов, которые могут содержать вложенные экземпляры других классов. Но так глубоко я проникать не стал ограничившись лишь принципами автогенерации model и усвоив тонкости описания связей БД и модели на базе класса CActiveRecord. На всякий случай до внесения своих правок сохранил БД и произвел Reverse Engeneering в MySQL Workbench.

Структура БД (Reverse Engeneering) до моих правок

Структура БД (Reverse Engeneering) до моих правок

Зайдя под логином admin во вкладку профиль я увидел поле недоступное для обычных пользователей (Manage Users) я узнал о нём когда редактировал меню в view профиля. Меня приятно удивил большой ассортимент возможностей по управлению пользователями из панели администратора (буду теперь это меню здесь и везде называть именно так и даже возьму его за основу панели администора всего проекта). И, самое главное, меня приятно удивило наличие возможности редактировать поля записи пользователя (manage profile fields) с возможностью указания правил поведения этих полей, например, есть возможность сделать поле и не показывать его пользователю при регистрации. Для начала, пользуясь панелью администратора, я создал поле authhashkey, необходимое для авторизации клиента на ws.

Магия сработала, изменения отразились в БД, но модели остались не тронутыми, что не совсем понятно, потому как структура БД изменилась.

db_after_adding_field_2

Я не совсем верно указал настройки при создании поля, в частности, тип и require (не учел что речь идёт о форме регистрации, а не require аттрибуте в БД), соответственно, удалил и создал повторно.

create-field

Осталось генерировать authhashkey при регистрации и проверять его ws при подключении к нему пользователя. В модуле users контроллер разделен на несколько файлов, в каждом из которых описана логика работы для конкретной ситуации. Я добавил в RegistrationController.php строку генерации $profile->authhashkey. И при генерации соответствующая строка с хешем оказалась в БД. Только мне показалось что 9 символов для хранения ключа маловато и я решил увеличить длину до 18, чтобы уменьшить вероятность коллизии и увеличить устойчивость при подборе. authhashkey виден только в БД, это связано с теми настройками которые я задал при создании поля в профиле.

Перенеся view /play в зону обслуживания контроллером play модуля user передаём в него данные профиля, таким образом, зашиваем временный ключ авторизации. Для сохранения короткого URL, создаём новое правило в urlManager:

'/play' => 'user/play'

Далее возникает непростая дилемма, т.к. мне нужно обращаться к БД из демона игрового сервера, как это реализовать? Обращаться к БД напрямую, кидая в неё запросы или подключив yii?
Первый способ кажется более быстрым с точки зрения производительности и реализации, но второй способ кажется более правильным с точки зрения архитектуры, чтобы соблюсти принцип MVC и работать со всеми данными только через Model.
Будем считать БД единой средой с которой могут работать разные инструменты и реализуем логику работы с БД со стороны демона игрового сервера на базе прямых запросов в БД. Впоследствии возможен переход демона игрового сервера на yii.

На реализацию подключения и получения данных из БД демоном игрового сервера ушло не более полутора часов, немного пришлось обновить клиент и структуру классов игрового сервера.

диаграмма классов

Вторым этапом была реализация загрузки/сохранения пользователя при его разлогинивании, создание записи пользователя в БД при заведении нового аккаунта — это оказалось затруднительно т.к. все игровые данные по-умолчанию можно было почерпнуть только из логики игры которая была представлена в классах игры. Для того чтобы можно было быстро подключить и инициировать игровые классы, я немного видоизменил конструктор game_class, сделал возможность создания экземпляра не для демона, а в качестве библиотеки, в настоящее время размер такой библиотеки в памяти занимает совсем немного. А также значительно переработал все классы игры, отказавшись от функций, инкапсулировав их в виде статических методов в соответствующие классы. В итоге удалось привести все классы игры к формату похожему на модуль yii. Подключение «модуля» игры выглядит следующим образом.

'import'=>array(
'application.models.*',
'application.components.*',
//added module Users
'application.modules.user.models.*',
'application.modules.user.components.*',
//added Game
'webroot.game.class.*',
),

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

Разведя огромное количество копипасты, мне удалось средствами yii сохранять данные игрока по-умолчанию из player_class::load_from_ini_file в БД. Также демон обзавелся методами сохранения и загрузки промежуточных данных игрока в БД. Теперь все данные игрока корректно хранятся в БД, чего пока нельзя сказать об игровом мире, гибнущем при перезагрузке демона.

7. Внедрение шаблона в Yii — фаза C (9 день)

При реализации личного кабинета пользователя на базе модуля yii-users я действительно несколько удивился количеству повторяющегося кода, например, одинаковое меню во всех view/profile, повторяющиеся view (например, при редакции и создания профиля, хотя их можно было упаковать в 1). Но в целом, после значительного погружения и правок, модуль мне показался вполне подходящим и я даже оптимизировал генерацию меню, сделав её в одном месте. Однако, чуть позже, я столкнулся с нехваткой опыта: в yii много где принято создавать меню $this->menu, затем вызывать отображение в \\main\column2, что при моём адаптиве оказалось просто не допустимым и я решил вместо создания навороченной css-ки, просто вернуть все меню непосредственно в view, но сделать это только для того, чтобы отображать его непосредственно в теле content страницы.

Может показаться странным, но работа с логикой оказалась гораздо менее трудоёмкой чем борьба с вёрсткой и css yii.

7. Последние штрихи в вёрстке (9 день)

Доделал такие приятные мелочи как модуль disqus, верстку профиля. Нужно будет обязательно заняться английскими текстами, а то пока они представляют из себя «мы вас не понимать ин инглиш».

Маинд-карта

2014.08.20

2014.08.20

Очевидно, что реализовать веб-сайт в полной мере не удалось, хотя и был проделан огромный объём работы. Для того чтобы ускорить выпуск игры, было принято решение публиковать веб-сайт с главным функционалом и в дизайне, оставив на потом вещи связанные с личным кабинетом игрока, рейтингами, социальными сетями и другими фичами, что хорошо видно в майнд-карте.

главная страница

главная страница

Краткий итог: Веб-сайт запущен, регистрация открыта, игровой мир пока по-прежнему живет несколько суток.

Сводка

Начало: 25 апреля 2014 года.
Приостановка проекта: 21 мая 2014 — 13 июля 2014 года.
Команда: 1 (25 апреля), 1 (14 июля).
Израсходовано: 232 + 80 = 312 чч., 96 + 0 = 96 чч.
Рабочих дней: 32 + 10 = 42, 12 + 0 = 12
Средняя производительность: 312/42 = 7,42 чч/день, 96/12 = 8 чч/день

Описание игрового процесса

60/100

Расчет баланса

60/100

Игровая графика

30/100

Веб-клиент

70/100

Игровой сервер

70/100

Веб-сайт

70/100

ИТОГО

355/600

Продолжение: Growing Crystals vol 16. Подготовка к первому альфа-релизу. Итоги