Написание консольных команд и заданий cron — КиберПедия 

Двойное оплодотворение у цветковых растений: Оплодотворение - это процесс слияния мужской и женской половых клеток с образованием зиготы...

История создания датчика движения: Первый прибор для обнаружения движения был изобретен немецким физиком Генрихом Герцем...

Написание консольных команд и заданий cron

2017-07-24 266
Написание консольных команд и заданий cron 0.00 из 5.00 0 оценок
Заказать работу

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

В предыдущих пунктах были реализованы методы для удаления таких корзин. Но чтобы это удаление происходило не вручную, а автоматически была реализована консольная команда, удаляющая корзины, ожидающие оплаты (по умолчанию старее трёх суток), а также неоплаченные корзины (по умолчанию одни сутки). Программный код данной команды представлен в приложении G. После ввода «php bin/console difuks:dazzle:basket:clear-old» заброшенные корзины, если они существуют, удаляются, а в консоли выводится количество удалённых корзин. Тем не менее, данный процесс всё ещё не автоматизирован. Для того, чтобы команда выполнялась автоматически, необходимо добавит её в задания cron [26]. Для систем Debian (в данном случае Ubuntu) это реализуется командой в терминале: «echo "0 0 * * * root cd /path/to/project/ && bin/console difuks:dazzle:basket:clear-old >> var/logs/cron.log" >> /etc/crontab». После ввода данной команды очистка заброшенных корзин будет производиться каждые сутки в 00:00 по серверному времени.

Также необходимо автоматизировать отправку почты, сохранённой во временный файл и ожидающей отправки с помощью команды в терминале «echo "0 * * * * root cd /path/to/project/ && bin/console swiftmailer:spool:send --message-limit=5 --env=prod >> var/logs/cron.log" >> /etc/crontab». Отправка почты будет производится каждый час по 5 email максимум. Это гарантирует защиту от блокировки за рассылку спама.

Тестирование

Тестирование осуществлялось с помощью встроенных механизмов Symfony, расширяющих возможности PHPUnit [27]. Были реализованы функциональные тесты, осуществляющие проверку корректности выполнения основных функций реализуемого веб-приложения, а именно:

· Корректное осуществление авторизации;

· Корректная деавторизация;

· Корректный статус ответа всех страниц публичной части;

· Осуществление перенаправления неавторизованного пользователя на страницу авторизации при попытке входа в административную панель, а также на страницу корзины; корректное отображения этих страниц для авторизованного пользователя;

· Корректное осуществление добавления игры в корзину (существование хотя бы одной доступной для покупки игры, невозможность покупки неавторизованному пользователю).

Программный код последнего из указанных тестов и конфигурация PHPUnit представлена в приложении G. Для запуска тестов необходимо выполнить команду: «phpunit», находясь в корневой директории проекта. В случае неуспешного прохождения какого-либо из тестов в консоли отобразиться описание ошибки и дополнительная информация по ней.

Перенос проекта на production-сервер

Перенос проекта на production-сервер состоял из следующих этапов:

· Приведения всего программного кода к стандартам Symfony (расширение стандартов PSR-2) с помощью утилиты php-cs-fixer [28].

· Отправка всех изменений на сервере разработки в систему контроля версий.

· Аренда виртуального сервера на хостинге FirstVDS. Характеристики арендованного сервера: Процессор Intel Xeon 2,4 ГГц (1 ядро), оперативная память 1 ГБ, диск HDD+SSD 30 ГБ, операционная система Ubuntu 16.04.

· Регистрация домена difk.ru и изменение серверов имён на серверы имён FirstVDS для доступа к серверу по адресу.

· Подключение по ssh к удалённому серверу.

· Установка PHP 7.1, PostgreSQL, git, composer, npm, webpack, bower, phpunit.

· Настройка PHP, создание базы данных PostgreSQL (процесс аналогичен таковому на сервере разработки).

· Клонирования репозитория из системы контроля версий.

· Установка зависимостей composer, npm, bower. Запуск сборщика webpack для production-среды (NODE_ENV=production webpack).

· Миграция базы данных.

· Запуск тестов PHPUnit.

· Добавление заданий cron.

· Настройка виртуального хоста apache2 (для доступа из сети по адресу difk.ru к папке web приложения).

· Перезапуск apache2.

После всех выполненных действий веб-приложение Symfony готово к работе и доступно по адресу http://difk.ru.

ЗАКЛЮЧЕНИЕ

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

В ходе анализа существующих современных технологий и моделей в области веб были обозначены проблемы в области веб-разработки и приведены некоторые типовые решения, способствующие устранению обозначенных проблем и противоречий. В качестве методологии в области веб-проектирования был обозначен шаблон проектирования MVC, реализующий ООП-подход в области веб.

Технологии Symfony Framework отвечают современным требованиям и стандартам, поддерживают шаблон проектирования MVC, обеспечивают безопасность доступа к данным, увеличивают быстродействие запросов, обладают высоким уровнем масштабируемости. Основные компоненты Symfony Framework: бандлы, Doctrine, маршрутизация, контроллеры, шаблонизатор Twig, сервисы и т.д. – предоставляют инструментарий для разработки сложно структурируемых веб-приложений в соответствии с принятыми стандартами в области веб. Отсутствие официальной русскоязычной документации по Symfony Framework поставило дополнительную задачу по адаптации официальной документации разработчика на русский язык.

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

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

