Skip to content
This repository was archived by the owner on Sep 16, 2021. It is now read-only.

Commit 0bb3009

Browse files
committed
Introduced the concept of loaders
This is the start of supporting annotations and mapping files instead of PHP extractor methods and lots of interfaces.
1 parent 315cb2a commit 0bb3009

File tree

14 files changed

+518
-356
lines changed

14 files changed

+518
-356
lines changed

CHANGELOG.md

+10-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
Changelog
22
=========
33

4+
* **2016-05-27**: [BC BREAK] Removed `SeoPresentation::addExtractor()`, use `ExtractorLoader::addExtractor()` instead.
5+
* **2016-05-27**: [BC BREAK] Changed the fourth argument of the constructorof `SeoPresentation` from `CacheInterface`
6+
to `LoaderInterface`.
7+
* **2016-05-27**: Added loaders.
8+
49
1.2.0
510
-----
611

@@ -11,15 +16,15 @@ Changelog
1116
* **2015-08-20**: Added templates configuration and `exclusion_rules` (based on the request matcher) to
1217
the error handling configuration
1318
* **2015-08-12**: Added configuration for the default data class of the `seo_metadata` form type.
14-
* **2015-07-20**: Cleaned up the sitemap generation. If you used the unreleased
19+
* **2015-07-20**: Cleaned up the sitemap generation. If you used the unreleased
1520
version of sitemaps, you will need to adjust your code. See https://github.com/symfony-cmf/SeoBundle/pull/225
16-
Options are available to keep all or no voters|guessers|loaders enabled or
21+
Options are available to keep all or no voters|guessers|loaders enabled or
1722
enable them one by one by their service id.
18-
* **2015-02-24**: Configuration for `content_key` moved to the `content_listener`
19-
section, and its now possible to disable the content listener by setting
23+
* **2015-02-24**: Configuration for `content_key` moved to the `content_listener`
24+
section, and its now possible to disable the content listener by setting
2025
`cmf_seo.content_listener.enabled: false`
2126
* **2015-02-14**: Added sitemap generation
22-
* **2015-02-14**: [BC BREAK] Changed method visibility of
27+
* **2015-02-14**: [BC BREAK] Changed method visibility of
2328
`SeoPresentation#getSeoMetadata()` from private to public.
2429
* **2014-10-04**: Custom exception controller for error handling.
2530

DependencyInjection/CmfSeoExtension.php

+1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ public function load(array $configs, ContainerBuilder $container)
5353

5454
$loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
5555
$loader->load('services.xml');
56+
$loader->load('loaders.xml');
5657
$loader->load('extractors.xml');
5758

5859
$this->loadSeoParameters($config, $container);

