WARNING: THIS SITE IS A MIRROR OF GITHUB.COM / IT CANNOT LOGIN OR REGISTER ACCOUNTS / THE CONTENTS ARE PROVIDED AS-IS / THIS SITE ASSUMES NO RESPONSIBILITY FOR ANY DISPLAYED CONTENT OR LINKS / IF YOU FOUND SOMETHING MAY NOT GOOD FOR EVERYONE, CONTACT ADMIN AT ilovescratch@foxmail.com
Skip to content

Commit 581f085

Browse files
Add Base implementation of website content resolving (#271)
* Add own implementation of Controller * Add PropertyResolver and MediaResourceLoader * Add review suggestions * Add unit tests * Add sulu/sulu fork * Keep required sulu version * Update Content/Application/ContentResolver/Value/ContentView.php Co-authored-by: Alexander Schranz <[email protected]> * Update Content/Application/ResourceLoader/ResourceLoaderInterface.php Co-authored-by: Alexander Schranz <[email protected]> * Remove sulu fork * Fix tests for php 8.1 * Fix tests for php 8.0 * Enhance content resolver test * Refactor contentResolver and add LinkPropertyProvider * Add tests * Add TeaserSelectionPropertyResolver * Move interfaces and adjust namespaces * Define COMPOSER_TOKEN to fix issues with using forks * Use automation bundle 3.0 * Update Sulu required version * Define composer requirements for Sulu 3.0 * Fix failings tests * Add debug line * Skip resolver tests for now * Trigger CI * Ignore skipped tests --------- Co-authored-by: Alexander Schranz <[email protected]>
1 parent a23a5b0 commit 581f085

File tree

46 files changed

+3886
-38
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+3886
-38
lines changed

.github/workflows/test-application.yaml

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ jobs:
1313
runs-on: ubuntu-latest
1414

1515
env:
16+
COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }}
1617
APP_ENV: test
1718
DATABASE_URL: mysql://root:[email protected]:3306/su_content_test?serverVersion=8.0.29
1819
DATABASE_CHARSET: utf8mb4
@@ -22,21 +23,9 @@ jobs:
2223
fail-fast: false
2324
matrix:
2425
include:
25-
- php-version: '8.0'
26-
coverage: false
27-
dependency-versions: 'lowest'
28-
env:
29-
SYMFONY_DEPRECATIONS_HELPER: disabled
30-
31-
- php-version: '8.1'
32-
coverage: false
33-
dependency-versions: 'highest'
34-
env:
35-
SYMFONY_DEPRECATIONS_HELPER: weak
36-
3726
- php-version: '8.2'
3827
coverage: false
39-
dependency-versions: 'highest'
28+
dependency-versions: 'lowest'
4029
env:
4130
SYMFONY_DEPRECATIONS_HELPER: weak
4231

@@ -112,6 +101,9 @@ jobs:
112101
name: "PHP Lint"
113102
runs-on: ubuntu-latest
114103