Таким образом цель выпускной квалификационной работы – выполнить анализ возможностей технологий Symfony Framework и реализовать приложение интернет-магазина компьютерных игр на основе современных подходов в области веб-разработки – была достигнута.

Результаты выпускной квалификационной работы могут быть использованы как теоретическое пособие по Symfony Framework, а практическая разработка - в качестве модели проектирования и реализации веб-приложения. При соответствующей адаптации к предметной области приложение может быть использовано как полноценный интернет-магазин.


 

СПИСОК ЛИТЕРАТУРЫ

1. History of the Web. URL: http://webfoundation.org/about/vision/history-of-the-web/ (дата обращения: 13.05.2016)

2. Internet Live Stats. URL: http://www.internetlivestats.com/ (дата обращения: 15.04.2017)

3. Selmanovic D.. The 10 Most Common Mistakes Web Developers Make. URL: https://www.toptal.com/web/top-10-mistakes-that-web-developers-make (дата обращения: 15.04.2017)

4. Helmke M., Joseph E., Rey J., Ballew P., Hill B. The Official Ubuntu Book. Upper Saddle River, NJ: Prentice Hall, 2014. 368 c.

5. Simpson K. You Don't Know JS: ES6 & Beyond. Sebastopol, CA: O'Reilly Media, 2015. 280 с.

6. Composer. URL: https://getcomposer.org/doc/ (дата обращения 19.04.2016)

7. Npm Documentation. URL: https://docs.npmjs.com/ (дата обращения 19.04.2017)

8. Potencier F. The Twig Book. Paris: SensioLabs, 2017. 156 с.

9. Romer M. PHP Persistence: Concepts, Techniques and Practical Solutions with Doctrine. New York City, NA: Apress, 2016. 107 с.

10. Chacon S., Straub B. Pro Git. New York City, NA: Apress, 2016. 456 с.

11. Chaudhary M., Kumar A. Birmingham. PhpStorm Cookbook. Birmingham: Packt Publishing, 2014. 256 с.

12. Webpack. URL: https://webpack.js.org/ (дата обращения 10.04.2017)

13. Material Design Light. URL: https://getmdl.io/ (дата обращения 21.04.2017)

14. Less.js: Getting started. URL: http://lesscss.org/ (дата обращения 21.04.2017)

15. Potencier F. Symfony. The Book. Paris: SensioLabs, 2016. 219 с.

16. Stones R. Beginning Databases with PostgreSQL: From Novice to Professional. New York City, NA: Apress, 2016. 664 с.

17. Hopkins C. The MVC Pattern and PHP. URL: https://www.sitepoint.com/the-mvc-pattern-and-php-1/ (дата обращения 11.04.2017)

18. Bierer Doug. PHP 7 Programming Cookbook. Birmingham: Packt, 2016. 610 с.

19. A Framework or a CMS? What is better to choose? URL: http://www.web-and-development.com/a-framework-or-a-cms-what-is-better-to-choose/ (дата обращения 11.04.2017)

20. Symfony versus Flat PHP. URL: https://symfony.com/doc/current/introduction/from_flat_php_to_symfony2.html (дата обращения 11.04.2017)

21. Symfony. URL: https://www.drupal.org/project/symfony (дата обращения 11.04.2017)

22. FOSUserBundle. URL: http://knpbundles.com/FriendsOfSymfony/FOSUserBundle (дата обращения 11.04.2017)

23. SonataAdminBundle. URL: https://sonata-project.org/bundles/admin/3-x/doc/index.html (дата обращения 11.04.2017)

24. Methodology BEM. URL: https://en.bem.info/methodology/ (дата обращения 20.04.2017)

25. Robokassa user manual. URL: https://docs.robokassa.ru/en/ (дата обращения 25.04.2017)

26. CronHowto. URL: https://help.ubuntu.com/community/CronHowto (дата обращения 25.04.2017)

27. Bergmann S. PHPUnit Manual. Siegburg: Sebastian Bergmann, 2017. 175 с.

28. PHP Coding Standards Fixer. URL: http://cs.sensiolabs.org/ (дата обращения 10.06.2017)

 


ПРИЛОЖЕНИЯ

Приложение A. Конфигурационные файлы

/app/config/parameters.yml

/.idea/

/var/

/vendor/

/web/assets/

/web/cache/

/web/upload/

/node_modules/

/bower_components/

.php_cs.cache

/web/bundles/

Листинг 19. Содержимое файла.gitignore

{

"name": "symfony-game-shop",

"version": "0.0.1",

"dependencies": {

"assets-webpack-plugin": "^3.2.0",

"autoprefixer": "^6.3.6",

"babel-core": "^6.17.0",

"babel-loader": "^6.2.5",

"babel-preset-es2015": "^6.16.0",

"bower": "^1.7.2",

"clean-webpack-plugin": "^0.1.8",

"clndr": "^1.4.6",

"css-loader": "^0.23.1",

"css-mqpacker": "^4.0.1",

"exports-loader": "^0.6.2",

"extract-text-webpack-plugin": "^1.0.1",

"file-loader": "^0.8.5",

"getmdl-select": "^1.0.4",

"imports-loader": "^0.6.5",

"jquery": "^1.11.3",

"jquery-mousewheel": "^3.1.13",

"jquery-ui": "^1.10.5",

"jquery.dotdotdot": "^1.7.4",

"less": "^2.3.1",

"less-loader": "^2.2.2",

"material-design-lite": "^1.3.0",

"moment": "^2.15.1",

"normalize.css": "^4.1.1",

"nouislider": "^9.2.0",

"picturefill": "^3.0.2",

"postcss-loader": "^0.8.2",

"resolve-url-loader": "^1.4.3",

"slick-carousel": "^1.6.0",

"style-loader": "^0.13.0",

"underscore": "^1.8.3",

"url-loader": "^0.5.7",

"webpack": "^1.12.11"

}

}