DependencyInjection/Compiler/RegisterExtractorsPass.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@ class RegisterExtractorsPass implements CompilerPassInterface
3131
*/
3232
public function process(ContainerBuilder $container)
3333
{
34-
if (!$container->hasDefinition('cmf_seo.presentation')) {
34+
if (!$container->hasDefinition('cmf_seo.loader.extractor')) {
3535
return;
3636
}
3737

38-
$strategyDefinition = $container->getDefinition('cmf_seo.presentation');
38+
$strategyDefinition = $container->getDefinition('cmf_seo.loader.extractor');
3939
$taggedServices = $container->findTaggedServiceIds('cmf_seo.extractor');
4040

4141
foreach ($taggedServices as $id => $attributes) {

Loader/ExtractorLoader.php

+146
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
<?php
2+
3+
namespace Symfony\Cmf\Bundle\SeoBundle\Loader;
4+
5+
use Symfony\Cmf\Bundle\SeoBundle\Cache\CacheInterface;
6+
use Symfony\Cmf\Bundle\SeoBundle\Extractor\ExtractorInterface;
7+
use Symfony\Component\Config\Loader\Loader;
8+
9+
/**
10+
* @author Wouter de Jong <[email protected]>
11+
*/
12+
class ExtractorLoader extends Loader
13+
{
14+
/**
15+
* @var null|CacheInterface
16+
*/
17+
private $cache;
18+
19+
/**
20+
* @var ExtractorInterface[][]
21+
*/
22+
private $extractors = array();
23+
24+
/**
25+
* @param CacheInterface $cache
26+
*/
27+
public function __construct(CacheInterface $cache = null)
28+
{
29+
$this->cache = $cache;
30+
}
31+
32+
/**
33+
* Add an extractor for SEO metadata.
34+
*
35+
* @param ExtractorInterface $extractor
36+
* @param int $priority
37+
*/
38+
public function addExtractor(ExtractorInterface $extractor, $priority = 0)
39+
{
40+
if (!isset($this->extractors[$priority])) {
41+
$this->extractors[$priority] = array();
42+
}
43+
$this->extractors[$priority][] = $extractor;
44+
}
45+
46+
/**
47+
* {@inheritdoc}
48+
*/
49+
public function supports($resource, $type = null)
50+
{
51+
return is_object($resource) && ((!$type && $this->containsExtractors($resource)) || 'extractors' === $type);
52+
}
53+
54+
/**
55+
* {@inheritdoc}
56+
*
57+
* @param object $content
58+
*/
59+
public function load($content, $type = null)
60+
{
61+
$seoMetadata = SeoMetadataFactory::initializeSeoMetadata($content);
62+
63+
$extractors = $this->getExtractorsForContent($content);
64+
65+
foreach ($extractors as $extractor) {
66+
$extractor->updateMetadata($content, $seoMetadata);
67+
}
68+
69+
return $seoMetadata;
70+
}
71+
72+
/**
73+
* Returns and caches the extractors for content.
74+
*
75+
* @param object $content
76+
*
77+
* @return ExtractorInterface[]
78+
*/
79+
private function getExtractorsForContent($content)
80+
{
81+
$cachingAvailable = (bool) $this->cache;
82+
83+
if (!$cachingAvailable) {
84+
return $this->findExtractorsForContent($content);
85+
}
86+
87+
$extractors = $this->cache->loadExtractorsFromCache(get_class($content));
88+
89+
if (null === $extractors || !$extractors->isFresh()) {
90+
$extractors = $this->findExtractorsForContent($content);
91+
$this->cache->putExtractorsInCache(get_class($content), $extractors);
92+
}
93+
94+
return $extractors;
95+
}
96+
97+
/**
98+
* Returns the extractors that support the content.
99+
*
100+
* @param object $content
101+
*
102+
* @return ExtractorInterface[]
103+
*/
104+
private function findExtractorsForContent($content)
105+
{
106+
$extractors = array();
107+
ksort($this->extractors);
108+
foreach ($this->extractors as $priority) {
109+
$supportedExtractors = array_filter($priority, function (ExtractorInterface $extractor) use ($content) {
110+
return $extractor->supports($content);
111+
});
112+
113+
$extractors = array_merge($extractors, $supportedExtractors);
114+
}
115+
116+
return $extractors;
117+
}
118+
119+
/**
120+
* Whether there are extractors supporting the content.
121+
*
122+
* @param object $content
123+
*
124+
* @return bool
125+
*/
126+
private function containsExtractors($content)
127+
{
128+
$cacheAvailable = (bool) $this->cache;
129+
if ($cacheAvailable) {
130+
$extractors = $this->cache->loadExtractorsFromCache(get_class($content));
131+
132+
if (null !== $extractors) {
133+
return count($extractors) > 0;
134+
}
135+
}
136+
137+
ksort($this->extractors);
138+
foreach (array_map('array_merge', $this->extractors) as $extractor) {
139+
if ($extractor->supports($content)) {
140+
return true;
141+
}
142+
}
143+
144+
return false;
145+
}
146+
}

Loader/SeoMetadataFactory.php

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<?php
2+
3+
namespace Symfony\Cmf\Bundle\SeoBundle\Loader;
4+
5+
use Symfony\Cmf\Bundle\SeoBundle\Exception\InvalidArgumentException;
6+
use Symfony\Cmf\Bundle\SeoBundle\Model\SeoMetadata;
7+
use Symfony\Cmf\Bundle\SeoBundle\Model\SeoMetadataInterface;
8+
use Symfony\Cmf\Bundle\SeoBundle\SeoAwareInterface;
9+
10+
/**
11+
* Creates a SeoMetadata object based on the content.
12+
*
13+
* This returns either an empty SeoMetadata instance or the
14+
* SeoMetadata instance return by getSeoMetadata() of the
15+
* content object.
16+
*
17+
* @author Wouter de Jong <[email protected]>
18+
*/
19+
class SeoMetadataFactory
20+
{
21+
/**
22+
* @param object $content
23+
*
24+
* @return SeoMetadataInterface
25+
*
26+
* @throws InvalidArgumentException
27+
*/
28+
public static function initializeSeoMetadata($content)
29+
{
30+
if (!$content instanceof SeoAwareInterface) {
31+
return new SeoMetadata();
32+
}
33+
34+
$contentSeoMetadata = $content->getSeoMetadata();
35+
36+
if ($contentSeoMetadata instanceof SeoMetadataInterface) {
37+
return self::copyMetadata($contentSeoMetadata);
38+
}
39+
40+
if (null === $contentSeoMetadata) {
41+
$seoMetadata = new SeoMetadata();
42+
$content->setSeoMetadata($seoMetadata); // make sure it has metadata the next time
43+
44+
return $seoMetadata;
45+
}
46+
47+
throw new InvalidArgumentException(sprintf(
48+
'getSeoMetadata must return either an instance of SeoMetadataInterface or null, "%s" given',
49+
is_object($contentSeoMetadata) ? get_class($contentSeoMetadata) : gettype($contentSeoMetadata)
50+
));
51+
}
52+
53+
/**
54+
* Copy the metadata object to sanitize it and remove doctrine traces.
55+
*
56+
* @param SeoMetadataInterface $contentSeoMetadata
57+
*
58+
* @return SeoMetadata
59+
*/
60+
private static function copyMetadata(SeoMetadataInterface $contentSeoMetadata)
61+
{
62+
$metadata = new SeoMetadata();
63+
64+
return $metadata
65+
->setTitle($contentSeoMetadata->getTitle())
66+
->setMetaKeywords($contentSeoMetadata->getMetaKeywords())
67+
->setMetaDescription($contentSeoMetadata->getMetaDescription())
68+
->setOriginalUrl($contentSeoMetadata->getOriginalUrl())
69+
->setExtraProperties($contentSeoMetadata->getExtraProperties() ?: array())
70+
->setExtraNames($contentSeoMetadata->getExtraNames() ?: array())
71+
->setExtraHttp($contentSeoMetadata->getExtraHttp() ?: array())
72+
;
73+
}
74+
}

Resources/config/loaders.xml

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?xml version="1.0" ?>
2+
3+
<container
4+
xmlns="http://symfony.com/schema/dic/services"
5+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
6+
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
7+
8+
<services>
9+
<service id="cmf_seo.loader_resolver" class="Symfony\Component\Config\Loader\LoaderResolver" public="false">
10+
<argument type="collection">
11+
<argument type="service" id="cmf_seo.loader.extractor"/>
12+
</argument>
13+
</service>
14+
15+
<service id="cmf_seo.loader" class="Symfony\Component\Config\Loader\DelegatingLoader" public="false">
16+
<argument type="service" id="cmf_seo.loader_resolver"/>
17+
</service>
18+
19+
<service id="cmf_seo.loader.extractor" class="Symfony\Cmf\Bundle\SeoBundle\Loader\ExtractorLoader" public="false">
20+
<argument type="service" id="file_locator"/>
21+
<argument type="service" id="cmf_seo.cache"/>
22+
</service>
23+
</services>
24+
</container>

Resources/config/services.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
<argument type="service" id="sonata.seo.page" />
4343
<argument type="service" id="translator" />
4444
<argument type="service" id="cmf_seo.config_values" />
45-
<argument type="service" id="cmf_seo.cache" />
45+
<argument type="service" id="cmf_seo.loader" />
4646
</service>
4747

4848
<service id="cmf_seo.error.suggestion_provider.controller" class="%cmf_seo.error.suggestion_provider.controller.class%">

0 commit comments

Comments
 (0)