104+
env:
105+
COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }}
106+
115107
steps:
116108
- name: Checkout project
117109
uses: actions/checkout@v2

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,5 @@ Tests/reports/
3535
.idea/*
3636
*.iml
3737
*~
38+
39+
.php-version
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of Sulu.
7+
*
8+
* (c) Sulu GmbH
9+
*
10+
* This source file is subject to the MIT license that is bundled
11+
* with this source code in the file LICENSE.
12+
*/
13+
14+
namespace Sulu\Bundle\ContentBundle\Content\Application\ContentResolver;
15+
16+
use Sulu\Bundle\ContentBundle\Content\Application\ContentResolver\Resolver\ResolverInterface;
17+
use Sulu\Bundle\ContentBundle\Content\Application\ContentResolver\Resolver\TemplateResolver;
18+
use Sulu\Bundle\ContentBundle\Content\Application\ContentResolver\Value\ContentView;
19+
use Sulu\Bundle\ContentBundle\Content\Application\ContentResolver\Value\ResolvableResource;
20+
use Sulu\Bundle\ContentBundle\Content\Application\ResourceLoader\ResourceLoaderProvider;
21+
use Sulu\Bundle\ContentBundle\Content\Domain\Model\DimensionContentInterface;
22+
23+
class ContentResolver implements ContentResolverInterface
24+
{
25+
/**
26+
* @param iterable<ResolverInterface> $contentResolvers
27+
*/
28+
public function __construct(
29+
private iterable $contentResolvers,
30+
private ResourceLoaderProvider $resourceLoaderProvider
31+
) {
32+
}
33+
34+
/**
35+
* @return array{
36+
* resource: object,
37+
* content: mixed,
38+
* view: mixed[]
39+
* }
40+
*/
41+
public function resolve(DimensionContentInterface $dimensionContent): array
42+
{
43+
$contentViews = [];
44+
foreach ($this->contentResolvers as $resolverKey => $contentResolver) {
45+
$contentView = $contentResolver->resolve($dimensionContent);
46+
47+
if ($contentResolver instanceof TemplateResolver) {
48+
/** @var mixed[] $content */
49+
$content = $contentView->getContent();
50+
$contentViews = \array_merge($contentViews, $content);
51+
continue;
52+
}
53+
54+
$contentViews[$resolverKey] = $contentView;
55+
}
56+
$resolvedContent = $this->resolveContentViews($contentViews);
57+
$resolvedResources = $this->loadAndResolveResources($resolvedContent['resolvableResources'], $dimensionContent->getLocale());
58+
$content = $this->replaceResolvableResourcesWithResolvedValues($resolvedContent['content'], $resolvedResources);
59+
60+
return [
61+
'resource' => $dimensionContent->getResource(),
62+
'content' => $content,
63+
'view' => $resolvedContent['view'],
64+
];
65+
}
66+
67+
/**
68+
* @param ContentView[] $contentViews
69+
*
70+
* @return array{
71+
* content: mixed[],
72+
* view: mixed[],
73+
* resolvableResources: array<string, array<ResolvableResource>>
74+
* }
75+
*/
76+
private function resolveContentViews(array $contentViews): array
77+
{
78+
$content = [];
79+
$view = [];
80+
$resolvableResources = [];
81+
82+
foreach ($contentViews as $name => $contentView) {
83+
$result = $this->resolveContentView($contentView, (string) $name);
84+
$content = \array_merge($content, $result['content']);
85+
$view = \array_merge($view, $result['view']);
86+
$resolvableResources = \array_merge_recursive($resolvableResources, $result['resolvableResources']);
87+
}
88+
89+
return [
90+
'content' => $content,
91+
'view' => $view,
92+
'resolvableResources' => $resolvableResources,
93+
];
94+
}
95+
96+
/**
97+
* @return array{
98+
* content: mixed[],
99+
* view: mixed[],
100+
* resolvableResources: array<string, array<ResolvableResource>>
101+
* }
102+
*/
103+
private function resolveContentView(ContentView $contentView, string $name): array
104+
{
105+
$content = $contentView->getContent();
106+
$view = $contentView->getView();
107+
108+
$result = [
109+
'content' => [],
110+
'view' => [],
111+
'resolvableResources' => [],
112+
];
113+
if (\is_array($content)) {
114+
if (\count(\array_filter($content, fn ($entry) => $entry instanceof ContentView)) === \count($content)) {
115+
// resolve array of content views
116+
$resolvedContentViews = $this->resolveContentViews($content);
117+
$result['content'][$name] = $resolvedContentViews['content'];
118+
$result['view'][$name] = $resolvedContentViews['view'];
119+
$result['resolvableResources'] = \array_merge_recursive($result['resolvableResources'], $resolvedContentViews['resolvableResources']);
120+
121+
return $result;
122+
}
123+
124+
$resolvableResources = [];
125+
foreach ($content as $key => $entry) {
126+
// resolve array of mixed content
127+
if ($entry instanceof ContentView) {
128+
$resolvedContentView = $this->resolveContentView($entry, $key);
129+
$result['content'][$name] = \array_merge($result['content'][$name] ?? [], $resolvedContentView['content']);
130+
$result['view'][$name] = \array_merge($result['view'][$name] ?? [], $resolvedContentView['view']);
131+
$resolvableResources = \array_merge_recursive($resolvableResources, $resolvedContentView['resolvableResources']);
132+
133+
continue;
134+
}
135+
136+
if ($entry instanceof ResolvableResource) {
137+
$resolvableResources[$entry->getResourceLoaderKey()][] = $entry;
138+
}
139+
140+
$result['content'][$name][$key] = $entry;
141+
$result['view'][$name][$key] = [];
142+
}
143+
144+
$result['resolvableResources'] = $resolvableResources;
145+
146+
return $result;
147+
}
148+
149+
if ($content instanceof ResolvableResource) {
150+
$result['resolvableResources'][$content->getResourceLoaderKey()][] = $content;
151+
}
152+
153+
$result['content'][$name] = $content;
154+
$result['view'][$name] = $view;
155+
156+
return $result;
157+
}
158+
159+
/**
160+
* Loads and resolves resources from various resource loaders.
161+
*
162+
* @param array<string, array<ResolvableResource>> $resourcesPerLoader Resource loaders and their associated resources to load
163+
*
164+
* @return array<string, mixed[]> Resolved resources organized by resource loader key
165+
*/
166+
private function loadAndResolveResources(array $resourcesPerLoader, ?string $locale): array
167+
{
168+
$resolvedResources = [];
169+
170+
foreach ($resourcesPerLoader as $loaderKey => $resourcesToLoad) {
171+
if (!$loaderKey) {
172+
throw new \RuntimeException(\sprintf('ResourceLoader key "%s" is invalid', $loaderKey));
173+
}
174+
175+
$resourceLoader = $this->resourceLoaderProvider->getResourceLoader($loaderKey);
176+
if (!$resourceLoader) {
177+
throw new \RuntimeException(\sprintf('ResourceLoader with key "%s" not found', $loaderKey));
178+
}
179+
180+
$resourceIds = \array_map(fn (ResolvableResource $resource) => $resource->getId(), $resourcesToLoad);
181+
$resolvedResources[$loaderKey] = $resourceLoader->load(
182+
$resourceIds,
183+
$locale
184+
);
185+
}
186+
187+
return $resolvedResources;
188+
}
189+
190+
/**
191+
* Replaces all instances of ResolvableResource in the given content with their resolved values.
192+
*
193+
* @param mixed[] $content The content to replace ResolvableResource instances
194+
* @param array<string, mixed[]> $resolvedResources The resolved resources, indexed by resource loader key and objectHash
195+
*
196+
* @return mixed[] The content with all ResolvableResource instances replaced with their resolved values
197+
*/
198+
private function replaceResolvableResourcesWithResolvedValues(array $content, array $resolvedResources): array
199+
{
200+
\array_walk_recursive($content, function(&$value) use ($resolvedResources) {
201+
if ($value instanceof ResolvableResource) {
202+
$value = $value->executeResourceCallback(
203+
$resolvedResources[$value->getResourceLoaderKey()][$value->getId()]
204+
);
205+
}
206+
});
207+
208+
return $content;
209+
}
210+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of Sulu.
7+
*
8+
* (c) Sulu GmbH
9+
*
10+
* This source file is subject to the MIT license that is bundled
11+
* with this source code in the file LICENSE.
12+
*/
13+
14+
namespace Sulu\Bundle\ContentBundle\Content\Application\ContentResolver;
15+
16+
use Sulu\Bundle\ContentBundle\Content\Domain\Model\ContentRichEntityInterface;
17+
use Sulu\Bundle\ContentBundle\Content\Domain\Model\DimensionContentInterface;
18+
19+
interface ContentResolverInterface
20+
{
21+
/**
22+
* @template T of ContentRichEntityInterface
23+
*
24+
* @param DimensionContentInterface<T> $dimensionContent
25+
*
26+
* @return array{
27+
* resource: object,
28+
* content: mixed,
29+
* view: mixed[]
30+
* }
31+
*/
32+
public function resolve(DimensionContentInterface $dimensionContent): array;
33+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of Sulu.
7+
*
8+
* (c) Sulu GmbH
9+
*
10+
* This source file is subject to the MIT license that is bundled
11+
* with this source code in the file LICENSE.
12+
*/
13+
14+
namespace Sulu\Bundle\ContentBundle\Content\Application\ContentResolver\Resolver;
15+
16+
use Sulu\Bundle\AdminBundle\Metadata\FormMetadata\FormMetadata;
17+
use Sulu\Bundle\AdminBundle\Metadata\MetadataProviderInterface;
18+
use Sulu\Bundle\CategoryBundle\Entity\CategoryInterface;
19+
use Sulu\Bundle\ContentBundle\Content\Application\ContentResolver\Value\ContentView;
20+
use Sulu\Bundle\ContentBundle\Content\Application\MetadataResolver\MetadataResolver;
21+
use Sulu\Bundle\ContentBundle\Content\Domain\Model\DimensionContentInterface;
22+
use Sulu\Bundle\ContentBundle\Content\Domain\Model\ExcerptInterface;
23+
use Sulu\Bundle\TagBundle\Tag\TagInterface;
24+
25+
class ExcerptResolver implements ResolverInterface
26+
{
27+
public function __construct(
28+
private MetadataProviderInterface $formMetadataProvider,
29+
private MetadataResolver $metadataResolver
30+
) {
31+
}
32+
33+
public function resolve(DimensionContentInterface $dimensionContent): ContentView
34+
{
35+
if (!$dimensionContent instanceof ExcerptInterface) {
36+
throw new \RuntimeException('DimensionContent needs to extend the ' . ExcerptInterface::class);
37+
}
38+
39+
/** @var string $locale */
40+
$locale = $dimensionContent->getLocale();
41+
42+
/** @var FormMetadata $formMetadata */
43+
$formMetadata = $this->formMetadataProvider->getMetadata($this->getFormKey(), $locale, []);
44+
45+
return ContentView::create(
46+
$this->metadataResolver->resolveItems(
47+
$formMetadata->getItems(),
48+
$this->getExcerptData($dimensionContent),
49+
$locale
50+
),
51+
[]
52+
);
53+
}
54+
55+
protected function getFormKey(): string
56+
{
57+
return 'content_excerpt';
58+
}
59+
60+
/**
61+
* @return array{
62+
* excerptTitle: string|null,
63+
* excerptMore: string|null,
64+
* excerptDescription: string|null,
65+
* excerptCategories: int[],
66+
* excerptTags: string[],
67+
* excerptIcon: array{id: int}|null,
68+
* excerptImage: array{id: int}|null
69+
* }
70+
*/
71+
protected function getExcerptData(ExcerptInterface $dimensionContent): array
72+
{
73+
return [
74+
'excerptTitle' => $dimensionContent->getExcerptTitle(),
75+
'excerptMore' => $dimensionContent->getExcerptMore(),
76+
'excerptDescription' => $dimensionContent->getExcerptDescription(),
77+
'excerptCategories' => \array_map(
78+
fn (CategoryInterface $category) => $category->getId(),
79+
$dimensionContent->getExcerptCategories()
80+
),
81+
'excerptTags' => \array_map(
82+
fn (TagInterface $tag) => $tag->getName(), $dimensionContent->getExcerptTags()
83+
),
84+
'excerptIcon' => $dimensionContent->getExcerptIcon(),
85+
'excerptImage' => $dimensionContent->getExcerptImage(),
86+
];
87+
}
88+
}

0 commit comments

Comments
 (0)