Листинг 20. Содержимое файла package.json

Приложение B. Класс сущности Game

<?php

 

declare (strict_types= 1);

 

namespace Difuks\DazzleBundle\Entity;

 

use Doctrine\ORM\Mapping as ORM;

use Doctrine\Common\Collections\ArrayCollection;

 

/**

* Game.

*

* @ORM\Table(name="game")

* @ORM\Entity(repositoryClass="Difuks\DazzleBundle\Repository\GameRepository")

*/

Class Game

{

/**

* @var int

*

* @ORM\Column(name="id", type="integer")

* @ORM\Id

* @ORM\GeneratedValue(strategy="AUTO")

*/

private $id;

 

/**

* @var string

*

* @ORM\Column(name="name", type="string", length=255)

*/

private $name;

 

/**

* @var string

*

* @ORM\Column(name="code", type="string", length=255, unique=true)

*/

private $code;

 

/**

* @var Image

*

* @ORM\ManyToOne(targetEntity="Image", cascade={"persist"})

* @ORM\JoinColumn(nullable=true)

*/

private $logo;

 

/**

* @var \DateTime

*

* @ORM\Column(name="release_date", type="datetime")

*/

private $releaseDate;

 

/**

* @var string

*

* @ORM\Column(name="site", type="string", length=255)

*/

private $site;

 

/**

* @var string

*

* @ORM\Column(name="video", type="string", length=255)

*/

private $video;

 

/**

* @var int

*

* @ORM\Column(name="age_restrictions", type="integer")

*/

private $ageRestrictions;

 

/**

* @var string

*

* @ORM\Column(name="description", type="text")

*/

private $description;

 

/**

* @var string

*

* @ORM\Column(name="system_requirements", type="text")

*/

private $systemRequirements;

 

/**

* @var float

*

* @ORM\Column(name="price", type="float")

*/

private $price;

 

/**

* @var Genre[]|ArrayCollection

*

* @ORM\ManyToMany(targetEntity="Genre", inversedBy="games", cascade={"persist"})

* @ORM\JoinTable(name="game_genre")

*/

private $genres;

 

/**

* @var Developer

*

* @ORM\ManyToOne(targetEntity="Developer", cascade={"persist"}, inversedBy="games")

* @ORM\JoinColumn(nullable=false)

*/

private $developer;

 

/**

* @var Publisher

*

* @ORM\ManyToOne(targetEntity="Publisher", cascade={"persist"})

* @ORM\JoinColumn(nullable=false)

*/

private $publisher;

 

/**

* @var Image[]|ArrayCollection

*

* @ORM\ManyToMany(targetEntity="Image", inversedBy="games", cascade={"persist"})

* @ORM\JoinTable(name="game_screenshots")

*/

private $screenshots;

 

/**

* @var Key[]|ArrayCollection

*

* @ORM\OneToMany(

* targetEntity="Key",

* mappedBy="game",

* orphanRemoval=true,

* cascade={"persist"}

*)

*/

private $keys;

 

/**

* @var bool

*

* @ORM\Column(name="multiplayer", type="boolean")

*/

private $multiplayer;

 

/**

* @var Review[]|ArrayCollection

*

* @ORM\OneToMany(

* targetEntity="Review",

* mappedBy="game",

* orphanRemoval=true,

* cascade={"persist"}

*)

*/

private $reviews;

 

/**

* @var Discount

*

* @ORM\ManyToOne(targetEntity="Discount", inversedBy="games", cascade={"persist"})

* @ORM\JoinColumn(nullable=true, onDelete="SET NULL")

*/

private $discount;

 

/**

* @var BasketProduct[]|ArrayCollection

*

* @ORM\OneToMany(

* targetEntity="BasketProduct",

* mappedBy="game",

* orphanRemoval=true

*)

*/

private $basketProducts;

 

/**

* @var int

*

* @ORM\Column(name="buy_count", type="integer", nullable=true)

*/

private $buyCount;

 

/**

* @var \DateTime

*

* @ORM\Column(name="last_buy", type="datetime", nullable=true)

*/

private $lastBuy;

 

/**

* @var float

*

* @ORM\Column(name="rate", type="float", nullable=true)

*/

private $rate;

 

/**

* @var bool

*

* @ORM\Column(name="is_released", type="boolean", options={"default": true})

*/

private $isReleased;

 

/**

* @var bool

*

* @ORM\Column(name="is_rus", type="boolean", options={"default": true})

*/

private $isRus;

 

public function __construct ()

{

$this->releaseDate = new \DateTime();

$this->genres = new ArrayCollection();

$this->keys = new ArrayCollection();

$this->screenshots = new ArrayCollection();

$this->reviews = new ArrayCollection();

$this->basketProducts = new ArrayCollection();

$this->buyCount = 0;

$this->rate = 0;

}

 

public function __toString ()

{

return (string) $this->getName();

}

 

public function getId ()

{

return $this->id;

}

 

public function setName (string $name)

{

$this->name = $name;

 

return $this;

}

 

public function getName ()

{

return $this->name;

}

 

public function setCode (string $code)

{

$this->code = $code;

 

return $this;

}

 

public function getCode ()

{

return $this->code;

}

 

public function setLogo (Image $logo)

{

$this->logo = $logo;

 

return $this;

}

 

public function getLogo ()

{

return $this->logo;

}

 

public function setReleaseDate (\DateTime $releaseDate)

{

$this->releaseDate = $releaseDate;

 

return $this;

}

 

public function getReleaseDate ()

{

return $this->releaseDate;

}

 

public function setSite (string $site)

{

$this->site = $site;

 

return $this;

}

 

public function getSite ()

{

return $this->site;

}

 

public function setVideo (string $video)

{

$this->video = $video;

 

return $this;

}

 

public function getVideo ()

{

return $this->video;

}

 

public function setAgeRestrictions (int $ageRestrictions)

{

$this->ageRestrictions = $ageRestrictions;

 

return $this;

}

 

public function getAgeRestrictions ()

{

return $this->ageRestrictions;

}

 

public function setDescription (string $description)

{

$this->description = $description;

 

return $this;

}

 

public function getDescription ()

{

return $this->description;

}

 

public function setSystemRequirements (string $systemRequirements)

{

$this->systemRequirements = $systemRequirements;

 

return $this;

}

 

public function getSystemRequirements ()

{

return $this->systemRequirements;

}

 

public function setPrice (float $price)

{

$this->price = $price;

 

return $this;

}

 

public function getPrice ()

{

return (int) $this->price;

}

 

public function addGenre (Genre $genre)

{

$this->genres->add($genre);

 

return $this;

}

 

public function removeGenre (Genre $genre)

{

$this->genres->removeElement($genre);

 

return $this;

}

 

public function getGenres ()

{

return $this->genres;

}

 

public function setGenres (ArrayCollection $genres)

{

$this->genres = $genres;

}

 

public function addScreenshot (Image $screenshot)

{

$this->screenshots->add($screenshot);

 

return $this;

}

 

public function removeScreenshot (Image $screenshot)

{

$this->screenshots->removeElement($screenshot);

 

return $this;

}

 

public function getScreenshots ()

{

return $this->screenshots;

}

 

public function setScreenshots (ArrayCollection $screenshots)

{

$this->screenshots = $screenshots;

}

 

public function getDeveloper ()

{

return $this->developer;

}

 

public function setDeveloper (Developer $developer)

{

$this->developer = $developer;

 

return $this;

}

 

public function getPublisher ()

{

return $this->publisher;

}

 

public function setPublisher (Publisher $publisher)

{

$this->publisher = $publisher;

 

return $this;

}

 

public function getKeys ()

{

$unPayedKeys = new ArrayCollection();

foreach ($this->keys as $key) {

if ($key->getStatus() == 0) {

$unPayedKeys->add($key);

}

}

 

return $unPayedKeys;

}

 

public function addKey (Key $key)

{

$this->keys->add($key);

$key->setGame($this);

 

return $this;

}

 

public function removeKey (Key $key)

{

$this->keys->removeElement($key);

 

return $this;

}

 

public function getReviews ()

{

return $this->reviews;

}

 

public function addReview (Review $review)

{

$this->reviews->add($review);

$review->setGame($this);

$rate = $review->getRate();

$count = 1;

foreach ($this->getReviews() as $newReview) {

$rate += $newReview->getRate();

++$count;

}

$rate = round($rate / $count, 1);

$this->setRate($rate);

 

return $this;

}

 

public function removeReview (Review $review)

{

$this->reviews->removeElement($review);

 

return $this;

}

 

public function getRate ()

{

return $this->rate;

}

 

public function setRate (float $rate)

{

$this->rate = $rate;

 

return $this;

}

 

public function getIntRate ()

{

return (int) $this->getRate();

}

 

public function setMultiplayer (bool $multiplayer)

{

$this->multiplayer = $multiplayer;

 

return $this;

}

 

public function getMultiplayer ()

{

return $this->multiplayer;

}

 

public function getDiscount ()

{

return $this->discount;

}

 

public function setDiscount ($discount)

{

$this->discount = $discount;

 

return $this;

}

 

public function removeDiscount ()

{

$this->discount = null;

 

return $this;

}

 

public function getDiscountPrice ()

{

if ($this->getDiscount()) {

$discountPrice = $this->getPrice() - $this->discount->getValue() * $this->getPrice() / 100;

} else {

$discountPrice = $this->getPrice();

}

 

return (int) $discountPrice;

}

 

public function getIsDiscount ()

{

return $this->getDiscountPrice()!= $this->getPrice();

}

 

public function getBasketProducts ()

{

return $this->basketProducts;

}

 

public function getBuyCount ()

{

return $this->buyCount;

}

 

public function setBuyCount ($buyCount)

{

$this->buyCount = $buyCount;

 

return $this;

}

 

public function getLastBuy ()

{

return $this->lastBuy;

}

 

public function setLastBuy (\DateTime $lastBuy)

{

$this->lastBuy = $lastBuy;

 

return $this;

}

 

public function getIsReleased ()

{

return $this->isReleased;

}

 

public function setIsReleased (bool $isReleased)

{

$this->isReleased = $isReleased;

 

return $this;

}

 

public function getIsFavorite (User $user = null)

{

if ($user == null) {

return false;

}

$favorites = $user->getFavoriteGames();

foreach ($favorites as $favorite) {

if ($favorite->getId() == $this->getId()) {

return true;

}

}

 

return false;

}

 

public function getReviewCount ()

{

return $this->reviews->count();

}

 

public function getIsRus ()

{

return $this->isRus;

}

 

public function setIsRus (bool $isRus)

{

$this->isRus = $isRus;

 

return $this;

}

 

public function getKeysCount ()

{

return $this->getKeys()->count();

}

}

