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

Commit dc9b017

Browse files
committed
Implemented annotations for SEO metadata information
1 parent d6d54db commit dc9b017

18 files changed

+644
-23
lines changed

Loader/Annotation/Extras.php

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<?php
2+
3+
namespace Symfony\Cmf\Bundle\SeoBundle\Loader\Annotation;
4+
5+
use Symfony\Cmf\Bundle\SeoBundle\Exception\InvalidArgumentException;
6+
use Symfony\Cmf\Bundle\SeoBundle\Model\SeoMetadataInterface;
7+
8+
/**
9+
* @author Wouter de Jong <[email protected]>
10+
* @Annotation
11+
*/
12+
class Extras implements SeoMetadataAnnotation
13+
{
14+
public $key;
15+
public $type;
16+
17+
private static $allowedTypesMethodMapping = [
18+
'property' => 'addExtraProperty',
19+
'name' => 'addExtraName',
20+
'http-equiv' => 'addExtraHttp',
21+
];
22+
23+
public function serialize()
24+
{
25+
return serialize($this->key);
26+
}
27+
28+
public function unserialize($serialized)
29+
{
30+
list($this->key) = unserialize($serialized);
31+
}
32+
33+
public function configureSeoMetadata(SeoMetadataInterface $seoMetadata, $value)
34+
{
35+
if (null === $this->key || null === $this->type) {
36+
if (!is_array($value)) {
37+
throw new InvalidArgumentException(
38+
'Either set the "type" and "key" options for the @Extras annotation or provide an array with extras.'
39+
);
40+
}
41+
42+
$this->configureAllExtras($seoMetadata, $value);
43+
44+
return;
45+
}
46+
47+
$this->guardTypeAllowed($this->type);
48+
49+
$seoMetadata->{self::$allowedTypesMethodMapping[$this->type]}($this->key, $value);
50+
}
51+
52+
private function configureAllExtras(SeoMetadataInterface $seoMetadata, $value)
53+
{
54+
foreach ($value as $type => $extras) {
55+
$this->guardTypeAllowed($type);
56+
57+
foreach ($extras as $key => $value) {
58+
$seoMetadata->{self::$allowedTypesMethodMapping[$type]}($key, $value);
59+
}
60+
}
61+
}
62+
63+
private function guardTypeAllowed($type)
64+
{
65+
if (!isset(self::$allowedTypesMethodMapping[$type])) {
66+
throw new InvalidArgumentException(sprintf(
67+
'Extras type "%s" not in the list of allowed ones: "%s".',
68+
$type,
69+
implode('", "', array_keys(self::$allowedTypesMethodMapping))
70+
));
71+
}
72+
}
73+
}

Loader/Annotation/MetaDescription.php

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
namespace Symfony\Cmf\Bundle\SeoBundle\Loader\Annotation;
4+
5+
use Symfony\Cmf\Bundle\SeoBundle\Model\SeoMetadataInterface;
6+
7+
/**
8+
* @author Wouter de Jong <[email protected]>
9+
* @Annotation
10+
*/
11+
class MetaDescription implements SeoMetadataAnnotation
12+
{
13+
/**
14+
* The description length to truncate the description.
15+
*
16+
* The default value 0 disables truncation.
17+
*
18+
* @var int
19+
*/
20+
public $truncate = 0;
21+
22+
public function serialize()
23+
{
24+
return serialize([$this->truncate]);
25+
}
26+
27+
public function unserialize($serialized)
28+
{
29+
list($this->truncate) = unserialize($serialized);
30+
}
31+
32+
public function configureSeoMetadata(SeoMetadataInterface $seoMetadata, $value)
33+
{
34+
if ($this->truncate > 0 && strlen($value) > $this->truncate) {
35+
$value = substr($value, 0, $this->truncate).'...';
36+
}
37+
38+
$seoMetadata->setMetaDescription($value);
39+
}
40+
}

