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

Commit

Permalink
Introduced the concept of loaders
Browse files Browse the repository at this point in the history
This is the start of supporting annotations and mapping files instead of
PHP extractor methods and lots of interfaces.
  • Loading branch information
wouterj committed May 27, 2016
1 parent 315cb2a commit 0bb3009
Show file tree
Hide file tree
Showing 14 changed files with 518 additions and 356 deletions.
15 changes: 10 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
Changelog
=========

* **2016-05-27**: [BC BREAK] Removed `SeoPresentation::addExtractor()`, use `ExtractorLoader::addExtractor()` instead.
* **2016-05-27**: [BC BREAK] Changed the fourth argument of the constructorof `SeoPresentation` from `CacheInterface`
to `LoaderInterface`.
* **2016-05-27**: Added loaders.

1.2.0
-----

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

Expand Down
1 change: 1 addition & 0 deletions DependencyInjection/CmfSeoExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ public function load(array $configs, ContainerBuilder $container)

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

$this->loadSeoParameters($config, $container);
Expand Down
4 changes: 2 additions & 2 deletions DependencyInjection/Compiler/RegisterExtractorsPass.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ class RegisterExtractorsPass implements CompilerPassInterface
*/
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('cmf_seo.presentation')) {
if (!$container->hasDefinition('cmf_seo.loader.extractor')) {
return;
}

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

foreach ($taggedServices as $id => $attributes) {
Expand Down
146 changes: 146 additions & 0 deletions Loader/ExtractorLoader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
<?php

namespace Symfony\Cmf\Bundle\SeoBundle\Loader;

use Symfony\Cmf\Bundle\SeoBundle\Cache\CacheInterface;
use Symfony\Cmf\Bundle\SeoBundle\Extractor\ExtractorInterface;
use Symfony\Component\Config\Loader\Loader;

/**
* @author Wouter de Jong <[email protected]>
*/
class ExtractorLoader extends Loader
{
/**
* @var null|CacheInterface
*/
private $cache;

/**
* @var ExtractorInterface[][]
*/
private $extractors = array();

/**
* @param CacheInterface $cache
*/
public function __construct(CacheInterface $cache = null)
{
$this->cache = $cache;
}

/**
* Add an extractor for SEO metadata.
*
* @param ExtractorInterface $extractor
* @param int $priority
*/
public function addExtractor(ExtractorInterface $extractor, $priority = 0)
{
if (!isset($this->extractors[$priority])) {
$this->extractors[$priority] = array();
}
$this->extractors[$priority][] = $extractor;
}

/**
* {@inheritdoc}
*/
public function supports($resource, $type = null)
{
return is_object($resource) && ((!$type && $this->containsExtractors($resource)) || 'extractors' === $type);
}

/**
* {@inheritdoc}
*
* @param object $content
*/
public function load($content, $type = null)
{
$seoMetadata = SeoMetadataFactory::initializeSeoMetadata($content);

$extractors = $this->getExtractorsForContent($content);

foreach ($extractors as $extractor) {
$extractor->updateMetadata($content, $seoMetadata);
}

return $seoMetadata;
}

/**
* Returns and caches the extractors for content.
*
* @param object $content
*
* @return ExtractorInterface[]
*/
private function getExtractorsForContent($content)
{
$cachingAvailable = (bool) $this->cache;

if (!$cachingAvailable) {
return $this->findExtractorsForContent($content);
}

$extractors = $this->cache->loadExtractorsFromCache(get_class($content));

if (null === $extractors || !$extractors->isFresh()) {
$extractors = $this->findExtractorsForContent($content);
$this->cache->putExtractorsInCache(get_class($content), $extractors);
}

return $extractors;
}

/**
* Returns the extractors that support the content.
*
* @param object $content
*
* @return ExtractorInterface[]
*/
private function findExtractorsForContent($content)
{
$extractors = array();
ksort($this->extractors);
foreach ($this->extractors as $priority) {
$supportedExtractors = array_filter($priority, function (ExtractorInterface $extractor) use ($content) {
return $extractor->supports($content);
});

$extractors = array_merge($extractors, $supportedExtractors);
}

return $extractors;
}

/**
* Whether there are extractors supporting the content.
*
* @param object $content
*
* @return bool
*/
private function containsExtractors($content)
{
$cacheAvailable = (bool) $this->cache;
if ($cacheAvailable) {
$extractors = $this->cache->loadExtractorsFromCache(get_class($content));

if (null !== $extractors) {
return count($extractors) > 0;
}
}

ksort($this->extractors);
foreach (array_map('array_merge', $this->extractors) as $extractor) {
if ($extractor->supports($content)) {
return true;
}
}

return false;
}
}
74 changes: 74 additions & 0 deletions Loader/SeoMetadataFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php

namespace Symfony\Cmf\Bundle\SeoBundle\Loader;

use Symfony\Cmf\Bundle\SeoBundle\Exception\InvalidArgumentException;
use Symfony\Cmf\Bundle\SeoBundle\Model\SeoMetadata;
use Symfony\Cmf\Bundle\SeoBundle\Model\SeoMetadataInterface;
use Symfony\Cmf\Bundle\SeoBundle\SeoAwareInterface;

/**
* Creates a SeoMetadata object based on the content.
*
* This returns either an empty SeoMetadata instance or the
* SeoMetadata instance return by getSeoMetadata() of the
* content object.
*
* @author Wouter de Jong <[email protected]>
*/
class SeoMetadataFactory
{
/**
* @param object $content
*
* @return SeoMetadataInterface
*
* @throws InvalidArgumentException
*/
public static function initializeSeoMetadata($content)
{
if (!$content instanceof SeoAwareInterface) {
return new SeoMetadata();
}

$contentSeoMetadata = $content->getSeoMetadata();

if ($contentSeoMetadata instanceof SeoMetadataInterface) {
return self::copyMetadata($contentSeoMetadata);
}

if (null === $contentSeoMetadata) {
$seoMetadata = new SeoMetadata();
$content->setSeoMetadata($seoMetadata); // make sure it has metadata the next time

return $seoMetadata;
}

throw new InvalidArgumentException(sprintf(
'getSeoMetadata must return either an instance of SeoMetadataInterface or null, "%s" given',
is_object($contentSeoMetadata) ? get_class($contentSeoMetadata) : gettype($contentSeoMetadata)
));
}

/**
* Copy the metadata object to sanitize it and remove doctrine traces.
*
* @param SeoMetadataInterface $contentSeoMetadata
*
* @return SeoMetadata
*/
private static function copyMetadata(SeoMetadataInterface $contentSeoMetadata)
{
$metadata = new SeoMetadata();

return $metadata
->setTitle($contentSeoMetadata->getTitle())
->setMetaKeywords($contentSeoMetadata->getMetaKeywords())
->setMetaDescription($contentSeoMetadata->getMetaDescription())
->setOriginalUrl($contentSeoMetadata->getOriginalUrl())
->setExtraProperties($contentSeoMetadata->getExtraProperties() ?: array())
->setExtraNames($contentSeoMetadata->getExtraNames() ?: array())
->setExtraHttp($contentSeoMetadata->getExtraHttp() ?: array())
;
}
}
24 changes: 24 additions & 0 deletions Resources/config/loaders.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?xml version="1.0" ?>

<container
xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

<services>
<service id="cmf_seo.loader_resolver" class="Symfony\Component\Config\Loader\LoaderResolver" public="false">
<argument type="collection">
<argument type="service" id="cmf_seo.loader.extractor"/>
</argument>
</service>

<service id="cmf_seo.loader" class="Symfony\Component\Config\Loader\DelegatingLoader" public="false">
<argument type="service" id="cmf_seo.loader_resolver"/>
</service>

<service id="cmf_seo.loader.extractor" class="Symfony\Cmf\Bundle\SeoBundle\Loader\ExtractorLoader" public="false">
<argument type="service" id="file_locator"/>
<argument type="service" id="cmf_seo.cache"/>
</service>
</services>
</container>
2 changes: 1 addition & 1 deletion Resources/config/services.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
<argument type="service" id="sonata.seo.page" />
<argument type="service" id="translator" />
<argument type="service" id="cmf_seo.config_values" />
<argument type="service" id="cmf_seo.cache" />
<argument type="service" id="cmf_seo.loader" />
</service>

<service id="cmf_seo.error.suggestion_provider.controller" class="%cmf_seo.error.suggestion_provider.controller.class%">
Expand Down
Loading

0 comments on commit 0bb3009

Please sign in to comment.