Листинг 21. Класс сущности Game


Приложение C. Репозиторий GameRepository сущности Game

<?

namespace Difuks\DazzleBundle\Repository;

use Doctrine\ORM\EntityRepository;

use Doctrine\ORM\Query\Expr\Join;

class GameRepository extends EntityRepository

{

public function count (): int

{

$db = $this->createQueryBuilder('t');

return $db

->select('count(t.id)')

->getQuery()

->getSingleScalarResult();

}

public function maxPrice ()

{

$db = $this->createQueryBuilder('t');

return $db

->select('MAX(t.price)')

->getQuery()

->getSingleScalarResult();

}

public function minPrice ()

{

$minPrice = $this->getEntityManager()

->createQuery(

'SELECT

round(MIN(CASE WHEN g.discount IS NULL THEN g.price ELSE (g.price - g.price * d.value / 100) END),1)

FROM DifuksDazzleBundle:Game g

LEFT JOIN DifuksDazzleBundle:Discount d

WHERE g.discount = d'

)

->getSingleScalarResult();

return $minPrice;

}

public function maxAgeRest ()

{

$db = $this->createQueryBuilder('t');

return $db

->select('MAX(t.ageRestrictions)')

->getQuery()

->getSingleScalarResult();

}

public function minAgeRest ()

{

$db = $this->createQueryBuilder('t');

return $db

->select('MIN(t.ageRestrictions)')

->getQuery()

->getSingleScalarResult();

}

public function findByFilter (array $filter = [], array $sort = [], int $page = 1, $count = 9)

{

$query = $this->createQueryBuilder('g');

$actualPrice = 'CASE WHEN g.discount IS NULL THEN g.price ELSE (g.price - g.price * d.value / 100) END';

$query->select("g, $actualPrice AS HIDDEN price");

$query

->leftJoin('DifuksDazzleBundle:Discount', 'd', Join::WITH, 'g.discount = d');

if (isset($filter['price']['min'])) {

$minPrice = $filter['price']['min'];

$query->andWhere("$actualPrice >= $minPrice");

}

if (isset($filter['price']['max'])) {

$maxPrice = $filter['price']['max'];

$query->andWhere("$actualPrice <= $maxPrice");

}

if (isset($filter['age']['min'])) {

$ageMin = $filter['age']['min'];

$query->andWhere("g.ageRestrictions >= $ageMin");

}

if (isset($filter['age']['max'])) {

$ageMax = $filter['age']['max'];

$query->andWhere("g.ageRestrictions <= $ageMax");

}

if (isset($filter['rate']['min'])) {

$rateMin = $filter['rate']['min'];

$query->andWhere("g.rate >= $rateMin");

}

if (isset($filter['rate']['max'])) {

$rateMax = $filter['rate']['max'];

$query->andWhere("g.rate <= $rateMax");

}

if (isset($filter['isReleased'])) {

$query->andWhere('g.isReleased = TRUE');

}

if (isset($filter['isDiscount'])) {

$query->andWhere('g.discount IS NOT NULL');

}

if (isset($filter['genre'])) {

$query->andWhere(':genres MEMBER OF g.genres');

$query->setParameter('genres', $filter['genre']);

}

$order = $sort['order'];

$by = $sort['by'];

if ($order == 'price') {

$query->addOrderBy('price', $by);

} else {

$query->addOrderBy("g.$order", $by);

}

$games = $query->getQuery()->setMaxResults($count * $page)->setFirstResult(($page - 1) * $count)->getResult();

$totalCount = count($query->select('g.id')->orderBy('g.id')->getQuery()->getResult());

return [

'elements' => $games,

'page' => [

'count' => ceil($totalCount / $count),

'current' => $page,

],

];

}

public function getDisocuntGames ()

{

$db = $this->createQueryBuilder('g');

$db->where('g.discount IS NOT NULL')->setFirstResult(0)->setMaxResults(10);

return $db->getQuery()->getResult();

}

}

