Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 33 additions & 5 deletions src/Attribute/Base.php
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,30 @@ public function getDefaultRenderSettings()
return new Simple(['template' => 'mm_attr_' . ($this->get('type') ?? '')]);
}

/**
* Build the render setting to retry with when rendering with the passed setting has failed.
*
* The parent is only carried over when the passed setting has one - render settings created on the fly (see
* getDefaultRenderSettings()) do not have a parent and must not let the fallback fail with
* "Parent has not been set", which would mask the problem that made the rendering fail in the first place.
*
* @param ISimpleRenderSetting $objSettings The render setting that failed.
*
* @return ISimpleRenderSetting
*/
protected function getFallbackRenderSetting($objSettings)
{
$objSettingsFallback = $this->getDefaultRenderSettings();

try {
$parent = $objSettings->getParent();
} catch (\RuntimeException) {
return $objSettingsFallback;
}

return $objSettingsFallback->setParent($parent);
}

/**
* {@inheritdoc}
*/
Expand All @@ -646,17 +670,21 @@ public function parseValue($arrRowData, $strOutputFormat = 'text', $objSettings

// Text rendering is mandatory, try with the current setting,
// upon exception, try again with the default settings, as the template name might have changed.
// if this fails again, we are definitely out of luck and bail the exception.
// if this fails again, we are definitely out of luck and bail the original exception.
try {
$arrResult['text'] = $objTemplate->parse('text', true);
} catch (\Exception $e) {
// FIXME: this throws when no parent has been set - need to catch!
$objSettingsFallback = $this->getDefaultRenderSettings()->setParent($objSettings->getParent());
} catch (\Exception $exception) {
$objSettingsFallback = $this->getFallbackRenderSetting($objSettings);

$objTemplate = new Template($objSettingsFallback->get('template') ?? '');
$this->prepareTemplate($objTemplate, $arrRowData, $objSettingsFallback);

$arrResult['text'] = $objTemplate->parse('text', true);
try {
$arrResult['text'] = $objTemplate->parse('text', true);
} catch (\Exception $fallbackException) {
// Do not mask the real problem with the follow-up error of the fallback rendering.
throw $exception;
}
}
} else {
// Text rendering is mandatory, therefore render using default render settings.
Expand Down
55 changes: 55 additions & 0 deletions tests/Attribute/BaseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@

use MetaModels\Attribute\Base;
use MetaModels\IMetaModel;
use MetaModels\Render\Setting\ICollection;
use MetaModels\Render\Setting\ISimple;
use MetaModels\Render\Setting\Simple as RenderSetting;
use PHPUnit\Framework\TestCase;

/**
Expand Down Expand Up @@ -191,4 +194,56 @@ public function testGetFieldDefinition()
self::assertEquals('some_widget_class', $fieldDefinition['eval']['tl_class']);
self::assertEquals(true, $fieldDefinition['eval']['readonly']);
}

/**
* The fallback render setting must not fail for render settings without parent.
*
* Settings created on the fly (getDefaultRenderSettings()) have no parent - obtaining it would throw and thereby
* mask the problem that made the rendering fail in the first place.
*
* @return void
*/
public function testGetFallbackRenderSettingWithoutParent()
{
$attribute = $this->getAttribute();

$fallback = $this->getFallbackRenderSetting($attribute, new RenderSetting([]));

self::assertInstanceOf(RenderSetting::class, $fallback);
self::assertSame('mm_attr_base', $fallback->get('template'));
}

/**
* The parent of the failing render setting is carried over to the fallback when there is one.
*
* @return void
*/
public function testGetFallbackRenderSettingKeepsParent()
{
$attribute = $this->getAttribute();
$parent = $this->getMockForAbstractClass(ICollection::class);

$setting = new RenderSetting([]);
$setting->setParent($parent);

$fallback = $this->getFallbackRenderSetting($attribute, $setting);

self::assertSame($parent, $fallback->getParent());
}

/**
* Call the protected getFallbackRenderSetting method on the passed attribute.
*
* @param Base $attribute The attribute to call the method on.
* @param ISimple $setting The render setting that failed.
*
* @return ISimple
*/
private function getFallbackRenderSetting($attribute, $setting)
{
$method = new \ReflectionMethod($attribute, 'getFallbackRenderSetting');
$method->setAccessible(true);

return $method->invoke($attribute, $setting);
}
}