Loader/Annotation/MetaKeywords.php

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
namespace Symfony\Cmf\Bundle\SeoBundle\Loader\Annotation;
4+
5+
use Symfony\Cmf\Bundle\SeoBundle\Model\SeoMetadataInterface;
6+
7+
/**
8+
* @author Wouter de Jong <[email protected]>
9+
* @Annotation
10+
*/
11+
class MetaKeywords implements SeoMetadataAnnotation
12+
{
13+
public function serialize()
14+
{
15+
return '';
16+
}
17+
18+
public function unserialize($serialized)
19+
{
20+
}
21+
22+
public function configureSeoMetadata(SeoMetadataInterface $seoMetadata, $value)
23+
{
24+
if ($value instanceof \Traversable) {
25+
$value = iterator_to_array($value);
26+
}
27+
28+
$seoMetadata->setMetaKeywords(implode(', ', (array) $value));
29+
}
30+
}

Loader/Annotation/OriginalUrl.php

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
namespace Symfony\Cmf\Bundle\SeoBundle\Loader\Annotation;
4+
5+
use Symfony\Cmf\Bundle\SeoBundle\Model\SeoMetadataInterface;
6+
7+
/**
8+
* @author Wouter de Jong <[email protected]>
9+
* @Annotation
10+
*/
11+
class OriginalUrl implements SeoMetadataAnnotation
12+
{
13+
public function serialize()
14+
{
15+
return '';
16+
}
17+
18+
public function unserialize($serialized)
19+
{
20+
}
21+
22+
public function configureSeoMetadata(SeoMetadataInterface $seoMetadata, $value)
23+
{
24+
$seoMetadata->setOriginalUrl($value);
25+
}
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace Symfony\Cmf\Bundle\SeoBundle\Loader\Annotation;
4+
5+
use Symfony\Cmf\Bundle\SeoBundle\Model\SeoMetadataInterface;
6+
7+
/**
8+
* @author Wouter de Jong <[email protected]>
9+
*/
10+
interface SeoMetadataAnnotation extends \Serializable
11+
{
12+
/**
13+
* Configures the seo metadata based on the extract value.
14+
*
15+
* @param SeoMetadataInterface $seoMetadata
16+
* @param mixed $value
17+
*/
18+
public function configureSeoMetadata(SeoMetadataInterface $seoMetadata, $value);
19+
}

Loader/Annotation/Title.php

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
namespace Symfony\Cmf\Bundle\SeoBundle\Loader\Annotation;
4+
5+
use Symfony\Cmf\Bundle\SeoBundle\Model\SeoMetadataInterface;
6+
7+
/**
8+
* @author Wouter de Jong <[email protected]>
9+
* @Annotation
10+
*/
11+
class Title implements SeoMetadataAnnotation
12+
{
13+
public function serialize()
14+
{
15+
return '';
16+
}
17+
18+
public function unserialize($serialized)
19+
{
20+
}
21+
22+
public function configureSeoMetadata(SeoMetadataInterface $seoMetadata, $value)
23+
{
24+
$seoMetadata->setTitle($value);
25+
}
26+
}

Loader/AnnotationLoader.php

+130
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
<?php
2+
3+
namespace Symfony\Cmf\Bundle\SeoBundle\Loader;
4+
5+
use Doctrine\Common\Annotations\Reader;
6+
use Symfony\Cmf\Bundle\SeoBundle\Loader\Annotation\MetaDescription;
7+
use Symfony\Cmf\Bundle\SeoBundle\Loader\Annotation\MetaKeywords;
8+
use Symfony\Cmf\Bundle\SeoBundle\Loader\Annotation\SeoMetadataAnnotation;
9+
use Symfony\Cmf\Bundle\SeoBundle\Loader\Annotation\Title;
10+
use Symfony\Cmf\Bundle\SeoBundle\Model\SeoMetadataInterface;
11+
use Symfony\Component\Config\Loader\Loader;
12+
13+
/**
14+
* @author Wouter de Jong <[email protected]>
15+
*/
16+
class AnnotationLoader extends Loader
17+
{
18+
/**
19+
* @var Reader
20+
*/
21+
private $reader;
22+
23+
public function __construct(Reader $reader)
24+
{
25+
$this->reader = $reader;
26+
}
27+
28+
public function supports($resource, $type = null)
29+
{
30+
return is_object($resource) && $this->isAnnotated($resource);
31+
}
32+
33+
public function load($content, $type = null)
34+
{
35+
$seoMetadata = SeoMetadataFactory::initializeSeoMetadata($content);
36+
37+
$data = $this->getAnnotationsFromContent($content);
38+
39+
// todo cache $data
40+
41+
$classReflection = new \ReflectionClass($content);
42+
foreach ($data['properties'] as $propertyName => $annotations) {
43+
/** @var SeoMetadataAnnotation $annotation */
44+
foreach ($annotations as $annotation) {
45+
$property = $classReflection->getProperty($propertyName);
46+
$property->setAccessible(true);
47+
48+
$annotation->configureSeoMetadata($seoMetadata, $property->getValue($content));
49+
}
50+
}
51+
52+
foreach ($data['methods'] as $methodName => $annotations) {
53+
/** @var SeoMetadataAnnotation $annotation */
54+
foreach ($annotations as $annotation) {
55+
$annotation->configureSeoMetadata($seoMetadata, $content->{$methodName}());
56+
}
57+
}
58+
59+
return $seoMetadata;
60+
}
61+
62+
private function getAnnotationsFromContent($content)
63+
{
64+
$classReflection = new \ReflectionClass($content);
65+
66+
return [
67+
'properties' => $this->readProperties($classReflection->getProperties()),
68+
'methods' => $this->readMethods($classReflection->getMethods()),
69+
];
70+
}
71+
72+
/**
73+
* @param \ReflectionProperty[] $properties
74+
*
75+
* @return SeoMetadataAnnotation[][]
76+
*/
77+
private function readProperties(array $properties)
78+
{
79+
$propertyAnnotations = [];
80+
81+
foreach ($properties as $reflectionProperty) {
82+
$annotations = $this->reader->getPropertyAnnotations($reflectionProperty);
83+
$propertyName = $reflectionProperty->getName();
84+
85+
foreach ($annotations as $annotation) {
86+
if ($annotation instanceof SeoMetadataAnnotation) {
87+
if (!isset($propertyAnnotations[$propertyName])) {
88+
$propertyAnnotations[$propertyName] = [];
89+
}
90+
91+
$propertyAnnotations[$propertyName][] = $annotation;
92+
}
93+
}
94+
}
95+
96+
return $propertyAnnotations;
97+
}
98+
99+
/**
100+
* @param \ReflectionMethod[] $methods
101+
*
102+
* @return SeoMetadataAnnotation[][]
103+
*/
104+
private function readMethods(array $methods)
105+
{
106+
$methodAnnotations = [];
107+
108+
foreach ($methods as $reflectionMethod) {
109+
$annotations = $this->reader->getMethodAnnotations($reflectionMethod);
110+
$methodName = $reflectionMethod->getName();
111+
112+
foreach ($annotations as $annotation) {
113+
if ($annotation instanceof SeoMetadataAnnotation) {
114+
if (!isset($methodAnnotations[$methodName])) {
115+
$methodAnnotations[$methodName] = [];
116+
}
117+
118+
$methodAnnotations[$methodName][] = $annotation;
119+
}
120+
}
121+
}
122+
123+
return $methodAnnotations;
124+
}
125+
126+
private function isAnnotated($content)
127+
{
128+
return 0 !== count(call_user_func_array('array_merge', $this->getAnnotationsFromContent($content)));
129+
}
130+
}

0 commit comments

Comments
 (0)