Листинг 22. Репозиторий GameRepository сущности Game


Приложение D. Класс генерации формы на основе сущности

<?

namespace Difuks\DazzleBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

use Symfony\Component\Validator\Constraints as Assert;

/**

* Feedback.

*

* @ORM\Table(name="feedback")

* @ORM\Entity()

*/

Class Feedback

{

/**

* @var int

*

* @ORM\Column(name="id", type="integer")

* @ORM\Id

* @ORM\GeneratedValue(strategy="AUTO")

*/

private $id;

/**

* @var string

*

* @Assert\NotBlank()

* @ORM\Column(name="name", type="string", length=255, nullable=false)

*/

private $name;

/**

* @var string

*

* @Assert\NotBlank()

* @Assert\Email()

* @ORM\Column(name="email", type="string", length=255, nullable=false)

*/

private $email;

/**

* @var string

*

* @Assert\NotBlank()

* @ORM\Column(name="text", type="text", length=255, nullable=false)

*/

private $text;

/**

* @var \DateTime

*

* @ORM\Column(name="date", type="datetime")

*/

private $date;

public function __construct ()

{

$this->date = new \DateTime();

}

public function getId ()

{

return $this->id;

}

public function getName ()

{

return $this->name;

}

public function setName (string $name)

{

$this->name = $name;

}

public function getEmail ()

{

return $this->email;

}

public function setEmail (string $email)

{

$this->email = $email;

}

public function getText ()

{

return $this->text;

}

public function setText (string $text)

{

$this->text = $text;

}

public function getDate ()

{

return $this->date;

}

public function setDate (\DateTime $date)

{

$this->date = $date;

}

}

