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

Commit

Permalink
Implemented annotations for SEO metadata information
Browse files Browse the repository at this point in the history
  • Loading branch information
wouterj committed May 27, 2016
1 parent d6d54db commit dc9b017
Show file tree
Hide file tree
Showing 18 changed files with 644 additions and 23 deletions.
73 changes: 73 additions & 0 deletions Loader/Annotation/Extras.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

namespace Symfony\Cmf\Bundle\SeoBundle\Loader\Annotation;

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

/**
* @author Wouter de Jong <[email protected]>
* @Annotation
*/
class Extras implements SeoMetadataAnnotation
{
public $key;
public $type;

private static $allowedTypesMethodMapping = [
'property' => 'addExtraProperty',
'name' => 'addExtraName',
'http-equiv' => 'addExtraHttp',
];

public function serialize()
{
return serialize($this->key);
}

public function unserialize($serialized)
{
list($this->key) = unserialize($serialized);
}

public function configureSeoMetadata(SeoMetadataInterface $seoMetadata, $value)
{
if (null === $this->key || null === $this->type) {
if (!is_array($value)) {
throw new InvalidArgumentException(
'Either set the "type" and "key" options for the @Extras annotation or provide an array with extras.'
);
}

$this->configureAllExtras($seoMetadata, $value);

return;
}

$this->guardTypeAllowed($this->type);

$seoMetadata->{self::$allowedTypesMethodMapping[$this->type]}($this->key, $value);
}

private function configureAllExtras(SeoMetadataInterface $seoMetadata, $value)
{
foreach ($value as $type => $extras) {
$this->guardTypeAllowed($type);

foreach ($extras as $key => $value) {
$seoMetadata->{self::$allowedTypesMethodMapping[$type]}($key, $value);
}
}
}

private function guardTypeAllowed($type)
{
if (!isset(self::$allowedTypesMethodMapping[$type])) {
throw new InvalidArgumentException(sprintf(
'Extras type "%s" not in the list of allowed ones: "%s".',
$type,
implode('", "', array_keys(self::$allowedTypesMethodMapping))
));
}
}
}
40 changes: 40 additions & 0 deletions Loader/Annotation/MetaDescription.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

namespace Symfony\Cmf\Bundle\SeoBundle\Loader\Annotation;

use Symfony\Cmf\Bundle\SeoBundle\Model\SeoMetadataInterface;

/**
* @author Wouter de Jong <[email protected]>
* @Annotation
*/
class MetaDescription implements SeoMetadataAnnotation
{
/**
* The description length to truncate the description.
*
* The default value 0 disables truncation.
*
* @var int
*/
public $truncate = 0;

public function serialize()
{
return serialize([$this->truncate]);
}

public function unserialize($serialized)
{
list($this->truncate) = unserialize($serialized);
}

public function configureSeoMetadata(SeoMetadataInterface $seoMetadata, $value)
{
if ($this->truncate > 0 && strlen($value) > $this->truncate) {
$value = substr($value, 0, $this->truncate).'...';
}

$seoMetadata->setMetaDescription($value);
}
}
30 changes: 30 additions & 0 deletions Loader/Annotation/MetaKeywords.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

namespace Symfony\Cmf\Bundle\SeoBundle\Loader\Annotation;

use Symfony\Cmf\Bundle\SeoBundle\Model\SeoMetadataInterface;

/**
* @author Wouter de Jong <[email protected]>
* @Annotation
*/
class MetaKeywords implements SeoMetadataAnnotation
{
public function serialize()
{
return '';
}

public function unserialize($serialized)
{
}

public function configureSeoMetadata(SeoMetadataInterface $seoMetadata, $value)
{
if ($value instanceof \Traversable) {
$value = iterator_to_array($value);
}

$seoMetadata->setMetaKeywords(implode(', ', (array) $value));
}
}
26 changes: 26 additions & 0 deletions Loader/Annotation/OriginalUrl.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace Symfony\Cmf\Bundle\SeoBundle\Loader\Annotation;

use Symfony\Cmf\Bundle\SeoBundle\Model\SeoMetadataInterface;

/**
* @author Wouter de Jong <[email protected]>
* @Annotation
*/
class OriginalUrl implements SeoMetadataAnnotation
{
public function serialize()
{
return '';
}

public function unserialize($serialized)
{
}

public function configureSeoMetadata(SeoMetadataInterface $seoMetadata, $value)
{
$seoMetadata->setOriginalUrl($value);
}
}
19 changes: 19 additions & 0 deletions Loader/Annotation/SeoMetadataAnnotation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace Symfony\Cmf\Bundle\SeoBundle\Loader\Annotation;

use Symfony\Cmf\Bundle\SeoBundle\Model\SeoMetadataInterface;

