mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
Compare commits
2 Commits
ad34ec779d
...
0a971d5f87
Author | SHA1 | Date | |
---|---|---|---|
|
0a971d5f87 | ||
|
7d5e854ae8 |
@ -17,7 +17,8 @@ use SilverStripe\Model\ArrayData;
|
||||
use SilverStripe\View\SSViewer;
|
||||
use LogicException;
|
||||
use SilverStripe\Control\HTTPResponse_Exception;
|
||||
use SilverStripe\View\SSViewer_FromString;
|
||||
use SilverStripe\View\SSTemplateEngine;
|
||||
use SilverStripe\View\ViewLayerData;
|
||||
|
||||
/**
|
||||
* This class is is responsible for adding objects to another object's has_many
|
||||
@ -284,12 +285,15 @@ class GridFieldAddExistingAutocompleter extends AbstractGridFieldComponent imple
|
||||
$json = [];
|
||||
Config::nest();
|
||||
SSViewer::config()->set('source_file_comments', false);
|
||||
$viewer = SSViewer_FromString::create($this->resultsFormat);
|
||||
|
||||
$engine = new SSTemplateEngine();
|
||||
foreach ($results as $result) {
|
||||
if (!$result->canView()) {
|
||||
continue;
|
||||
}
|
||||
$title = Convert::html2raw($viewer->process($result, cache: false));
|
||||
$title = Convert::html2raw(
|
||||
$engine->renderString($this->resultsFormat, ViewLayerData::create($result), cache: false)
|
||||
);
|
||||
$json[] = [
|
||||
'label' => $title,
|
||||
'value' => $title,
|
||||
|
@ -18,7 +18,6 @@ use stdClass;
|
||||
*/
|
||||
class ArrayData extends ModelData
|
||||
{
|
||||
|
||||
/**
|
||||
* @var array
|
||||
* @see ArrayData::_construct()
|
||||
@ -87,6 +86,7 @@ class ArrayData extends ModelData
|
||||
*/
|
||||
public function setField(string $fieldName, mixed $value): static
|
||||
{
|
||||
$this->objCacheClear();
|
||||
$this->array[$fieldName] = $value;
|
||||
return $this;
|
||||
}
|
||||
@ -102,6 +102,11 @@ class ArrayData extends ModelData
|
||||
return isset($this->array[$fieldName]);
|
||||
}
|
||||
|
||||
public function exists(): bool
|
||||
{
|
||||
return !empty($this->array);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an associative array to a simple object
|
||||
*
|
||||
|
@ -206,6 +206,7 @@ class ModelData implements Stringable
|
||||
|
||||
public function setDynamicData(string $field, mixed $value): static
|
||||
{
|
||||
$this->objCacheClear();
|
||||
$this->dynamicData[$field] = $value;
|
||||
return $this;
|
||||
}
|
||||
@ -415,10 +416,8 @@ class ModelData implements Stringable
|
||||
|
||||
/**
|
||||
* Clear object cache
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function objCacheClear()
|
||||
public function objCacheClear(): static
|
||||
{
|
||||
$this->objCache = [];
|
||||
return $this;
|
||||
|
@ -1934,6 +1934,7 @@ class DataObject extends ModelData implements DataObjectInterface, i18nEntityPro
|
||||
string $eagerLoadRelation,
|
||||
EagerLoadedList|DataObject $eagerLoadedData
|
||||
): void {
|
||||
$this->objCacheClear();
|
||||
$this->eagerLoadedData[$eagerLoadRelation] = $eagerLoadedData;
|
||||
}
|
||||
|
||||
|
@ -21,11 +21,11 @@ class CastingService
|
||||
/**
|
||||
* Cast a value to the relevant object (usually a DBField instance) for use in the view layer.
|
||||
*
|
||||
* @param null|array|ModelData $source Where the data originates from. This is used both to check for casting helpers
|
||||
* @param null|array|object $source Where the data originates from. This is used both to check for casting helpers
|
||||
* and to help set the value in cast DBField instances.
|
||||
* @param bool $strict If true, an object will be returned even if $data is null.
|
||||
*/
|
||||
public function cast(mixed $data, null|array|ModelData $source = null, string $fieldName = '', bool $strict = false): ?object
|
||||
public function cast(mixed $data, null|array|object $source = null, string $fieldName = '', bool $strict = false): ?object
|
||||
{
|
||||
// null is null - we shouldn't cast it to an object, because that makes it harder
|
||||
// for downstream checks to know there's "no value".
|
||||
@ -42,6 +42,10 @@ class CastingService
|
||||
$service = null;
|
||||
if ($source instanceof ModelData) {
|
||||
$service = $source->castingHelper($fieldName);
|
||||
} elseif (is_object($source)) {
|
||||
// $source is passed into setValue for the DBField instances, but those don't accept
|
||||
// objects that aren't ModelData
|
||||
$source = null;
|
||||
}
|
||||
|
||||
// Cast to object if there's an explicit casting for this field
|
||||
|
@ -5,6 +5,7 @@ namespace SilverStripe\View;
|
||||
use InvalidArgumentException;
|
||||
use Psr\SimpleCache\CacheInterface;
|
||||
use SilverStripe\Control\Director;
|
||||
use SilverStripe\Core\Config\Configurable;
|
||||
use SilverStripe\Core\Flushable;
|
||||
use SilverStripe\Core\Injector\Injectable;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
@ -32,6 +33,12 @@ use SilverStripe\View\Exception\MissingTemplateException;
|
||||
class SSTemplateEngine implements TemplateEngine, Flushable
|
||||
{
|
||||
use Injectable;
|
||||
use Configurable;
|
||||
|
||||
/**
|
||||
* Default prepended cache key for partial caching
|
||||
*/
|
||||
private static string $global_key = '$CurrentReadingMode, $CurrentUser.ID';
|
||||
|
||||
/**
|
||||
* List of models being processed
|
||||
|
@ -781,7 +781,7 @@ class SSTemplateParser extends Parser implements TemplateParser
|
||||
// the passed cache key, the block index, and the sha hash of the template.
|
||||
$res['php'] .= '$keyExpression = function() use ($scope, $cache) {' . PHP_EOL;
|
||||
$res['php'] .= '$val = \'\';' . PHP_EOL;
|
||||
if ($globalKey = SSViewer::config()->get('global_key')) {
|
||||
if ($globalKey = SSTemplateEngine::config()->get('global_key')) {
|
||||
// Embed the code necessary to evaluate the globalKey directly into the template,
|
||||
// so that SSTemplateParser only needs to be called during template regeneration.
|
||||
// Warning: If the global key is changed, it's necessary to flush the template cache.
|
||||
|
@ -3430,7 +3430,7 @@ class SSTemplateParser extends Parser implements TemplateParser
|
||||
// the passed cache key, the block index, and the sha hash of the template.
|
||||
$res['php'] .= '$keyExpression = function() use ($scope, $cache) {' . PHP_EOL;
|
||||
$res['php'] .= '$val = \'\';' . PHP_EOL;
|
||||
if ($globalKey = SSViewer::config()->get('global_key')) {
|
||||
if ($globalKey = SSTemplateEngine::config()->get('global_key')) {
|
||||
// Embed the code necessary to evaluate the globalKey directly into the template,
|
||||
// so that SSTemplateParser only needs to be called during template regeneration.
|
||||
// Warning: If the global key is changed, it's necessary to flush the template cache.
|
||||
|
@ -48,11 +48,6 @@ class SSViewer
|
||||
*/
|
||||
private static bool $theme_enabled = true;
|
||||
|
||||
/**
|
||||
* Default prepended cache key for partial caching
|
||||
*/
|
||||
private static string $global_key = '$CurrentReadingMode, $CurrentUser.ID';
|
||||
|
||||
/**
|
||||
* If true, rendered templates will include comments indicating which template file was used.
|
||||
* May not be supported for some rendering engines.
|
||||
@ -71,14 +66,18 @@ class SSViewer
|
||||
|
||||
/**
|
||||
* Overridden value of rewrite_hash_links config
|
||||
*
|
||||
* Can be set to "php" to rewrite hash links with PHP executable code.
|
||||
*/
|
||||
protected static ?bool $current_rewrite_hash_links = null;
|
||||
protected static null|bool|string $current_rewrite_hash_links = null;
|
||||
|
||||
/**
|
||||
* Instance variable to disable rewrite_hash_links (overrides global default)
|
||||
* Leave null to use global state.
|
||||
*
|
||||
* Can be set to "php" to rewrite hash links with PHP executable code.
|
||||
*/
|
||||
protected ?bool $rewriteHashlinks = null;
|
||||
protected null|bool|string $rewriteHashlinks = null;
|
||||
|
||||
/**
|
||||
* Determines whether resources from the Requirements API are included in a processed result.
|
||||
@ -250,10 +249,8 @@ class SSViewer
|
||||
|
||||
/**
|
||||
* Check if rewrite hash links are enabled on this instance
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getRewriteHashLinks()
|
||||
public function getRewriteHashLinks(): null|bool|string
|
||||
{
|
||||
if ($this->rewriteHashlinks !== null) {
|
||||
return $this->rewriteHashlinks;
|
||||
@ -263,11 +260,8 @@ class SSViewer
|
||||
|
||||
/**
|
||||
* Set if hash links are rewritten for this instance
|
||||
*
|
||||
* @param bool $rewrite
|
||||
* @return $this
|
||||
*/
|
||||
public function setRewriteHashLinks($rewrite)
|
||||
public function setRewriteHashLinks(null|bool|string $rewrite): static
|
||||
{
|
||||
$this->rewriteHashlinks = $rewrite;
|
||||
return $this;
|
||||
@ -275,10 +269,8 @@ class SSViewer
|
||||
|
||||
/**
|
||||
* Get default value for rewrite hash links for all modules
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function getRewriteHashLinksDefault()
|
||||
public static function getRewriteHashLinksDefault(): null|bool|string
|
||||
{
|
||||
// Check if config overridden
|
||||
if (static::$current_rewrite_hash_links !== null) {
|
||||
@ -289,10 +281,8 @@ class SSViewer
|
||||
|
||||
/**
|
||||
* Set default rewrite hash links
|
||||
*
|
||||
* @param bool $rewrite
|
||||
*/
|
||||
public static function setRewriteHashLinksDefault($rewrite)
|
||||
public static function setRewriteHashLinksDefault(null|bool|string $rewrite)
|
||||
{
|
||||
static::$current_rewrite_hash_links = $rewrite;
|
||||
}
|
||||
@ -369,39 +359,6 @@ PHP;
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the evaluated string, passing it the given data.
|
||||
* Used by partial caching to evaluate custom cache keys expressed using
|
||||
* template expressions
|
||||
*
|
||||
* @param string $content Input string
|
||||
* @param mixed $data Data context
|
||||
* @param array $arguments Additional arguments
|
||||
* @param bool $globalRequirements
|
||||
*
|
||||
* @return string Evaluated result
|
||||
*/
|
||||
public static function execute_string($content, $data, $arguments = [], $globalRequirements = false)
|
||||
{
|
||||
// @TODO come back to this. Probably delete it but keeping for now due to $globalRequirements
|
||||
$v = SSViewer_FromString::create($content);
|
||||
|
||||
if ($globalRequirements) {
|
||||
$v->includeRequirements(false);
|
||||
} else {
|
||||
//nest a requirements backend for our template rendering
|
||||
$origBackend = Requirements::backend();
|
||||
Requirements::set_backend(Requirements_Backend::create());
|
||||
}
|
||||
try {
|
||||
return $v->process($data, $arguments);
|
||||
} finally {
|
||||
if (!$globalRequirements) {
|
||||
Requirements::set_backend($origBackend);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an appropriate base tag for the given template.
|
||||
* It will be closed on an XHTML document, and unclosed on an HTML document.
|
||||
|
@ -1,45 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\View;
|
||||
|
||||
use SilverStripe\Core\Config\Config;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\ORM\FieldType\DBField;
|
||||
use SilverStripe\ORM\FieldType\DBHTMLText;
|
||||
|
||||
/**
|
||||
* Special SSViewer that will process a template passed as a string, rather than a filename.
|
||||
*/
|
||||
class SSViewer_FromString extends SSViewer
|
||||
{
|
||||
/**
|
||||
* The template to use
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $content;
|
||||
|
||||
/**
|
||||
* @param string $content
|
||||
* @param TemplateParser $parser
|
||||
*/
|
||||
public function __construct(string $content, ?TemplateEngine $templateEngine = null)
|
||||
{
|
||||
$this->content = $content;
|
||||
if (!$templateEngine) {
|
||||
$templateEngine = Injector::inst()->create(TemplateEngine::class);
|
||||
}
|
||||
$this->setTemplateEngine($templateEngine);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function process(mixed $item, array $overlay = [], bool $cache = true): DBHTMLText
|
||||
{
|
||||
$item = ViewLayerData::create($item);
|
||||
$output = $this->getTemplateEngine()->renderString($this->content, $item, $overlay, $cache);
|
||||
$html = DBField::create_field('HTMLFragment', $output);
|
||||
return $html;
|
||||
}
|
||||
}
|
@ -2,14 +2,11 @@
|
||||
|
||||
namespace SilverStripe\View;
|
||||
|
||||
use ArrayIterator;
|
||||
use Countable;
|
||||
use InvalidArgumentException;
|
||||
use Iterator;
|
||||
use LogicException;
|
||||
use SilverStripe\Core\ClassInfo;
|
||||
use SilverStripe\Core\Injector\Injector;
|
||||
use SilverStripe\ORM\FieldType\DBField;
|
||||
|
||||
/**
|
||||
* This tracks the current scope for an SSViewer instance. It has three goals:
|
||||
@ -47,68 +44,50 @@ class SSViewer_Scope
|
||||
/**
|
||||
* The stack of previous items ("scopes") - an indexed array of: item, item iterator, item iterator total,
|
||||
* pop index, up index, current index & parent overlay
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $itemStack = [];
|
||||
private array $itemStack = [];
|
||||
|
||||
/**
|
||||
* The current "global" item (the one any lookup starts from)
|
||||
*
|
||||
* @var object
|
||||
*/
|
||||
protected $item;
|
||||
protected ?ViewLayerData $item;
|
||||
|
||||
/**
|
||||
* If we're looping over the current "global" item, here's the iterator that tracks with item we're up to
|
||||
*
|
||||
* @var Iterator
|
||||
*/
|
||||
protected $itemIterator;
|
||||
protected ?Iterator $itemIterator;
|
||||
|
||||
/**
|
||||
* Total number of items in the iterator
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $itemIteratorTotal;
|
||||
protected int $itemIteratorTotal;
|
||||
|
||||
/**
|
||||
* A pointer into the item stack for the item that will become the active scope on the next pop call
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $popIndex;
|
||||
private ?int $popIndex;
|
||||
|
||||
/**
|
||||
* A pointer into the item stack for which item is "up" from this one
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $upIndex;
|
||||
private ?int $upIndex;
|
||||
|
||||
/**
|
||||
* A pointer into the item stack for which the active item (or null if not in stack yet)
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $currentIndex;
|
||||
private int $currentIndex;
|
||||
|
||||
/**
|
||||
* A store of copies of the main item stack, so it's preserved during a lookup from local scope
|
||||
* (which may push/pop items to/from the main item stack)
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $localStack = [];
|
||||
private array $localStack = [];
|
||||
|
||||
/**
|
||||
* The index of the current item in the main item stack, so we know where to restore the scope
|
||||
* stored in $localStack.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $localIndex = 0;
|
||||
private int $localIndex = 0;
|
||||
|
||||
/**
|
||||
* List of global property providers
|
||||
@ -128,35 +107,24 @@ class SSViewer_Scope
|
||||
|
||||
/**
|
||||
* Overlay variables. Take precedence over anything from the current scope
|
||||
*
|
||||
* @var array|null
|
||||
*/
|
||||
protected $overlay;
|
||||
protected array $overlay;
|
||||
|
||||
/**
|
||||
* Flag for whether overlay should be preserved when pushing a new scope
|
||||
*
|
||||
* @see SSViewer_Scope::pushScope()
|
||||
* @var bool
|
||||
*/
|
||||
protected $preserveOverlay = false;
|
||||
protected bool $preserveOverlay = false;
|
||||
|
||||
/**
|
||||
* Underlay variables. Concede precedence to overlay variables or anything from the current scope
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $underlay;
|
||||
protected array $underlay;
|
||||
|
||||
/**
|
||||
* @var object $item
|
||||
* @var SSViewer_Scope $inheritedScope
|
||||
*/
|
||||
public function __construct(
|
||||
$item,
|
||||
array $overlay = null,
|
||||
array $underlay = null,
|
||||
SSViewer_Scope $inheritedScope = null
|
||||
?ViewLayerData $item,
|
||||
array $overlay = [],
|
||||
array $underlay = [],
|
||||
?SSViewer_Scope $inheritedScope = null
|
||||
) {
|
||||
$this->item = $item;
|
||||
|
||||
@ -164,8 +132,8 @@ class SSViewer_Scope
|
||||
$this->itemIteratorTotal = ($inheritedScope) ? $inheritedScope->itemIteratorTotal : 0;
|
||||
$this->itemStack[] = [$this->item, $this->itemIterator, $this->itemIteratorTotal, null, null, 0];
|
||||
|
||||
$this->overlay = $overlay ?: [];
|
||||
$this->underlay = $underlay ?: [];
|
||||
$this->overlay = $overlay;
|
||||
$this->underlay = $underlay;
|
||||
|
||||
$this->cacheGlobalProperties();
|
||||
$this->cacheIteratorProperties();
|
||||
@ -351,29 +319,18 @@ class SSViewer_Scope
|
||||
|
||||
if (!$this->itemIterator) {
|
||||
// Note: it is important that getIterator() is called before count() as implemenations may rely on
|
||||
// this to efficiency get both the number of records and an iterator (e.g. DataList does this)
|
||||
// this to efficiently get both the number of records and an iterator (e.g. DataList does this)
|
||||
$this->itemIterator = $this->item->getIterator();
|
||||
|
||||
// Item may be an array or a regular IteratorAggregate
|
||||
if (is_array($this->item)) {
|
||||
$this->itemIterator = new ArrayIterator($this->item);
|
||||
} elseif ($this->item instanceof Iterator) {
|
||||
$this->itemIterator = $this->item;
|
||||
} else {
|
||||
$this->itemIterator = $this->item->getIterator();
|
||||
// This will execute code in a generator up to the first yield. For example, this ensures that
|
||||
// DataList::getIterator() is called before Datalist::count() which means we only run the query once
|
||||
// instead of running a separate explicit count() query
|
||||
$this->itemIterator->rewind();
|
||||
|
||||
// This will execute code in a generator up to the first yield. For example, this ensures that
|
||||
// DataList::getIterator() is called before Datalist::count()
|
||||
$this->itemIterator->rewind();
|
||||
}
|
||||
|
||||
// If the item implements Countable, use that to fetch the count, otherwise we have to inspect the
|
||||
// iterator and then rewind it.
|
||||
if ($this->item instanceof Countable) {
|
||||
$this->itemIteratorTotal = count($this->item);
|
||||
} else {
|
||||
$this->itemIteratorTotal = iterator_count($this->itemIterator);
|
||||
$this->itemIterator->rewind();
|
||||
}
|
||||
// Get the number of items in the iterator.
|
||||
// Don't just use iterator_count because that results in running through the list
|
||||
// which causes some iterators to no longer be iterable for some reason
|
||||
$this->itemIteratorTotal = $this->item->getIteratorCount();
|
||||
|
||||
$this->itemStack[$this->localIndex][SSViewer_Scope::ITEM_ITERATOR] = $this->itemIterator;
|
||||
$this->itemStack[$this->localIndex][SSViewer_Scope::ITEM_ITERATOR_TOTAL] = $this->itemIteratorTotal;
|
||||
@ -412,7 +369,7 @@ class SSViewer_Scope
|
||||
} else {
|
||||
$on = $this->getCurrentItem();
|
||||
if ($on && isset($on->$name)) {
|
||||
$retval = $on->getRawDataValue($name, $type, $arguments);
|
||||
$retval = $on->getRawDataValue($name, $arguments, $type);
|
||||
}
|
||||
|
||||
if ($retval === null) {
|
||||
@ -431,7 +388,7 @@ class SSViewer_Scope
|
||||
/**
|
||||
* Check if the current item in scope has a value for the named field.
|
||||
*/
|
||||
public function hasValue(string $name, array $arguments): bool
|
||||
public function hasValue(string $name, array $arguments, string $type): bool
|
||||
{
|
||||
// @TODO: look for ways to remove the need to call hasValue (e.g. using isset($this->getCurrentItem()->$name) and an equivalent for over/underlays)
|
||||
$retval = null;
|
||||
@ -443,7 +400,7 @@ class SSViewer_Scope
|
||||
if ($retval === null) {
|
||||
$on = $this->getCurrentItem();
|
||||
if ($on) {
|
||||
$retval = $on->hasDataValue($name, $arguments);
|
||||
$retval = $on->hasDataValue($name, $arguments, $type);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,6 @@
|
||||
namespace SilverStripe\View;
|
||||
|
||||
use BadMethodCallException;
|
||||
use Countable;
|
||||
use InvalidArgumentException;
|
||||
use IteratorAggregate;
|
||||
use SilverStripe\Core\ClassInfo;
|
||||
@ -13,7 +12,7 @@ use SilverStripe\Model\ModelDataCustomised;
|
||||
use Stringable;
|
||||
use Traversable;
|
||||
|
||||
class ViewLayerData implements IteratorAggregate, Stringable, Countable
|
||||
class ViewLayerData implements IteratorAggregate, Stringable
|
||||
{
|
||||
use Injectable;
|
||||
|
||||
@ -40,19 +39,18 @@ class ViewLayerData implements IteratorAggregate, Stringable, Countable
|
||||
|
||||
/**
|
||||
* Needed so we can rewind in SSViewer_Scope::next() after getting itemIteratorTotal without throwing an exception.
|
||||
* @TODO see if we can remove the need for this
|
||||
*/
|
||||
public function count(): int
|
||||
public function getIteratorCount(): int
|
||||
{
|
||||
$count = $this->getRawDataValue('count');
|
||||
if ($count) {
|
||||
if (is_numeric($count)) {
|
||||
return $count;
|
||||
}
|
||||
if (is_countable($this->data)) {
|
||||
return count($this->data);
|
||||
}
|
||||
if (ClassInfo::hasMethod($this->data, 'getIterator')) {
|
||||
return count($this->data->getIterator());
|
||||
return iterator_count($this->data->getIterator());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@ -90,7 +88,7 @@ class ViewLayerData implements IteratorAggregate, Stringable, Countable
|
||||
|
||||
public function __get(string $name): ?ViewLayerData
|
||||
{
|
||||
$value = $this->getRawDataValue($name, ViewLayerData::TYPE_PROPERTY);
|
||||
$value = $this->getRawDataValue($name, type: ViewLayerData::TYPE_PROPERTY);
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
@ -100,7 +98,7 @@ class ViewLayerData implements IteratorAggregate, Stringable, Countable
|
||||
|
||||
public function __call(string $name, array $arguments = []): ?ViewLayerData
|
||||
{
|
||||
$value = $this->getRawDataValue($name, ViewLayerData::TYPE_METHOD, $arguments);
|
||||
$value = $this->getRawDataValue($name, $arguments, ViewLayerData::TYPE_METHOD);
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
@ -119,23 +117,38 @@ class ViewLayerData implements IteratorAggregate, Stringable, Countable
|
||||
/**
|
||||
* Check if there is a truthy value or (for ModelData) if the data exists().
|
||||
*/
|
||||
public function hasDataValue(?string $name = null, array $arguments = []): bool
|
||||
public function hasDataValue(?string $name = null, array $arguments = [], string $type = ViewLayerData::TYPE_ANY): bool
|
||||
{
|
||||
if ($name) {
|
||||
// Ask the model if it has a value for that field
|
||||
if ($this->data instanceof ModelData) {
|
||||
return $this->data->hasValue($name, $arguments);
|
||||
}
|
||||
return ViewLayerData::create($this->getRawDataValue($name, arguments: $arguments))->hasDataValue();
|
||||
// Check for ourselves if there's a value for that field
|
||||
// This mimics what ModelData does, which provides consistency
|
||||
$value = $this->getRawDataValue($name, $arguments, $type);
|
||||
if ($value === null) {
|
||||
return false;
|
||||
}
|
||||
return ViewLayerData::create($value, $this->data, $name)->hasDataValue();
|
||||
}
|
||||
// Ask the model if it "exists"
|
||||
if ($this->data instanceof ModelData) {
|
||||
return $this->data->exists();
|
||||
}
|
||||
// Mimics ModelData checks on lists
|
||||
if (is_countable($this->data)) {
|
||||
return count($this->data) > 0;
|
||||
}
|
||||
// Check for truthiness (which is effectively `return true` since data is an object)
|
||||
// We do this to mimic ModelData->hasValue() for consistency
|
||||
return (bool) $this->data;
|
||||
}
|
||||
|
||||
// @TODO We need this publicly right now for the ss template engine method args, but need to check if
|
||||
// we can rely on it, since twig won't be calling this at all
|
||||
public function getRawDataValue(string $name, string $type = ViewLayerData::TYPE_ANY, array $arguments = []): mixed
|
||||
/**
|
||||
* Get the raw value of some field/property/method on the data, without wrapping it in ViewLayerData.
|
||||
*/
|
||||
public function getRawDataValue(string $name, array $arguments = [], string $type = ViewLayerData::TYPE_ANY): mixed
|
||||
{
|
||||
if ($type !== ViewLayerData::TYPE_ANY && $type !== ViewLayerData::TYPE_METHOD && $type !== ViewLayerData::TYPE_PROPERTY) {
|
||||
throw new InvalidArgumentException('$type must be one of the TYPE_* constant values');
|
||||
|
@ -19,6 +19,8 @@ use SilverStripe\Dev\FunctionalTest;
|
||||
use SilverStripe\Security\Member;
|
||||
use SilverStripe\View\SSViewer;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use SilverStripe\Control\Tests\ControllerTest\ControllerWithDummyEngine;
|
||||
use SilverStripe\Control\Tests\ControllerTest\DummyTemplateEngine;
|
||||
|
||||
class ControllerTest extends FunctionalTest
|
||||
{
|
||||
@ -858,4 +860,12 @@ class ControllerTest extends FunctionalTest
|
||||
$response = $this->post('HTTPMethodTestController', ['dummy' => 'example']);
|
||||
$this->assertEquals('Routed to postLegacyRoot', $response->getBody());
|
||||
}
|
||||
|
||||
public function testTemplateEngineUsed()
|
||||
{
|
||||
$controller = new ControllerWithDummyEngine();
|
||||
$this->assertSame('This is my controller', $controller->render()->getValue());
|
||||
$this->assertSame('This is my controller', $controller->renderWith('literally-any-template')->getValue());
|
||||
$this->assertInstanceOf(DummyTemplateEngine::class, $controller->getViewer('')->getTemplateEngine());
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Control\Tests\ControllerTest;
|
||||
|
||||
use SilverStripe\Control\Controller;
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
use SilverStripe\View\TemplateEngine;
|
||||
|
||||
class ControllerWithDummyEngine extends Controller implements TestOnly
|
||||
{
|
||||
protected function getTemplateEngine(): TemplateEngine
|
||||
{
|
||||
return new DummyTemplateEngine();
|
||||
}
|
||||
}
|
40
tests/php/Control/ControllerTest/DummyTemplateEngine.php
Normal file
40
tests/php/Control/ControllerTest/DummyTemplateEngine.php
Normal file
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\Control\Tests\ControllerTest;
|
||||
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
use SilverStripe\View\TemplateEngine;
|
||||
use SilverStripe\View\ViewLayerData;
|
||||
|
||||
/**
|
||||
* A dummy template renderer that doesn't actually render any templates.
|
||||
*/
|
||||
class DummyTemplateEngine implements TemplateEngine, TestOnly
|
||||
{
|
||||
private string $output = 'This is my controller';
|
||||
|
||||
public function __construct(string|array $templateCandidates = [])
|
||||
{
|
||||
// no-op
|
||||
}
|
||||
|
||||
public function setTemplate(string|array $templateCandidates): static
|
||||
{
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function hasTemplate(string|array $templateCandidates): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function renderString(string $template, ViewLayerData $model, array $overlay = [], bool $cache = true): string
|
||||
{
|
||||
return $this->output;
|
||||
}
|
||||
|
||||
public function render(ViewLayerData $model, array $overlay = []): string
|
||||
{
|
||||
return $this->output;
|
||||
}
|
||||
}
|
206
tests/php/View/CastingServiceTest.php
Normal file
206
tests/php/View/CastingServiceTest.php
Normal file
@ -0,0 +1,206 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\View\Tests;
|
||||
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\Model\ArrayData;
|
||||
use SilverStripe\Model\List\ArrayList;
|
||||
use SilverStripe\ORM\FieldType\DBBoolean;
|
||||
use SilverStripe\ORM\FieldType\DBCurrency;
|
||||
use SilverStripe\ORM\FieldType\DBDate;
|
||||
use SilverStripe\ORM\FieldType\DBField;
|
||||
use SilverStripe\ORM\FieldType\DBFloat;
|
||||
use SilverStripe\ORM\FieldType\DBHTMLText;
|
||||
use SilverStripe\ORM\FieldType\DBInt;
|
||||
use SilverStripe\ORM\FieldType\DBText;
|
||||
use SilverStripe\ORM\FieldType\DBTime;
|
||||
use SilverStripe\View\CastingService;
|
||||
use SilverStripe\View\Tests\CastingServiceTest\TestDataObject;
|
||||
use stdClass;
|
||||
|
||||
class CastingServiceTest extends SapphireTest
|
||||
{
|
||||
// protected static $extra_dataobjects = [
|
||||
// TestDataObject::class,
|
||||
// ];
|
||||
|
||||
protected $usesDatabase = false;
|
||||
|
||||
public static function provideCast(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
'data' => null,
|
||||
'source' => null,
|
||||
'fieldName' => '',
|
||||
'expected' => null,
|
||||
],
|
||||
[
|
||||
'data' => new stdClass(),
|
||||
'source' => null,
|
||||
'fieldName' => '',
|
||||
'expected' => stdClass::class,
|
||||
],
|
||||
[
|
||||
'data' => new stdClass(),
|
||||
'source' => TestDataObject::class,
|
||||
'fieldName' => 'DateField',
|
||||
'expected' => stdClass::class,
|
||||
],
|
||||
[
|
||||
'data' => new DBText(),
|
||||
'source' => TestDataObject::class,
|
||||
'fieldName' => 'DateField',
|
||||
'expected' => stdClass::class,
|
||||
],
|
||||
[
|
||||
'data' => '2024-10-10',
|
||||
'source' => TestDataObject::class,
|
||||
'fieldName' => 'DateField',
|
||||
'expected' => DBDate::class,
|
||||
],
|
||||
[
|
||||
'data' => 'some value',
|
||||
'source' => TestDataObject::class,
|
||||
'fieldName' => 'HtmlField',
|
||||
'expected' => DBHTMLText::class,
|
||||
],
|
||||
[
|
||||
'data' => '12.35',
|
||||
'source' => TestDataObject::class,
|
||||
'fieldName' => 'OverrideCastingHelper',
|
||||
'expected' => DBCurrency::class,
|
||||
],
|
||||
[
|
||||
'data' => '10:17:36',
|
||||
'source' => TestDataObject::class,
|
||||
'fieldName' => 'TimeField',
|
||||
'expected' => DBTime::class,
|
||||
],
|
||||
[
|
||||
'data' => 123456,
|
||||
'source' => TestDataObject::class,
|
||||
'fieldName' => 'RandomField',
|
||||
'expected' => DBInt::class,
|
||||
],
|
||||
[
|
||||
'data' => '<body>some text</body>',
|
||||
'source' => TestDataObject::class,
|
||||
'fieldName' => 'RandomField',
|
||||
'expected' => DBText::class,
|
||||
],
|
||||
[
|
||||
'data' => '12.35',
|
||||
'source' => null,
|
||||
'fieldName' => 'OverrideCastingHelper',
|
||||
'expected' => DBText::class,
|
||||
],
|
||||
[
|
||||
'data' => 123456,
|
||||
'source' => null,
|
||||
'fieldName' => 'RandomField',
|
||||
'expected' => DBInt::class,
|
||||
],
|
||||
[
|
||||
'data' => '10:17:36',
|
||||
'source' => null,
|
||||
'fieldName' => 'TimeField',
|
||||
'expected' => DBText::class,
|
||||
],
|
||||
[
|
||||
'data' => '<body>some text</body>',
|
||||
'source' => null,
|
||||
'fieldName' => '',
|
||||
'expected' => DBText::class,
|
||||
],
|
||||
[
|
||||
'data' => true,
|
||||
'source' => null,
|
||||
'fieldName' => '',
|
||||
'expected' => DBBoolean::class,
|
||||
],
|
||||
[
|
||||
'data' => false,
|
||||
'source' => null,
|
||||
'fieldName' => '',
|
||||
'expected' => DBBoolean::class,
|
||||
],
|
||||
[
|
||||
'data' => 1.234,
|
||||
'source' => null,
|
||||
'fieldName' => '',
|
||||
'expected' => DBFloat::class,
|
||||
],
|
||||
[
|
||||
'data' => [],
|
||||
'source' => null,
|
||||
'fieldName' => '',
|
||||
'expected' => ArrayList::class,
|
||||
],
|
||||
[
|
||||
'data' => [1,2,3,4],
|
||||
'source' => null,
|
||||
'fieldName' => '',
|
||||
'expected' => ArrayList::class,
|
||||
],
|
||||
[
|
||||
'data' => ['one' => 1, 'two' => 2],
|
||||
'source' => null,
|
||||
'fieldName' => '',
|
||||
'expected' => ArrayData::class,
|
||||
],
|
||||
[
|
||||
'data' => ['one' => 1, 'two' => 2],
|
||||
'source' => TestDataObject::class,
|
||||
'fieldName' => 'AnyField',
|
||||
'expected' => ArrayData::class,
|
||||
],
|
||||
[
|
||||
'data' => ['one' => 1, 'two' => 2],
|
||||
'source' => TestDataObject::class,
|
||||
'fieldName' => 'ArrayAsText',
|
||||
'expected' => DBText::class,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
#[DataProvider('provideCast')]
|
||||
public function testCast(mixed $data, ?string $source, string $fieldName, ?string $expected): void
|
||||
{
|
||||
// Can't instantiate DataObject in a data provider
|
||||
if (is_string($source)) {
|
||||
$source = new $source();
|
||||
}
|
||||
$service = new CastingService();
|
||||
$value = $service->cast($data, $source, $fieldName);
|
||||
|
||||
// Check the cast object is the correct type
|
||||
if ($expected === null) {
|
||||
$this->assertNull($value);
|
||||
} elseif (is_object($data)) {
|
||||
$this->assertSame($data, $value);
|
||||
} else {
|
||||
$this->assertInstanceOf($expected, $value);
|
||||
}
|
||||
|
||||
// Check the value is retained
|
||||
if ($value instanceof DBField && !is_object($data)) {
|
||||
$this->assertSame($data, $value->getValue());
|
||||
}
|
||||
if ($value instanceof ArrayData && !is_object($data)) {
|
||||
$this->assertSame($data, $value->toMap());
|
||||
}
|
||||
if ($value instanceof ArrayList && !is_object($data)) {
|
||||
$this->assertSame($data, $value->toArray());
|
||||
}
|
||||
}
|
||||
|
||||
public function testCastStrict(): void
|
||||
{
|
||||
$service = new CastingService();
|
||||
$value = $service->cast(null, strict: true);
|
||||
$this->assertInstanceOf(DBText::class, $value);
|
||||
$this->assertNull($value->getValue());
|
||||
}
|
||||
}
|
30
tests/php/View/CastingServiceTest/TestDataObject.php
Normal file
30
tests/php/View/CastingServiceTest/TestDataObject.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace SilverStripe\View\Tests\CastingServiceTest;
|
||||
|
||||
use SilverStripe\Dev\TestOnly;
|
||||
use SilverStripe\ORM\DataObject;
|
||||
|
||||
class TestDataObject extends DataObject implements TestOnly
|
||||
{
|
||||
private static string $table_name = 'CastingServiceTest_TestDataObject';
|
||||
|
||||
private static array $db = [
|
||||
'HtmlField' => 'HTMLText',
|
||||
'DateField' => 'Date',
|
||||
];
|
||||
|
||||
private static array $casting = [
|
||||
'DateField' => 'Text', // won't override
|
||||
'TimeField' => 'Time',
|
||||
'ArrayAsText' => 'Text',
|
||||
];
|
||||
|
||||
public function castingHelper(string $field): ?string
|
||||
{
|
||||
if ($field === 'OverrideCastingHelper') {
|
||||
return 'Currency';
|
||||
}
|
||||
return parent::castingHelper($field);
|
||||
}
|
||||
}
|
@ -20,6 +20,8 @@ class SSTemplateEngineFindTemplateTest extends SapphireTest
|
||||
{
|
||||
private string $base;
|
||||
|
||||
private ThemeResourceLoader $origLoader;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
@ -40,6 +42,7 @@ class SSTemplateEngineFindTemplateTest extends SapphireTest
|
||||
$themeManifest->setProject('myproject');
|
||||
$themeManifest->init();
|
||||
// New Loader for that root
|
||||
$this->origLoader = ThemeResourceLoader::inst();
|
||||
$themeResourceLoader = new ThemeResourceLoader($this->base);
|
||||
$themeResourceLoader->addSet('$default', $themeManifest);
|
||||
ThemeResourceLoader::set_instance($themeResourceLoader);
|
||||
@ -50,6 +53,7 @@ class SSTemplateEngineFindTemplateTest extends SapphireTest
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
ThemeResourceLoader::set_instance($this->origLoader);
|
||||
ModuleLoader::inst()->popManifest();
|
||||
parent::tearDown();
|
||||
}
|
||||
|
@ -27,7 +27,6 @@ use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\DoesNotPerformAssertions;
|
||||
use SilverStripe\View\Exception\MissingTemplateException;
|
||||
use SilverStripe\View\SSTemplateEngine;
|
||||
use SilverStripe\View\ThemeResourceLoader;
|
||||
use SilverStripe\View\ViewLayerData;
|
||||
|
||||
class SSTemplateEngineTest extends SapphireTest
|
||||
@ -2076,9 +2075,6 @@ after'
|
||||
$this->assertEquals(1, $count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if caching for SSViewer_FromString is working
|
||||
*/
|
||||
public function testFromStringCaching()
|
||||
{
|
||||
$content = 'Test content';
|
||||
@ -2177,7 +2173,7 @@ after'
|
||||
private function render(string $templateString, mixed $data = null, bool $cacheTemplate = false): string
|
||||
{
|
||||
$engine = new SSTemplateEngine();
|
||||
if (!$data) {
|
||||
if ($data === null) {
|
||||
$data = new SSTemplateEngineTest\TestFixture();
|
||||
}
|
||||
$data = new ViewLayerData($data);
|
||||
|
@ -162,7 +162,6 @@ class SSViewerTest extends SapphireTest
|
||||
<body>
|
||||
</html>'
|
||||
);
|
||||
// Note: SSViewer_FromString doesn't rewrite hash links.
|
||||
$tmpl = new SSViewer([], $engine);
|
||||
$result = $tmpl->process('pretend this is a model');
|
||||
$this->assertStringContainsString(
|
||||
@ -206,7 +205,6 @@ class SSViewerTest extends SapphireTest
|
||||
<body>
|
||||
</html>'
|
||||
);
|
||||
// Note: SSViewer_FromString doesn't rewrite hash links.
|
||||
$tmpl = new SSViewer([], $engine);
|
||||
$result = $tmpl->process('pretend this is a model');
|
||||
|
||||
|
@ -5,10 +5,12 @@ namespace SilverStripe\View\Tests;
|
||||
use ArrayIterator;
|
||||
use BadMethodCallException;
|
||||
use Error;
|
||||
use InvalidArgumentException;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use SilverStripe\Dev\SapphireTest;
|
||||
use SilverStripe\Model\ArrayData;
|
||||
use SilverStripe\Model\List\ArrayList;
|
||||
use SilverStripe\Model\ModelData;
|
||||
use SilverStripe\ORM\FieldType\DBDate;
|
||||
use SilverStripe\ORM\FieldType\DBHTMLText;
|
||||
use SilverStripe\View\Exception\MissingTemplateException;
|
||||
@ -88,7 +90,7 @@ class ViewLayerDataTest extends SapphireTest
|
||||
}
|
||||
}
|
||||
|
||||
public static function provideCount(): array
|
||||
public static function provideGetIteratorCount(): array
|
||||
{
|
||||
return [
|
||||
'uncountable object' => [
|
||||
@ -100,8 +102,8 @@ class ViewLayerDataTest extends SapphireTest
|
||||
'expected' => 12,
|
||||
],
|
||||
'uncountable object - has count field (non-int)' => [
|
||||
'data' => new ArrayData(['count' => 'aahhh', 'Field2' => 'value2']), // @TODO fix this
|
||||
'expected' => 12,
|
||||
'data' => new ArrayData(['count' => 'aahhh', 'Field2' => 'value2']),
|
||||
'expected' => 0,
|
||||
],
|
||||
'empty array' => [
|
||||
'data' => [],
|
||||
@ -130,11 +132,11 @@ class ViewLayerDataTest extends SapphireTest
|
||||
];
|
||||
}
|
||||
|
||||
#[DataProvider('provideCount')]
|
||||
public function testCount(mixed $data, int $expected): void
|
||||
#[DataProvider('provideGetIteratorCount')]
|
||||
public function testGetIteratorCount(mixed $data, int $expected): void
|
||||
{
|
||||
$viewLayerData = new ViewLayerData($data);
|
||||
$this->assertSame($expected, $viewLayerData->count());
|
||||
$this->assertSame($expected, $viewLayerData->getIteratorCount());
|
||||
}
|
||||
|
||||
public static function provideIsSet(): array
|
||||
@ -290,6 +292,8 @@ class ViewLayerDataTest extends SapphireTest
|
||||
|
||||
public static function provideGetComplex(): array
|
||||
{
|
||||
// Note the actual value checks aren't very comprehensive here because that's done
|
||||
// in more detail in testGetRawDataValue
|
||||
return [
|
||||
'exception gets thrown if not __call() method' => [
|
||||
'name' => 'badMethodCall',
|
||||
@ -500,28 +504,225 @@ class ViewLayerDataTest extends SapphireTest
|
||||
$this->assertSame($expected, (string) $viewLayerData);
|
||||
}
|
||||
|
||||
// public function provideHasDataValue(): array
|
||||
// {
|
||||
// return [
|
||||
// [
|
||||
public static function provideHasDataValue(): array
|
||||
{
|
||||
return [
|
||||
'empty array' => [
|
||||
'data' => [],
|
||||
'name' => null,
|
||||
'expected' => false,
|
||||
],
|
||||
'empty ArrayList' => [
|
||||
'data' => new ArrayList(),
|
||||
'name' => null,
|
||||
'expected' => false,
|
||||
],
|
||||
'empty ArrayData' => [
|
||||
'data' => new ArrayData(),
|
||||
'name' => null,
|
||||
'expected' => false,
|
||||
],
|
||||
'empty ArrayIterator' => [
|
||||
'data' => new ArrayIterator(),
|
||||
'name' => null,
|
||||
'expected' => false,
|
||||
],
|
||||
'empty ModelData' => [
|
||||
'data' => new ModelData(),
|
||||
'name' => null,
|
||||
'expected' => true,
|
||||
],
|
||||
'non-countable object' => [
|
||||
'data' => new ExtensibleObject(),
|
||||
'name' => null,
|
||||
'expected' => true,
|
||||
],
|
||||
'array with data' => [
|
||||
'data' => [1,2,3],
|
||||
'name' => null,
|
||||
'expected' => true,
|
||||
],
|
||||
'associative array' => [
|
||||
'data' => ['one' => 1, 'two' => 2],
|
||||
'name' => null,
|
||||
'expected' => true,
|
||||
],
|
||||
'ArrayList with data' => [
|
||||
'data' => new ArrayList([1,2,3]),
|
||||
'name' => null,
|
||||
'expected' => true,
|
||||
],
|
||||
'ArrayData with data' => [
|
||||
'data' => new ArrayData(['one' => 1, 'two' => 2]),
|
||||
'name' => null,
|
||||
'expected' => true,
|
||||
],
|
||||
'ArrayIterator with data' => [
|
||||
'data' => new ArrayIterator([1,2,3]),
|
||||
'name' => null,
|
||||
'expected' => true,
|
||||
],
|
||||
'ArrayData missing value' => [
|
||||
'data' => new ArrayData(['one' => 1, 'two' => 2]),
|
||||
'name' => 'three',
|
||||
'expected' => false,
|
||||
],
|
||||
'ArrayData with truthy value' => [
|
||||
'data' => new ArrayData(['one' => 1, 'two' => 2]),
|
||||
'name' => 'one',
|
||||
'expected' => true,
|
||||
],
|
||||
'ArrayData with null value' => [
|
||||
'data' => new ArrayData(['nullVal' => null, 'two' => 2]),
|
||||
'name' => 'nullVal',
|
||||
'expected' => false,
|
||||
],
|
||||
'ArrayData with falsy value' => [
|
||||
'data' => new ArrayData(['zero' => 0, 'two' => 2]),
|
||||
'name' => 'zero',
|
||||
'expected' => false,
|
||||
],
|
||||
'Empty string' => [
|
||||
'data' => '',
|
||||
'name' => null,
|
||||
'expected' => false,
|
||||
],
|
||||
'Truthy string' => [
|
||||
'data' => 'has a value',
|
||||
'name' => null,
|
||||
'expected' => true,
|
||||
],
|
||||
'Field on a string' => [
|
||||
'data' => 'has a value',
|
||||
'name' => 'SomeField',
|
||||
'expected' => false,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
// ],
|
||||
// ];
|
||||
// }
|
||||
#[DataProvider('provideHasDataValue')]
|
||||
public function testHasDataValue(mixed $data, ?string $name, bool $expected): void
|
||||
{
|
||||
$viewLayerData = new ViewLayerData($data);
|
||||
$this->assertSame($expected, $viewLayerData->hasDataValue($name)); // @TODO call methods with(out) args
|
||||
}
|
||||
|
||||
// #[DataProvider('provideHasDataValue')]
|
||||
// public function testHasDataValue(): void
|
||||
// {
|
||||
public static function provideGetRawDataValue(): array
|
||||
{
|
||||
$dbHtml = (new DBHTMLText())->setValue('Some html text');
|
||||
// Note we're not checking the fetch order or passing args here - see testGet and testCall for that.
|
||||
return [
|
||||
[
|
||||
'data' => ['MyField' => 'some value'],
|
||||
'name' => 'MissingField',
|
||||
'expected' => null,
|
||||
],
|
||||
[
|
||||
'data' => ['MyField' => null],
|
||||
'name' => 'MyField',
|
||||
'expected' => null,
|
||||
],
|
||||
[
|
||||
'data' => ['MyField' => 'some value'],
|
||||
'name' => 'MyField',
|
||||
'expected' => 'some value',
|
||||
],
|
||||
[
|
||||
'data' => ['MyField' => 123],
|
||||
'name' => 'MyField',
|
||||
'expected' => 123,
|
||||
],
|
||||
[
|
||||
'data' => ['MyField' => true],
|
||||
'name' => 'MyField',
|
||||
'expected' => true,
|
||||
],
|
||||
[
|
||||
'data' => ['MyField' => false],
|
||||
'name' => 'MyField',
|
||||
'expected' => false,
|
||||
],
|
||||
[
|
||||
'data' => ['MyField' => $dbHtml],
|
||||
'name' => 'MyField',
|
||||
'expected' => $dbHtml,
|
||||
],
|
||||
[
|
||||
'data' => (new ArrayData(['MyField' => 1234]))->customise(new ArrayData(['MyField' => 'overridden value'])),
|
||||
'name' => 'MyField',
|
||||
'expected' => 'overridden value',
|
||||
],
|
||||
[
|
||||
'data' => (new ArrayData(['MyField' => 1234]))->customise(new ArrayData(['FieldTwo' => 'checks here'])),
|
||||
'name' => 'FieldTwo',
|
||||
'expected' => 'checks here',
|
||||
],
|
||||
[
|
||||
'data' => (new ArrayData(['MyField' => 1234]))->customise(new ArrayData(['FieldTwo' => 'not here'])),
|
||||
'name' => 'MyField',
|
||||
'expected' => 1234,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
// }
|
||||
#[DataProvider('provideGetRawDataValue')]
|
||||
public function testGetRawDataValue(mixed $data, string $name, mixed $expected): void
|
||||
{
|
||||
$viewLayerData = new ViewLayerData($data);
|
||||
$this->assertSame($expected, $viewLayerData->getRawDataValue($name));
|
||||
}
|
||||
|
||||
// public function testGetRawDataValue(): void
|
||||
// {
|
||||
public static function provideGetRawDataValueType(): array
|
||||
{
|
||||
// The types aren't currently used, but are passed in so we can use them later
|
||||
// if we find the distinction useful. We should test they do what we expect
|
||||
// in the meantime.
|
||||
return [
|
||||
[
|
||||
'type' => 'property',
|
||||
'shouldThrow' => false,
|
||||
],
|
||||
[
|
||||
'type' => 'method',
|
||||
'shouldThrow' => false,
|
||||
],
|
||||
[
|
||||
'type' => 'any',
|
||||
'shouldThrow' => false,
|
||||
],
|
||||
[
|
||||
'type' => 'constant',
|
||||
'shouldThrow' => true,
|
||||
],
|
||||
[
|
||||
'type' => 'randomtext',
|
||||
'shouldThrow' => true,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
// }
|
||||
#[DataProvider('provideGetRawDataValueType')]
|
||||
public function testGetRawDataValueType(string $type, bool $shouldThrow): void
|
||||
{
|
||||
$viewLayerData = new ViewLayerData([]);
|
||||
if ($shouldThrow) {
|
||||
$this->expectException(InvalidArgumentException::class);
|
||||
} else {
|
||||
$this->expectNotToPerformAssertions();
|
||||
}
|
||||
$viewLayerData->getRawDataValue('something', type: $type);
|
||||
}
|
||||
|
||||
// public function testCache(): void
|
||||
// {
|
||||
public function testCache(): void
|
||||
{
|
||||
$data = new ArrayData(['MyField' => 'some value']);
|
||||
$viewLayerData = new ViewLayerData($data);
|
||||
|
||||
// }
|
||||
// No cache because we haven't fetched anything
|
||||
$this->assertNull($data->objCacheGet('MyField'));
|
||||
|
||||
// Fetching the value caches it
|
||||
$viewLayerData->MyField;
|
||||
$this->assertSame('some value', $data->objCacheGet('MyField'));
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ use SilverStripe\View\ThemeResourceLoader;
|
||||
use SilverStripe\View\ThemeManifest;
|
||||
use SilverStripe\Model\ModelData;
|
||||
use SilverStripe\View\SSViewer_Scope;
|
||||
use SilverStripe\View\ViewLayerData;
|
||||
use Symfony\Component\Translation\Loader\ArrayLoader;
|
||||
use Symfony\Component\Translation\Translator;
|
||||
|
||||
@ -73,7 +74,7 @@ trait i18nTestManifest
|
||||
{
|
||||
// force SSViewer_Scope to cache global template vars before we switch to the
|
||||
// test-project class manifest (since it will lose visibility of core classes)
|
||||
$presenter = new SSViewer_Scope(new ModelData());
|
||||
$presenter = new SSViewer_Scope(new ViewLayerData([]));
|
||||
unset($presenter);
|
||||
|
||||
// Switch to test manifest
|
||||
|
Loading…
Reference in New Issue
Block a user