Листинг 22. Сущность Feedback

<?

namespace Difuks\DazzleBundle\Form;

use Difuks\DazzleBundle\Entity\Feedback;

use Symfony\Component\Form\AbstractType;

use Symfony\Component\Form\Extension\Core\Type\EmailType;

use Symfony\Component\Form\Extension\Core\Type\TextType;

use Symfony\Component\Form\Extension\Core\Type\TextareaType;

use Symfony\Component\Form\FormBuilderInterface;

use Symfony\Component\OptionsResolver\OptionsResolver;

class FeedbackType extends AbstractType

{

public function buildForm (FormBuilderInterface $builder, array $options)

{

$builder

->add('name', TextType::class, [

'label' => 'Имя',

'required' => false,

'mapped' => true,

])

->add('email', EmailType::class, [

'label' => 'Email',

'required' => false,

])

->add('text', TextareaType::class, [

'label' => 'Текст обращения',

'required' => false,

])

;

}

public function configureOptions (OptionsResolver $resolver)

{

$resolver->setDefaults([

'data_class' => Feedback::class,

]);

}

}

Листинг 23. Класс генерации формы обратной связи


Приложение E. Маршруты и контроллер публичной части сайта

index:

path: /

defaults: { _controller: DifuksDazzleBundle:Public:index }

 

genres:

path: /genres/

defaults: { _controller: DifuksDazzleBundle:Public:genres }

 

catalog_all:

path: /catalog/

defaults: { _controller: DifuksDazzleBundle:Public:catalog }

 

catalog:

path: /catalog/{code}

defaults: { _controller: DifuksDazzleBundle:Public:catalog }

 

product:

path: /product/{code}

defaults: { _controller: DifuksDazzleBundle:Public:product, code: default }

 

basket:

path: /basket/

defaults: { _controller: DifuksDazzleBundle:Public:basket }

 

feedback:

path: /feedback/

defaults: { _controller: DifuksDazzleBundle:Public:feedback }

 

unsubscribe:

path: /unsubscribe/{hash}

defaults: { _controller: DifuksDazzleBundle:Public:unsubscribe }

Листинг 24. Конфигурация маршрутов публичной части

<?

declare (strict_types= 1);

namespace Difuks\DazzleBundle\Controller;

use Difuks\DazzleBundle\Entity\Feedback;

use Difuks\DazzleBundle\Entity\Game;

use Difuks\DazzleBundle\Entity\Genre;

use Difuks\DazzleBundle\Entity\Subscribes;

use Difuks\DazzleBundle\Form\FeedbackType;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;

use Symfony\Component\HttpFoundation\Response;

class PublicController extends Controller

{

public function indexAction (): Response

{

return $this->render('DifuksDazzleBundle:Public:index.html.twig');

}

public function genresAction (): Response

{

$genres = $this->getDoctrine()->getRepository(Genre::class)->findBy([], ['id' => 'ASC']);

$totalCount = $this->getDoctrine()->getRepository(Game::class)->count();

return $this->render('DifuksDazzleBundle:Public:genres.html.twig', ['genres' => $genres, 'totalCount' => $totalCount]);

}

public function catalogAction (Genre $genre = null): Response

{

return $this->render('DifuksDazzleBundle:Public:catalog.html.twig', ['genre' => $genre]);

}

public function productAction (Game $game): Response

{

return $this->render('DifuksDazzleBundle:Public:product.html.twig', ['game' => $game]);

}

public function basketAction (): Response

{

return $this->render('@DifuksDazzle/Public/basket.html.twig');

}

public function feedbackAction (): Response

{

$form = $this->createForm(FeedbackType::class);

return $this->render('@DifuksDazzle/Public/feedback.html.twig', ['form' => $form->createView()]);

}

public function unsubscribeAction (Subscribes $subscribe): Response

{

$this->get('difuks.dazzle.social_service')->unSubscribe($subscribe);

return $this->render('@DifuksDazzle/Public/unsubscribe.html.twig');

}

}

Листинг 25. Контроллер публичной части сайта


Приложение F. Сервис для работы с корзиной и заказами. Настройка сервисов

<?

namespace Difuks\DazzleBundle\Services;

use Difuks\DazzleBundle\Entity\Basket;

use Difuks\DazzleBundle\Entity\BasketProduct;

use Difuks\DazzleBundle\Entity\Game;

use Doctrine\ORM\EntityManager;

use Symfony\Component\DependencyInjection\Container;

Class OrderService