/**
* @author Wouter de Jong <[email protected]>
*/
interface SeoMetadataAnnotation extends \Serializable
{
/**
* Configures the seo metadata based on the extract value.
*
* @param SeoMetadataInterface $seoMetadata
* @param mixed $value
*/
public function configureSeoMetadata(SeoMetadataInterface $seoMetadata, $value);
}
26 changes: 26 additions & 0 deletions Loader/Annotation/Title.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace Symfony\Cmf\Bundle\SeoBundle\Loader\Annotation;

use Symfony\Cmf\Bundle\SeoBundle\Model\SeoMetadataInterface;

/**
* @author Wouter de Jong <[email protected]>
* @Annotation
*/
class Title implements SeoMetadataAnnotation
{
public function serialize()
{
return '';
}

public function unserialize($serialized)
{
}

public function configureSeoMetadata(SeoMetadataInterface $seoMetadata, $value)
{
$seoMetadata->setTitle($value);
}
}
130 changes: 130 additions & 0 deletions Loader/AnnotationLoader.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
<?php

namespace Symfony\Cmf\Bundle\SeoBundle\Loader;

use Doctrine\Common\Annotations\Reader;
use Symfony\Cmf\Bundle\SeoBundle\Loader\Annotation\MetaDescription;
use Symfony\Cmf\Bundle\SeoBundle\Loader\Annotation\MetaKeywords;
use Symfony\Cmf\Bundle\SeoBundle\Loader\Annotation\SeoMetadataAnnotation;
use Symfony\Cmf\Bundle\SeoBundle\Loader\Annotation\Title;
use Symfony\Cmf\Bundle\SeoBundle\Model\SeoMetadataInterface;
use Symfony\Component\Config\Loader\Loader;

/**
* @author Wouter de Jong <[email protected]>
*/
class AnnotationLoader extends Loader
{
/**
* @var Reader
*/
private $reader;

public function __construct(Reader $reader)
{
$this->reader = $reader;
}

public function supports($resource, $type = null)
{
return is_object($resource) && $this->isAnnotated($resource);
}

public function load($content, $type = null)
{
$seoMetadata = SeoMetadataFactory::initializeSeoMetadata($content);

$data = $this->getAnnotationsFromContent($content);

// todo cache $data

$classReflection = new \ReflectionClass($content);
foreach ($data['properties'] as $propertyName => $annotations) {
/** @var SeoMetadataAnnotation $annotation */
foreach ($annotations as $annotation) {
$property = $classReflection->getProperty($propertyName);
$property->setAccessible(true);

$annotation->configureSeoMetadata($seoMetadata, $property->getValue($content));
}
}

foreach ($data['methods'] as $methodName => $annotations) {
/** @var SeoMetadataAnnotation $annotation */
foreach ($annotations as $annotation) {
$annotation->configureSeoMetadata($seoMetadata, $content->{$methodName}());
}
}

return $seoMetadata;
}

private function getAnnotationsFromContent($content)
{
$classReflection = new \ReflectionClass($content);

return [
'properties' => $this->readProperties($classReflection->getProperties()),
'methods' => $this->readMethods($classReflection->getMethods()),
];
}

/**
* @param \ReflectionProperty[] $properties
*
* @return SeoMetadataAnnotation[][]
*/
private function readProperties(array $properties)
{
$propertyAnnotations = [];

foreach ($properties as $reflectionProperty) {
$annotations = $this->reader->getPropertyAnnotations($reflectionProperty);
$propertyName = $reflectionProperty->getName();

foreach ($annotations as $annotation) {
if ($annotation instanceof SeoMetadataAnnotation) {
if (!isset($propertyAnnotations[$propertyName])) {
$propertyAnnotations[$propertyName] = [];
}

$propertyAnnotations[$propertyName][] = $annotation;
}
}
}

return $propertyAnnotations;
}

/**
* @param \ReflectionMethod[] $methods
*
* @return SeoMetadataAnnotation[][]
*/
private function readMethods(array $methods)
{
$methodAnnotations = [];

foreach ($methods as $reflectionMethod) {
$annotations = $this->reader->getMethodAnnotations($reflectionMethod);
$methodName = $reflectionMethod->getName();

foreach ($annotations as $annotation) {
if ($annotation instanceof SeoMetadataAnnotation) {
if (!isset($methodAnnotations[$methodName])) {
$methodAnnotations[$methodName] = [];
}

$methodAnnotations[$methodName][] = $annotation;
}
}
}

return $methodAnnotations;
}

private function isAnnotated($content)
{
return 0 !== count(call_user_func_array('array_merge', $this->getAnnotationsFromContent($content)));
}
}
Loading

0 comments on commit dc9b017

Please sign in to comment.