<?php
/**
* EntryのHotel/Workspace/Activity/Mobilityの一覧取得と検索を行う
*/
namespace App\Service\Entry;
use App\Entity\Entry\Actibity;
use App\Entity\Entry\Entry;
use App\Entity\Entry\Hotel;
use App\Entity\Entry\ModelPlan;
use App\Entity\Entry\Workspace;
use App\Entity\Master\Area;
use App\Entity\Master\Equipment;
use App\Entity\Master\Service;
use App\Entity\Master\Tag;
use App\Entity\Master\Target;
use App\Repository\Entry\EntryRepository;
use App\Utils\GetQueryUtilTrait;
use App\Utils\PaginationUtil;
use App\Utils\TargetChoiceUtil;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\QueryBuilder;
use Symfony\Component\HttpFoundation\Request;
class FrontService
{
use GetQueryUtilTrait;
private EntityManagerInterface $em;
private EntryRepository $entryRepository;
private ?int $type = null;
private string $locale;
public function __construct(
EntityManagerInterface $em,
EntryRepository $entryRepository
) {
$this->em = $em;
$this->entryRepository = $entryRepository;
}
/**
* 実行するエントリーのカテゴリを指定
* モデルプランは指定できない
* 2度は指定できない
*
* @param int $type
* @return $this
* @throws \Exception
*/
public function setType(int $type): self
{
if(null !== $this->type) {
throw new \Exception('setType() method call only once');
}
switch(true) {
case TargetChoiceUtil::inModelPlan($type):
case TargetChoiceUtil::inHotel($type):
case TargetChoiceUtil::inWorkspace($type):
$this->type = $type;
return $this;
case TargetChoiceUtil::inActivity($type):
case TargetChoiceUtil::inMobility($type):
$this->type = TargetChoiceUtil::ACTIVITY;
return $this;
}
throw new \InvalidArgumentException("type invalid");
}
/**
* 現在の言語を指定
* @param string $locale
* @return $this
*/
public function setLocale(string $locale): self
{
if(!in_array($locale, ["ja", "en", "cn", "tw"], true)) {
$locale = "ja";
}
$this->locale = $locale;
return $this;
}
private function createResumeSessionName(): string
{
return sprintf("entry_list_resume_%s", $this->type);
}
/**
* 選択項目データを今回のカテゴリーに基づいて返す
*
* @return array|array[]
*/
public function getFilter(): array
{
switch($this->type) {
case TargetChoiceUtil::MODELPLAN:
return $this->getModelPlanFilter();
case TargetChoiceUtil::HOTEL:
return $this->getHotelFilter();
case TargetChoiceUtil::WORKSPACE:
return $this->getWorkSpaceFilter();
case TargetChoiceUtil::ACTIVITY:
case TargetChoiceUtil::MOBILITY:
return $this->getActivityFilter();
}
throw new \InvalidArgumentException('type unset. call setType() before.');
}
protected function getModelPlanFilter(): array
{
return [
"Area" => $this->getAreaChoice(),
"Equipment" => $this->getChoice(Equipment::class),
"Service" => $this->getChoice(Service::class),
"Target" => $this->getChoice(Target::class),
"Tag" => $this->getChoice(Tag::class, function(Tag $value) {
return $value->getCategoryLocaleName($this->locale);
}),
];
}
protected function getHotelFilter(): array
{
return [
"Area" => $this->getAreaChoice(),
"Equipment" => $this->getChoice(Equipment::class),
"Service" => $this->getChoice(Service::class),
"Target" => $this->getChoice(Target::class)
];
}
protected function getWorkSpaceFilter(): array
{
return [
"Area" => $this->getAreaChoice(),
"Equipment" => $this->getChoice(Equipment::class),
"Service" => $this->getChoice(Service::class)
];
}
protected function getActivityFilter(): array
{
return [
"Area" => $this->getAreaChoice(),
"Service" => $this->getChoice(Service::class),
"Tag" => $this->getChoice(Tag::class, function(Tag $value) {
return $value->getCategoryLocaleName($this->locale);
}),
"Target" => $this->getChoice(Target::class),
"Category" => [
"choice" => null,
"choices" => [
"_all" => [
TargetChoiceUtil::ACTIVITY => [
"label" => TargetChoiceUtil::localeName(TargetChoiceUtil::ACTIVITY, $this->locale),
"choice" => false
],
TargetChoiceUtil::MOBILITY => [
"label" => TargetChoiceUtil::localeName(TargetChoiceUtil::MOBILITY, $this->locale),
"choice" => false
]
]
]
]
];
}
private function getAreaChoice(): array
{
$area = new Area();
$names = [
"_all" => []
];
foreach($area->getLocaleAreaMapArray($this->locale) as $id => $name) {
$names["_all"][$id] = [
"label" => $name,
"choice" => false
];
}
return [
"choice" => null,
"choices" => $names
];
}
private function getChoice(string $className, ?callable $groupingFunction = null): array
{
$repository = $this->em->getRepository($className);
$qb = $repository->getKeyValueQueryForCategory($this->type);
return $this->createChoiceArray($qb->getQuery()->getResult(), $groupingFunction);
}
private function createChoiceArray(array $result, ?callable $groupingFunction = null): array
{
$choices = [];
foreach($result as $value) {
if(null === $groupingFunction) {
$group = "_all";
} else {
$group = $groupingFunction($value);
}
if(!isset($choices[$group])) {
$choices[$group] = [];
}
$choices[$group][$value->getId()] = [
"label" => $value->getLocaleName($this->locale),
"entity" => $value,
"choice" => false
];
}
return [
"choice" => null,
"choices" => $choices
];
}
/**
* Request queryから選択データを取得する
* @param Request $request
* @return array
*/
public function getCriteriaFromRequest(Request $request): array
{
$criteria = [];
if($area = $this->getSingleQuery($request, "area")) {
$criteria['area'] = $area;
}
if($target = $this->getMultiQuery($request, "target")) {
$criteria['target'] = $target;
}
if($equipment = $this->getMultiQuery($request, "equipment")) {
$criteria['equipment'] = $equipment;
}
if($service = $this->getMultiQuery($request, 'service')) {
$criteria['service'] = $service;
}
if($tag = $this->getMultiQuery($request, 'tag')) {
$criteria['tag'] = $tag;
}
if($mode = $this->getSingleQuery($request, "category")) {
$criteria["category"] = $mode;
}
if($page = $this->getSingleQuery($request, "page")) {
$criteria['page'] = $page;
} else {
$criteria['page'] = 1;
}
$request->getSession()->set($this->createResumeSessionName(), $criteria);
return $criteria;
}
public function getCriteriaFromSession(Request $request): array
{
$criteria = $request->getSession()->get($this->createResumeSessionName());
if(!$criteria) return [];
return $criteria;
}
/**
* getFilter()で取得した選択肢データにリクエストなどで受け取った選択情報をアサインしていく
*
* @param array $filter
* @param array $criteria
* @return array
*/
public function handleRequest(array $filter, array $criteria): array
{
$filter = $this->handleAreaRequest($filter, $criteria);
switch($this->type) {
case TargetChoiceUtil::MODELPLAN:
return $this->handleModelPlanRequest($filter, $criteria);
case TargetChoiceUtil::HOTEL:
return $this->handleHotelRequest($filter, $criteria);
case TargetChoiceUtil::WORKSPACE:
return $this->handleWorkSpaceRequest($filter, $criteria);
case TargetChoiceUtil::ACTIVITY:
case TargetChoiceUtil::MOBILITY:
return $this->handleActivityRequest($filter, $criteria);
}
throw new \InvalidArgumentException('type unset. call setType() before.');
}
private function handleAreaRequest(array $filter, array $criteria): array
{
if(isset($criteria['area'])) {
$filter['Area'] = $this->handleRequestSingle($filter['Area'], $criteria['area']);
}
return $filter;
}
private function handleModelPlanRequest(array $filter, array $criteria): array
{
if(isset($criteria['target']) && is_array($criteria['target'])) {
$filter['Target'] = $this->handleRequestMultiple($filter['Target'], $criteria['target']);
}
if(isset($criteria['equipment']) && is_array($criteria['equipment'])) {
$filter['Equipment'] = $this->handleRequestMultiple($filter['Equipment'], $criteria['equipment']);
}
if(isset($criteria['service']) && is_array($criteria['service'])) {
$filter['Service'] = $this->handleRequestMultiple($filter['Service'], $criteria['service']);
}
if(isset($criteria['tag']) && is_array($criteria['tag'])) {
$filter['Tag'] = $this->handleRequestMultiple($filter['Tag'], $criteria['tag']);
}
return $filter;
}
private function handleHotelRequest(array $filter, array $criteria): array
{
if(isset($criteria['target']) && is_array($criteria['target'])) {
$filter['Target'] = $this->handleRequestMultiple($filter['Target'], $criteria['target']);
}
if(isset($criteria['equipment']) && is_array($criteria['equipment'])) {
$filter['Equipment'] = $this->handleRequestMultiple($filter['Equipment'], $criteria['equipment']);
}
if(isset($criteria['service']) && is_array($criteria['service'])) {
$filter['Service'] = $this->handleRequestMultiple($filter['Service'], $criteria['service']);
}
return $filter;
}
private function handleWorkSpaceRequest(array $filter, array $criteria): array
{
if(isset($criteria['equipment']) && is_array($criteria['equipment'])) {
$filter['Equipment'] = $this->handleRequestMultiple($filter['Equipment'], $criteria['equipment']);
}
if(isset($criteria['service']) && is_array($criteria['service'])) {
$filter['Service'] = $this->handleRequestMultiple($filter['Service'], $criteria['service']);
}
return $filter;
}
private function handleActivityRequest(array $filter, array $criteria): array
{
if(isset($criteria['service']) && is_array($criteria['service'])) {
$filter['Service'] = $this->handleRequestMultiple($filter['Service'], $criteria['service']);
}
if(isset($criteria['target']) && is_array($criteria['target'])) {
$filter['Target'] = $this->handleRequestMultiple($filter['Target'], $criteria['target']);
}
if(isset($criteria['tag']) && is_array($criteria['tag'])) {
$filter['Tag'] = $this->handleRequestMultiple($filter['Tag'], $criteria['tag']);
}
if(isset($criteria['category'])) {
$filter['Category'] = $this->handleRequestSingle($filter['Category'], $criteria['category']);
}
return $filter;
}
private function handleRequestSingle(array $filterSingle, $value): array
{
$value = intval($value);
if(!$value) return $filterSingle;
foreach($filterSingle['choices'] as $groupName => $choices) {
if(isset($filterSingle['choices'][$groupName][$value])) {
$filterSingle['choices'][$groupName][$value]['choice'] = true;
$filterSingle['choice'] = $value;
}
}
return $filterSingle;
}
private function handleRequestMultiple(array $filterItem, array $choiceIds): array
{
if(!count($choiceIds)) return $filterItem;
foreach($choiceIds as $choiceId) {
foreach($filterItem['choices'] as $groupName => $choices) {
if(isset($choices[$choiceId])) {
$filterItem['choices'][$groupName][$choiceId]['choice'] = true;
if(null === $filterItem['choice']) {
$filterItem['choice'] = [];
}
$filterItem['choice'][] = $choiceId;
}
}
}
return $filterItem;
}
/**
* 現在の区分に基づくQueryBuilderを構築
* @param string $alias
* @return QueryBuilder
*/
public function getQueryBuilderForList(string $alias = "a"): QueryBuilder
{
$qb = $this->entryRepository->getListQuery([]);
if($this->type !== TargetChoiceUtil::ACTIVITY) {
$qb
->andWhere($qb->expr()->eq("e.category", ":category"))
->setParameter("category", $this->type);
}
$this->entryRepository->setDefaultOrder($qb);
$this->entryRepository->addPublishWhere($qb);
switch($this->type) {
case TargetChoiceUtil::MODELPLAN:
$qb
->leftJoin(ModelPlan::class, $alias, "with", "e.id = ".$alias.".Entry");
break;
case TargetChoiceUtil::HOTEL:
$qb
->leftJoin(Hotel::class, $alias, "with", "e.id = ".$alias.".Entry");
break;
case TargetChoiceUtil::WORKSPACE:
$qb
->innerJoin(Workspace::class, $alias, "with", "e.id = ".$alias.".Entry");
break;
case TargetChoiceUtil::ACTIVITY:
case TargetChoiceUtil::MOBILITY:
$qb
->leftJoin(Actibity::class, $alias, "with", "e.id = ".$alias. ".Entry");
break;
}
return $qb;
}
/**
* getFilter()で取得してhandleRequest()で設定した選択肢データに基づいて
* DQLの検索条件を設定していく
*
* @param QueryBuilder $qb
* @param array $filter
* @param string $alias
* @param string $entryAlias
* @return QueryBuilder
*/
public function addDqlWhere(QueryBuilder $qb, array $filter, string $alias = "a", string $entryAlias = "e"): QueryBuilder
{
if(isset($filter['Area'])) {
if($filter['Area']['choice']) {
$qb
->andWhere($qb->expr()->eq("area.area_group", ":Area"))
->setParameter("Area", $filter['Area']['choice'])
;
}
}
switch(true) {
case TargetChoiceUtil::inModelPlan($this->type):
case TargetChoiceUtil::inHotel($this->type):
case TargetChoiceUtil::inWorkspace($this->type):
$qb
->andWhere($qb->expr()->eq($entryAlias.".category", ":Category"))
->setParameter("Category", $this->type)
;
break;
case TargetChoiceUtil::inActivity($this->type):
if(isset($filter['Category']) && $filter['Category']['choice']) {
$qb
->andWhere($qb->expr()->eq($entryAlias.".category", ":Category"))
->setParameter("Category", $filter['Category']['choice'])
;
} else {
$qb
->andWhere($qb->expr()->in($entryAlias.".category", ":Category"))
->setParameter("Category", [TargetChoiceUtil::ACTIVITY, TargetChoiceUtil::MOBILITY])
;
}
break;
}
$this
->addDqlWhereMultiple($qb, $filter, "Target", $alias.".Target")
->addDqlWhereMultiple($qb, $filter, "Equipment", $alias.".Equipment")
->addDqlWhereMultiple($qb, $filter, "Service", $alias. ".Service")
->addDqlWhereMultiple($qb, $filter, "Tag", $alias. ".Tag")
;
return $qb;
}
private function addDqlWhereSingle(QueryBuilder $qb, array $filter, string $name, string $columnName): self
{
if(isset($filter[$name]) && $filter[$name]['choice']) {
$qb
->andWhere(":". $name. " MEMBER OF ". $columnName)
->setParameter($name, $filter[$name]['choice']);
}
return $this;
}
private function addDqlWhereMultiple(QueryBuilder $qb, array $filter, string $name, string $columnName): self
{
if(isset($filter[$name])) {
$choice = $filter[$name]['choice'];
if(is_array($choice) && count($choice)) {
$qb
->andWhere(":".$name. " MEMBER OF ". $columnName)
->setParameter($name, $choice);
}
}
return $this;
}
/**
* ページネーションを適用させる
* @param QueryBuilder $queryBuilder
* @param array $criteria
* @return PaginationUtil
*/
public function handleList(QueryBuilder $queryBuilder, array $criteria): PaginationUtil
{
if(!isset($criteria['page'])) {
$criteria['page'] = 1;
}
if(!isset($criteria['limit'])) {
$criteria['limit'] = 10;
}
return new PaginationUtil($queryBuilder, $criteria['page'], $criteria['limit']);
}
/**
* 選択肢設定からURLクエリー用の配列を作る
* @param array $filter
* @return array
*/
public function createQueryFromFilter(array $filter): array
{
$queries = [];
if(isset($filter['Area']) && $filter['Area']['choice']) {
$queries['area'] = $filter['Area']['choice'];
}
if($target = $this->createMultiQuery($filter, "Target")) {
$queries['target'] = $target;
}
if($equipment = $this->createMultiQuery($filter, "Equipment")) {
$queries['equipment'] = $equipment;
}
if($service = $this->createMultiQuery($filter, "Service")) {
$queries['service'] = $service;
}
if($tag = $this->createMultiQuery($filter, "Tag")) {
$queries['tag'] = $tag;
}
if(isset($filter['Category']) && $filter['Category']['choice']) {
$queries['category'] = $filter['Category']['choice'];
}
return $queries;
}
private function createMultiQuery(array $filter, string $name): ?array
{
if(isset($filter[$name]) && is_array($filter[$name]['choice']) && count($filter[$name]['choice'])) {
return $filter[$name]['choice'];
}
return null;
}
/**
* 記事1件取得
* @param int $id
* @return Entry|null
* @throws \Doctrine\ORM\NonUniqueResultException
*/
public function getOneEntry(int $id): ?Entry
{
$qb = $this->entryRepository->createQueryBuilder('e');
$this->entryRepository->addPublishWhere($qb);
$this->entryRepository->joinOwner($qb);
$qb
->andWhere($qb->expr()->eq('e.id', ':id'))
->setParameter('id', $id)
;
$this->addCategoryQuery($qb);
return $qb->getQuery()->getOneOrNullResult();
}
/**
* 同じカテゴリ・同じエリアの他の施設を返す
* @param Entry $entry
* @param int $maxResult
* @return array
*/
public function getRelatedEntry(Entry $entry, int $maxResult = 3): array
{
$qb = $this->entryRepository->createQueryBuilder('e');
$this->entryRepository->addPublishWhere($qb);
$this->entryRepository->joinOwner($qb);
$this->addCategoryQuery($qb)
->leftJoin(Area::class, "area", "with", "e.Area = area.id")
->andWhere($qb->expr()->eq('area.area_group', ':area'))
->setParameter("area", $entry->getArea()->getAreaGroup())
->andWhere($qb->expr()->neq('e.id', ':id'))
->setParameter('id', $entry->getId())
->setMaxResults($maxResult)
->orderBy('rand()')
;
return $qb->getQuery()->getResult();
}
/**
* おすすめ一覧
* @param int $limit
* @return array
*/
public function getRecommends(int $limit = 9): array
{
$qb = $this->entryRepository->createQueryBuilder('e');
$qb
->leftJoin(ModelPlan::class, "m", "with", "e.id = m.Entry")
->andWhere("e.category = 1")
->setMaxResults($limit)
->orderBy('m.is_recommend', 'DESC')
->addOrderBy('rand()')
->addOrderBy('e.id', 'DESC')
;
$this->entryRepository->addPublishWhere($qb);
$this->entryRepository->joinOwner($qb);
return $qb->getQuery()->getResult();
}
/**
* 現在指定のカテゴリをQBに追加
* @param QueryBuilder $qb
* @return QueryBuilder
*/
private function addCategoryQuery(QueryBuilder $qb): QueryBuilder {
switch(true) {
case TargetChoiceUtil::inModelPlan($this->type):
case TargetChoiceUtil::inHotel($this->type):
case TargetChoiceUtil::inWorkspace($this->type):
$qb
->andWhere($qb->expr()->eq('e.category', ':category'))
->setParameter('category', $this->type)
;
break;
case TargetChoiceUtil::inActivity($this->type):
$qb
->andWhere($qb->expr()->in('e.category', ':category'))
->setParameter('category', [TargetChoiceUtil::ACTIVITY, TargetChoiceUtil::MOBILITY])
;
break;
}
return $qb;
}
}