{

protected $container;

protected $pass;

protected $pass2;

protected $login;

protected $em;

protected $mailer;

public function __construct (Container $container, EntityManager $em, \Swift_Mailer $mailer)

{

$this->container = $container;

$this->mailer = $mailer;

$this->em = $em;

$this->login = $container->getParameter('robokassa.login');

$this->pass = $container->getParameter('robokassa.password');

$this->pass2 = $container->getParameter('robokassa.password2');

}

/**

* Получает url для перевода в систему оплаты.

*

* @param int $id id заказа

* @param float $sum сумма заказа

*

* @return string url

*/

public function getUrl (int $id, float $sum): string

{

$descr = 'Оформление заказа №'.$id;

$crc = md5("$this->login:$sum:$id:$this->pass");

$url = "https://auth.robokassa.ru/Merchant/Index.aspx?MrchLogin=$this->login&".

"OutSum=$sum&InvId=$id&Description=$descr&SignatureValue=$crc&IsTest=1";

return $url;

}

/**

* Обрабатывает результат запроса от службы оплаты.

*

* @param int $id id заказа

* @param float $sum сумма заказа

* @param string $crc хэш

*

* @throws \Exception в случае несовпадения хэша

*/

public function setResult (int $id, float $sum, string $crc): void

{

$crc = strtoupper($crc);

$myCrc = strtoupper(md5("$sum:$id:$this->pass2"));

if ($myCrc!= $crc) {

throw new \Exception('Неверные данные оплаты. Хеш '.$myCrc.' и '.$crc.' не совпадают');

}

$basket = $this->em->getRepository(Basket::class)->find($id);

$fromEmail = $this->container->getParameter('mailer_user');

if ($basket->getPaymentState()!= 2) {

$this->sendEmailAboutPay($basket, $fromEmail);

$this->refreshGamesRate($basket);

$basket->setPaymentState(2);

$this->em->persist($basket);

$this->em->flush();

}

}

/**

* Отправляет email об успешной оплате.

*

* @param Basket $basket

* @param string $email

*/

protected function sendEmailAboutPay (Basket $basket, string $email): void

{

$body = [];

$keys = $basket->getKeys();

/*

* @var Key

*/

foreach ($keys as $key) {

$body[$key->getGame()->getName()][] = $key->getKey();

}

$message = \Swift_Message::newInstance()

->setSubject('Покупка игр')

->setFrom($email)

->setTo($basket->getUser()->getEmail())

->setBody($this->container->get('templating')->render(

'@DifuksDazzle/Email/keys.send.html.twig',

['body' => $body]

),

'text/html');

$this->mailer->send($message);

}

/**

* Обновляет количество покупок игры, а так же дату последней покупки.

*

* @param Basket $basket

*/

protected function refreshGamesRate (Basket $basket): void

{

$products = $basket->getProducts();

foreach ($products as $product) {

$game = $product->getGame();

$currentCount = ($game->getBuyCount())?: 0;

$game->setBuyCount($currentCount + $product->getQuantity());

$game->setLastBuy(new \DateTime());

$this->em->persist($game);

$this->em->flush();

}

}

/**

* Обрабатывает запрос на странице завершения оплаты.

*

* @param int $id id заказа

* @param float $sum сумма заказа

* @param string $crc хэш

*

* @throws \Exception в случае несовпадения хэша

*

* @return string текст с результатом

*/

public function getDone (int $id, float $sum, string $crc): string

{

$crc = strtoupper($crc);

$myCrc = strtoupper(md5("$sum:$id:$this->pass"));

if ($myCrc!= $crc) {

throw new \Exception('Неверные данные оплаты. Хеш '.$myCrc.' и '.$crc.' не совпадают');

}

$basket = $this->em->getRepository(Basket::class)->find($id);

if (isset($basket)) {

if ($basket->getPaymentState()!= 2) {

$basket->setPaymentState(1);

$this->em->persist($basket);

$this->em->flush();

}

return 'Операция прошла успешно. После проведения оплаты ключи отправят вам на email. Спасибо за покупку!';

} else {

return 'Нет заказа с таким номером';

}

}

/**

* Получает текущую корзину пользователя.

*

* @return Basket

*/

public function getCurrentBasket (): Basket

{

$user = $this->container->get('security.token_storage')->getToken()->getUser();

$basket = $this->em->getRepository(Basket::class)->getCurrentBasketByUser($user);

return $basket;

}

/**

* Добавляет необходимое количество игр в корзину. Возвращает оставшееся количество ключей.

*

* @param Basket $basket

* @param Game $game

* @param int $quantity

*

* @return int

*/

public function addToBasket (Basket $basket, Game $game, int $quantity): int

{

$basket->addGame($game, $quantity);

$this->em->persist($basket);

$this->em->flush();

return $game->getKeysCount();

}

/**

* Изменят количество находящейся в корзине игр

*

* @param BasketProduct $basketProduct

* @param int $quantity

*/

public function changeBasketProductCount (BasketProduct $basketProduct, int $quantity): void

{

$basketProduct->getBasket()->changeGameCount($basketProduct->getGame(), $quantity);

$this->em->persist($basketProduct);

$this->em->flush();

}

/**

* Удаляет игру из корзины.

*

* @param BasketProduct $basketProduct

*/

public function deleteBasketProduct (BasketProduct $basketProduct): void

{

$basket = $basketProduct->getBasket();

$basket->removeProduct($basketProduct);

if ($basket->getProductCount() == 0) {

$this->em->remove($basket);

} else {

$this->em->persist($basket);

}

$this->em->flush();

}

/**

* Удаляет старые корзины.

*

* @param int $notPayDay количество дней для удаления корзин в статусе неоплачено

* @param int $payDay количество дней для удаления корзин в статусе ожидания оплаты

*

* @return int число удалённых корзин

*/

public function clearOldBasket (int $notPayDay, int $payDay): int

{

$oldBaskets = $this->em

->getRepository(Basket::class)

->getOldBaskets(

$notPayDay,

$payDay

);

$count = count($oldBaskets);

foreach ($oldBaskets as $basket) {

$this->em->remove($basket);

}

$this->em->flush();

return $count;

}

}

