Commit bf60401c authored by e0ipso's avatar e0ipso
Browse files

wip: adds support for wrapper refinements

parent 44cf211c
......@@ -4,6 +4,9 @@ namespace Drupal\typed_entity_example\TypedRepositories;
use Drupal\Core\Entity\Query\ConditionInterface;
use Drupal\typed_entity\TypedRepositories\TypedEntityRepositoryBase;
use Drupal\typed_entity\WrappedEntityVariants\FieldValueVariantCondition;
use Drupal\typed_entity_example\WrappedEntities\BakingArticle;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* The repository for articles.
......@@ -15,6 +18,13 @@ final class ArticleRepository extends TypedEntityRepositoryBase {
*/
const FIELD_TAGS_NAME = 'field_tags';
public function __construct(ContainerInterface $container) {
parent::__construct($container);
$this->variantConditions = [
new FieldValueVariantCondition(static::FIELD_TAGS_NAME, 24, BakingArticle::class),
];
}
/**
* Finds article by tags.
*
......
......@@ -11,7 +11,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* The wrapped entity for the article content type.
*/
final class Article extends WrappedEntityBase {
class Article extends WrappedEntityBase {
/**
* The messenger.
......
<?php
namespace Drupal\typed_entity_example\WrappedEntities;
/**
* The wrapped entity for the article content type tagged with Baking.
*/
final class BakingArticle extends Article {
public function yeastOrBakingSoda(): string {
return mt_rand(0, 1) ? 'yeast' : 'baking soda';
}
}
......@@ -8,6 +8,8 @@ use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\Query\QueryInterface;
use Drupal\typed_entity\InvalidValueException;
use Drupal\typed_entity\WrappedEntities\WrappedEntityInterface;
use Drupal\typed_entity\WrappedEntityVariants\ContextAwareInterface;
use Drupal\typed_entity\WrappedEntityVariants\VariantConditionInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use UnexpectedValueException;
......@@ -58,6 +60,13 @@ class TypedEntityRepositoryBase implements TypedEntityRepositoryInterface {
*/
protected $wrapperClass;
/**
* Variant conditions.
*
* @var \Drupal\typed_entity\WrappedEntityVariants\VariantConditionInterface[]
*/
protected $variantConditions = [];
/**
* RepositoryCollector constructor.
*
......@@ -74,15 +83,14 @@ class TypedEntityRepositoryBase implements TypedEntityRepositoryInterface {
*/
public function wrap(EntityInterface $entity): WrappedEntityInterface {
// Validate that this entity can be wrapped.
$can_be_wrapped = $this->entityType->id() === $entity->getEntityTypeId();
if ($this->bundle) {
$can_be_wrapped = $this->bundle === $entity->bundle();
}
$can_be_wrapped = $this->entityType->id() === $entity->getEntityTypeId()
&& $this->bundle === $entity->bundle();
if (!$can_be_wrapped) {
throw new InvalidValueException('Unable to wrap entity with this repository.');
}
$class = $this->negotiateVariant($entity);
return call_user_func(
[$this->wrapperClass, 'create'],
[$class, 'create'],
$this->container,
$entity
);
......@@ -106,6 +114,36 @@ class TypedEntityRepositoryBase implements TypedEntityRepositoryInterface {
return $wrapped;
}
/**
* Negotiates possible variants to the default based on entity values.
*
* Override this in the repositories that need variance.
*
* @param \Drupal\Core\Entity\EntityInterface $entity
* The entity.
*
* @return string
* The negotiated variant.
*/
protected function negotiateVariant(EntityInterface $entity): string {
// Match the first variant condition found.
foreach ($this->variantConditions as $variant_condition) {
assert($variant_condition instanceof ContextAwareInterface);
$variant_condition->setContext('entity', $entity);
assert($variant_condition instanceof VariantConditionInterface);
if ($variant_condition->evaluate()) {
// Only use it if the variant is also a wrapperClass.
$variant = $variant_condition->variant();
if (class_exists($variant) && is_subclass_of($variant, $this->wrapperClass)) {
// Return early to avoid evaluating more conditions.
return $variant;
}
}
}
// If none matches use the wrapper class.
return $this->wrapperClass;
}
/**
* {@inheritdoc}
*/
......
<?php
namespace Drupal\typed_entity\WrappedEntityVariants;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\typed_entity\InvalidValueException;
interface ContextAwareInterface {
public function getContext(string $name);
public function setContext(string $name, $data): void;
/**
* Validates the context.
*
* @throws \Drupal\typed_entity\InvalidValueException
*/
public function validateContext(): void;
}
<?php
namespace Drupal\typed_entity\WrappedEntityVariants;
trait ContextAwareTrait {
protected $contexts = [];
public function getContext(string $name) {
return $this->contexts[$name] ?? NULL;
}
public function setContext(string $name, $data): void {
$this->contexts[$name] = $data;
}
}
<?php
namespace Drupal\typed_entity\WrappedEntityVariants;
use Drupal\Core\Entity\EntityFieldManager;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\typed_entity\InvalidValueException;
class FieldValueVariantCondition implements VariantConditionInterface, ContextAwareInterface {
use ContextAwareTrait;
use StringTranslationTrait;
protected $isNegated = FALSE;
protected $fieldName = '';
protected $value = NULL;
protected $variant;
/**
* FieldValueVariantCondition constructor.
*
* @param bool $is_negated
* @param string $field_name
* @param null $value
* @param $variant
*/
public function __construct(string $field_name, $value, $variant, bool $is_negated = FALSE) {
$this->isNegated = $is_negated;
$this->fieldName = $field_name;
$this->value = $value;
$this->variant = $variant;
}
public function isNegated(): bool {
return $this->isNegated;
}
public function evaluate(): bool {
$this->validateContext();
$entity = $this->getContext('entity');
assert($entity instanceof FieldableEntityInterface);
// Check if the any of the values for the field match the configured value.
$values = $entity->get($this->fieldName)->getValue();
// TODO: explore the field configuration to learn about the main property.
$field_manager = \Drupal::service('entity_field.manager');
assert($field_manager instanceof EntityFieldManager);
$definition = $field_manager->getFieldStorageDefinitions($entity->getEntityTypeId())[$this->fieldName];
assert($definition instanceof FieldStorageDefinitionInterface);
$main_property = $definition->getMainPropertyName();
$result = array_reduce($values, function ($carry, $value) use ($main_property) {
return $carry || ($value[$main_property] ?? NULL) == $this->value;
}, FALSE);
return $this->isNegated() ? !$result : $result;
}
public function summary(): string {
return $this->t('Active when the %field is %value.', [
'%field' => $this->fieldName,
'%value' => $this->value,
]);
}
public function variant(): string {
return $this->variant;
}
/**
* {@inheritdoc}
*/
public function validateContext(): void {
$entity = $this->getContext('entity');
if (!$entity instanceof FieldableEntityInterface) {
throw new InvalidValueException('The context for the entity was not fulfilled');
}
if (!$entity->hasField($this->fieldName)) {
$message = sprintf(
'The entity type "%s" with bundle "%s" does not have a field by name "%s".',
$entity->getEntityTypeId(),
$entity->bundle(),
$this->fieldName
);
throw new InvalidValueException($message);
}
}
}
<?php
namespace Drupal\typed_entity\WrappedEntityVariants;
interface VariantConditionInterface {
public function isNegated(): bool;
public function evaluate(): bool;
public function summary(): string;
public function variant(): string;
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment