Total Complexity | 42 |
Total Lines | 297 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like SeoActionController often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use SeoActionController, and based on these observations, apply Extract Interface, too.
1 | <?php declare(strict_types=1); |
||
34 | #[Route(defaults: ['_routeScope' => ['api']])] |
||
35 | #[Package('buyers-experience')] |
||
36 | class SeoActionController extends AbstractController |
||
37 | { |
||
38 | /** |
||
39 | * @internal |
||
40 | */ |
||
41 | public function __construct( |
||
42 | private readonly SeoUrlGenerator $seoUrlGenerator, |
||
43 | private readonly SeoUrlPersister $seoUrlPersister, |
||
44 | private readonly DefinitionInstanceRegistry $definitionRegistry, |
||
45 | private readonly SeoUrlRouteRegistry $seoUrlRouteRegistry, |
||
46 | private readonly SeoUrlDataValidationFactoryInterface $seoUrlValidator, |
||
47 | private readonly DataValidator $validator, |
||
48 | private readonly EntityRepository $salesChannelRepository, |
||
49 | private readonly RequestCriteriaBuilder $requestCriteriaBuilder, |
||
50 | private readonly DefinitionInstanceRegistry $definitionInstanceRegistry |
||
51 | ) { |
||
52 | } |
||
53 | |||
54 | #[Route(path: '/api/_action/seo-url-template/validate', name: 'api.seo-url-template.validate', methods: ['POST'])] |
||
66 | } |
||
67 | |||
68 | #[Route(path: '/api/_action/seo-url-template/preview', name: 'api.seo-url-template.preview', methods: ['POST'])] |
||
69 | public function preview(Request $request, Context $context): Response |
||
70 | { |
||
71 | $this->validateSeoUrlTemplate($request); |
||
72 | $seoUrlTemplate = $request->request->all(); |
||
73 | |||
74 | $previewCriteria = new Criteria(); |
||
75 | if (\array_key_exists('criteria', $seoUrlTemplate) && \is_string($seoUrlTemplate['entityName']) && \is_array($seoUrlTemplate['criteria'])) { |
||
76 | $definition = $this->definitionInstanceRegistry->getByEntityName($seoUrlTemplate['entityName']); |
||
77 | |||
78 | $previewCriteria = $this->requestCriteriaBuilder->handleRequest( |
||
79 | Request::create('', 'POST', $seoUrlTemplate['criteria']), |
||
80 | $previewCriteria, |
||
81 | $definition, |
||
82 | $context |
||
83 | ); |
||
84 | unset($seoUrlTemplate['criteria']); |
||
85 | } |
||
86 | |||
87 | try { |
||
88 | $preview = $this->getPreview($seoUrlTemplate, $context, $previewCriteria); |
||
89 | } catch (NoEntitiesForPreviewException) { |
||
90 | return new Response('', Response::HTTP_NO_CONTENT); |
||
91 | } |
||
92 | |||
93 | return new JsonResponse($preview); |
||
94 | } |
||
95 | |||
96 | #[Route(path: '/api/_action/seo-url-template/context', name: 'api.seo-url-template.context', methods: ['POST'])] |
||
97 | public function getSeoUrlContext(RequestDataBag $data, Context $context): JsonResponse |
||
98 | { |
||
99 | $routeName = $data->get('routeName'); |
||
100 | $fk = $data->get('foreignKey'); |
||
101 | $seoUrlRoute = $this->seoUrlRouteRegistry->findByRouteName($routeName); |
||
102 | if (!$seoUrlRoute) { |
||
103 | throw SeoException::seoUrlRouteNotFound($routeName); |
||
104 | } |
||
105 | |||
106 | $config = $seoUrlRoute->getConfig(); |
||
107 | $repository = $this->getRepository($config); |
||
108 | |||
109 | $criteria = new Criteria(); |
||
110 | if (!empty($fk)) { |
||
111 | $criteria = new Criteria([$fk]); |
||
112 | } |
||
113 | $criteria->setLimit(1); |
||
114 | |||
115 | $entity = $repository |
||
116 | ->search($criteria, $context) |
||
117 | ->first(); |
||
118 | |||
119 | if (!$entity) { |
||
120 | return new JsonResponse(null, Response::HTTP_NOT_FOUND); |
||
121 | } |
||
122 | |||
123 | $mapping = $seoUrlRoute->getMapping($entity, null); |
||
124 | |||
125 | return new JsonResponse($mapping->getSeoPathInfoContext()); |
||
126 | } |
||
127 | |||
128 | #[Route(path: '/api/_action/seo-url/canonical', name: 'api.seo-url.canonical', methods: ['PATCH'])] |
||
129 | public function updateCanonicalUrl(RequestDataBag $seoUrl, Context $context): Response |
||
130 | { |
||
131 | if (!$seoUrl->has('routeName')) { |
||
132 | throw SeoException::routeNameParameterIsMissing(); |
||
133 | } |
||
134 | |||
135 | $seoUrlRoute = $this->seoUrlRouteRegistry->findByRouteName($seoUrl->get('routeName') ?? ''); |
||
136 | if (!$seoUrlRoute) { |
||
137 | throw SeoException::seoUrlRouteNotFound($seoUrl->get('routeName')); |
||
138 | } |
||
139 | |||
140 | $validation = $this->seoUrlValidator->buildValidation($context, $seoUrlRoute->getConfig()); |
||
141 | |||
142 | $seoUrlData = $seoUrl->all(); |
||
143 | $this->validator->validate($seoUrlData, $validation); |
||
144 | $seoUrlData['isModified'] ??= true; |
||
145 | |||
146 | $salesChannelId = $seoUrlData['salesChannelId'] ?? null; |
||
147 | |||
148 | if ($salesChannelId === null) { |
||
149 | throw SeoException::salesChannelIdParameterIsMissing(); |
||
150 | } |
||
151 | |||
152 | /** @var SalesChannelEntity|null $salesChannel */ |
||
153 | $salesChannel = $this->salesChannelRepository->search(new Criteria([$salesChannelId]), $context)->first(); |
||
154 | |||
155 | if ($salesChannel === null) { |
||
156 | throw SeoException::salesChannelNotFound($salesChannelId); |
||
157 | } |
||
158 | |||
159 | if ($salesChannel->getTypeId() === Defaults::SALES_CHANNEL_TYPE_API) { |
||
160 | if (Feature::isActive('v6.6.0.0')) { |
||
161 | return new Response('', Response::HTTP_NO_CONTENT); |
||
162 | } |
||
163 | } |
||
164 | |||
165 | $this->seoUrlPersister->updateSeoUrls( |
||
166 | $context, |
||
167 | $seoUrlData['routeName'], |
||
168 | [$seoUrlData['foreignKey']], |
||
169 | [$seoUrlData], |
||
170 | $salesChannel |
||
171 | ); |
||
172 | |||
173 | return new Response('', Response::HTTP_NO_CONTENT); |
||
174 | } |
||
175 | |||
176 | #[Route(path: '/api/_action/seo-url/create-custom-url', name: 'api.seo-url.create', methods: ['POST'])] |
||
177 | public function createCustomSeoUrls(RequestDataBag $dataBag, Context $context): Response |
||
178 | { |
||
179 | /** @var ParameterBag $dataBag */ |
||
180 | $dataBag = $dataBag->get('urls'); |
||
181 | $urls = $dataBag->all(); |
||
182 | |||
183 | /** @var SeoUrlValidationFactory $validatorBuilder */ |
||
184 | $validatorBuilder = $this->seoUrlValidator; |
||
185 | |||
186 | $validation = $validatorBuilder->buildValidation($context, null); |
||
187 | $salesChannels = new SalesChannelCollection(); |
||
188 | |||
189 | $salesChannelIds = array_column($urls, 'salesChannelId'); |
||
190 | |||
191 | if (!empty($salesChannelIds)) { |
||
192 | $salesChannels = $this->salesChannelRepository->search(new Criteria($salesChannelIds), $context)->getEntities(); |
||
193 | } |
||
194 | |||
195 | $writeData = []; |
||
196 | |||
197 | foreach ($urls as $seoUrlData) { |
||
198 | $id = $seoUrlData['salesChannelId'] ?? null; |
||
199 | |||
200 | $this->validator->validate($seoUrlData, $validation); |
||
201 | $seoUrlData['isModified'] ??= true; |
||
202 | |||
203 | $writeData[$id][] = $seoUrlData; |
||
204 | } |
||
205 | |||
206 | foreach ($writeData as $salesChannelId => $writeRows) { |
||
207 | if ($salesChannelId === '') { |
||
208 | throw SeoException::salesChannelIdParameterIsMissing(); |
||
209 | } |
||
210 | |||
211 | /** @var SalesChannelEntity $salesChannelEntity */ |
||
212 | $salesChannelEntity = $salesChannels->get($salesChannelId); |
||
213 | |||
214 | if ($salesChannelEntity === null) { |
||
215 | throw SeoException::salesChannelNotFound((string) $salesChannelId); |
||
216 | } |
||
217 | |||
218 | if ($salesChannelEntity->getTypeId() === Defaults::SALES_CHANNEL_TYPE_API) { |
||
219 | if (Feature::isActive('v6.6.0.0')) { |
||
220 | continue; |
||
221 | } |
||
222 | } |
||
223 | |||
224 | $this->seoUrlPersister->updateSeoUrls( |
||
225 | $context, |
||
226 | $writeRows[0]['routeName'], |
||
227 | array_column($writeRows, 'foreignKey'), |
||
228 | $writeRows, |
||
229 | $salesChannelEntity |
||
230 | ); |
||
231 | } |
||
232 | |||
233 | return new Response('', Response::HTTP_NO_CONTENT); |
||
234 | } |
||
235 | |||
236 | #[Route(path: '/api/_action/seo-url-template/default/{routeName}', name: 'api.seo-url-template.default', methods: ['GET'])] |
||
237 | public function getDefaultSeoTemplate(string $routeName, Context $context): JsonResponse |
||
238 | { |
||
239 | $seoUrlRoute = $this->seoUrlRouteRegistry->findByRouteName($routeName); |
||
240 | |||
241 | if (!$seoUrlRoute) { |
||
242 | throw SeoException::seoUrlRouteNotFound($routeName); |
||
243 | } |
||
244 | |||
245 | return new JsonResponse(['defaultTemplate' => $seoUrlRoute->getConfig()->getTemplate()]); |
||
246 | } |
||
247 | |||
248 | private function validateSeoUrlTemplate(Request $request): void |
||
249 | { |
||
250 | if (!$request->request->has('template')) { |
||
251 | throw SeoException::templateParameterIsMissing(); |
||
252 | } |
||
253 | |||
254 | if (!$request->request->has('salesChannelId')) { |
||
255 | throw SeoException::salesChannelIdParameterIsMissing(); |
||
256 | } |
||
257 | |||
258 | if (!$request->request->has('routeName')) { |
||
259 | throw SeoException::routeNameParameterIsMissing(); |
||
260 | } |
||
261 | |||
262 | if (!$request->request->has('entityName')) { |
||
263 | throw SeoException::entityNameParameterIsMissing(); |
||
264 | } |
||
265 | } |
||
266 | |||
267 | /** |
||
268 | * @param array<string, mixed> $seoUrlTemplate |
||
269 | * |
||
270 | * @return array<SeoUrlEntity> |
||
271 | */ |
||
272 | private function getPreview(array $seoUrlTemplate, Context $context, ?Criteria $previewCriteria = null): array |
||
326 | } |
||
327 | |||
328 | private function getRepository(SeoUrlRouteConfig $config): EntityRepository |
||
331 | } |
||
332 | } |
||
333 |