Листинг 26. Сервис для работы с корзиной и заказами

services:

difuks.dazzle.file.twig.extension:

class: Difuks\DazzleBundle\Extension\Twig\FileExtension

arguments: ['@service_container']

tags:

- { name: twig.extension }

 

difuks.dazzle.flush_handler:

class: Difuks\DazzleBundle\EventHandler\FlushHandler

arguments: ['@swiftmailer.mailer.cron', '@service_container']

tags:

- { name: doctrine.event_listener, event: onFlush }

 

difuks.dazzle.authentication_handler:

class: Difuks\DazzleBundle\EventHandler\AuthenticationHandler

arguments: ['@router', '@security.authorization_checker']

 

difuks.dazzle.order_service:

class: Difuks\DazzleBundle\Services\OrderService

arguments: ['@service_container', '@doctrine.orm.entity_manager', '@swiftmailer.mailer.moment']

 

difuks.dazzle.social_service:

class: Difuks\DazzleBundle\Services\SocialService

arguments: ['@service_container', '@doctrine.orm.entity_manager']

 

difuks.dazzle.form.registration:

class: Difuks\DazzleBundle\Form\RegistrationType

tags:

- { name: form.type, alias: difuks_dazzle_user_registration }

 

difuks.dazzle.form.profile:

class: Difuks\DazzleBundle\Form\ProfileType

tags:

- { name: form.type, alias: difuks_dazzle_user_profile }

Листинг 27. Файл настройки собственных сервисов


Приложение F. Класс консольной команды очистки заброшенных корзин

<?

declare (strict_types= 1);

namespace Difuks\DazzleBundle\Command;

use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;

use Symfony\Component\Console\Input\InputInterface;

use Symfony\Component\Console\Input\InputOption;

use Symfony\Component\Console\Output\OutputInterface;

class ClearOldBasketCommand extends ContainerAwareCommand

{

protected function configure ()

{

$this->addOption('not-pay-day', null, InputOption::VALUE_REQUIRED, 'Количество дней для удаление корзин со статусом 0', 1);

$this->addOption('pay-day', null, InputOption::VALUE_REQUIRED, 'Количество дней для удаление корзин со статусом 1', 3);

$this

->setName('difuks:dazzle:basket:clear-old')

->setDescription('Очищает старые корзины')

->setHelp('Очищает корзины со статусом 0 (старее одного дня) и 1 (старее трёх дней)');

}

protected function execute (InputInterface $input, OutputInterface $output)

{

$notPayDay = (int) $input->getOption('not-pay-day');

$payDay = (int) $input->getOption('pay-day');

$count = $this->getContainer()->get('difuks.dazzle.order_service')->clearOldBasket($notPayDay, $payDay);

$output->writeln((new \DateTime())->format('d.m.Y H:i:s')." Remove $count baskets");

}

}

Листинг 28. Класс консольной команды очистки заброшенных корзин


Приложение G. Функциональный тест виджета добавления в корзину и конфигурация PHPUnit

<?

namespace Difuks\DazzleBundle\Tests\Functional\Controller;

use Difuks\DazzleBundle\Tests\Functional\BaseControllerTest;

class WidgetControllerTest extends BaseControllerTest

{

public function testProductSliderWidget ()

{

$crawler = $this->client->request('GET', '/');

$gameButtons = $crawler->filter('button:contains("В корзину")');

$this->assertTrue($gameButtons->count() > 0, 'Ни одной доступной для покупки игры');

if ($gameButtons->count()) {

$this->checkUrl('/ajax/add-to-basket/'.$gameButtons->first()->attr('data-id'));

$answer = json_decode($this->client->getResponse()->getContent(), true);

$this->assertTrue(isset($answer['error']) && $answer['error'] == true, 'Добавление в корзину доступно неавторизованному пользователю');

$this->logIn();

$this->checkUrl('/ajax/add-to-basket/'.$gameButtons->first()->attr('data-id'));

$this->logout();

}

}

}

Листинг 29. Функциональный тест виджета добавления в корзину

<?xml version="1.0" encoding="UTF-8"?>

 

<!-- https://phpunit.de/manual/current/en/appendixes.configuration.html -->

<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.8/phpunit.xsd"

backupGlobals="false"

colors="true"

bootstrap="app/autoload.php"

>

<php>

<ini name="error_reporting" value="-1" />

<server name="KERNEL_DIR&qu


Поделиться с друзьями:

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

Организация стока поверхностных вод: Наибольшее количество влаги на земном шаре испаряется с поверхности морей и океанов (88‰)...

Механическое удерживание земляных масс: Механическое удерживание земляных масс на склоне обеспечивают контрфорсными сооружениями различных конструкций...

Особенности сооружения опор в сложных условиях: Сооружение ВЛ в районах с суровыми климатическими и тяжелыми геологическими условиями...



© cyberpedia.su 2017-2024 - Не является автором материалов. Исключительное право сохранено за автором текста.
Если вы не хотите, чтобы данный материал был у нас на сайте, перейдите по ссылке: Нарушение авторских прав. Мы поможем в написании вашей работы!

2.017 с.