-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added new View Decorators. #5567
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This proposal defines global decorators, which makes it impossible to generate a view without decorators or selectively apply them.
if (empty($decorators)) { | ||
return $html; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The decorators property is an array. An empty array will not be processed by foreach.
It seems to me there is no point in this check.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using empty()
is conceptually expandable to if (! isset(config('View')->decorators) || config('View')->decorators) === [])
- in other words, it covers backwards-compatibility for projects that don't add the $decorators
property to their App Config file. Alternatively we could (should?) add $decorators
to CodeIgniter\Config\View
in which case then I would agree we can drop this check.
I know @lonnieezell has been opposed to duplicating Config values between system/ and app/, but given that the View config class already exists in system/ I suspect this is the way to go.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did add it the system config file. Well I've corrected that since I put it in the wrong file last night. Silly me :) Will remove.
* chance to modify the output from the view() calls | ||
* prior to it being cached. | ||
*/ | ||
interface ViewDecorator |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that the name of the interface should contain the suffix Interface
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the current name makes sense in the subdirectory Interfaces, but none of our other libraries have a dedicated Interfaces
namespace so I would be in favor of moving this file up a directory and renaming it as @iRedds suggests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree - will make it match the rest of them. I was even the one who initially argued the point that they should be named that way.
foreach ($decorators as $className) { | ||
$decorator = new $className(); | ||
|
||
if (! $decorator instanceof ViewDecorator) { | ||
throw ViewException::forInvalidDecorator($className); | ||
} | ||
|
||
$html = $decorator->decorate($html); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This code can be optimized.
foreach ($decorators as $className) { | |
$decorator = new $className(); | |
if (! $decorator instanceof ViewDecorator) { | |
throw ViewException::forInvalidDecorator($className); | |
} | |
$html = $decorator->decorate($html); | |
foreach ($decorators as $decorator) | |
if (! is_subclass_of($decorator, ViewDecorator::class) { | |
throw ViewException::forInvalidDecorator($decorator); | |
} | |
$html = (new $decorator())->decorate($html); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we go with the static method suggestion above this can be simplified even more:
foreach ($decorators as $className) { | |
$decorator = new $className(); | |
if (! $decorator instanceof ViewDecorator) { | |
throw ViewException::forInvalidDecorator($className); | |
} | |
$html = $decorator->decorate($html); | |
foreach ($decorators as $className) { | |
if (! is_subclass_of($decorator, ViewDecorator::class) { | |
throw ViewException::forInvalidDecorator($className); | |
} | |
$html = $decorator::decorate($html); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the static method suggestion here. Will change and simplify.
Can you give a real life example of using view decorators? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm super excited for this! Lots of comments, and also some CI fixes that CS Fixer/Rector ought tot take care of.
if (empty($decorators)) { | ||
return $html; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using empty()
is conceptually expandable to if (! isset(config('View')->decorators) || config('View')->decorators) === [])
- in other words, it covers backwards-compatibility for projects that don't add the $decorators
property to their App Config file. Alternatively we could (should?) add $decorators
to CodeIgniter\Config\View
in which case then I would agree we can drop this check.
I know @lonnieezell has been opposed to duplicating Config values between system/ and app/, but given that the View config class already exists in system/ I suspect this is the way to go.
* chance to modify the output from the view() calls | ||
* prior to it being cached. | ||
*/ | ||
interface ViewDecorator |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the current name makes sense in the subdirectory Interfaces, but none of our other libraries have a dedicated Interfaces
namespace so I would be in favor of moving this file up a directory and renaming it as @iRedds suggests.
system/Language/en/View.php
Outdated
@@ -17,4 +17,5 @@ | |||
'noCellClass' => 'No view cell class provided.', | |||
'invalidCellClass' => 'Unable to locate view cell class: {0}.', | |||
'tagSyntaxError' => 'You have a syntax error in your Parser tags: {0}', | |||
'invalidDecoratorClass' => '{0} is not a valid ViewDecorator.', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we go with the rename I think this would read better without the Interface
suffix so I suggest making it natural:
'invalidDecoratorClass' => '{0} is not a valid ViewDecorator.', | |
'invalidDecoratorClass' => '{0} is not a valid View Decorator.', |
foreach ($decorators as $className) { | ||
$decorator = new $className(); | ||
|
||
if (! $decorator instanceof ViewDecorator) { | ||
throw ViewException::forInvalidDecorator($className); | ||
} | ||
|
||
$html = $decorator->decorate($html); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we go with the static method suggestion above this can be simplified even more:
foreach ($decorators as $className) { | |
$decorator = new $className(); | |
if (! $decorator instanceof ViewDecorator) { | |
throw ViewException::forInvalidDecorator($className); | |
} | |
$html = $decorator->decorate($html); | |
foreach ($decorators as $className) { | |
if (! is_subclass_of($decorator, ViewDecorator::class) { | |
throw ViewException::forInvalidDecorator($className); | |
} | |
$html = $decorator::decorate($html); |
@iRedds I'm excited for this because it takes some pressure off of Most of my "HTML injection" libraries use the Filters method, here is an example that would be replaced by a single Decorator: https://github.com/tattersoftware/codeigniter4-alerts/blob/96cd7fa922cad556fb20f015d746f38fb85c748f/src/Filters/AlertsFilter.php One other "plus" of Decorators is they are a lot easier to configure and apply! For a third-party library to activate its Filter it needs to use one of these hack-y FIlter configs: ... and then the implementing project still needs to add it to the desired routes in app/Config/Filters.php: public $globals = [
'after' => [
'alerts' => '*',
],
]; |
@lonnieezell what about some built-in tools or core handling for non-HTML output? Is there any scenario where we need to be "header-aware" (like disabling Decorators for JSON content), or is that up to the developer? For example, I add this to all my HTML-injection Filters: |
Thinking about this more my hunch is that View doesn't care at all about formatting or headers and it would be on the developer to know that passing content through will subject it to Decorators. I suspect most APIs dealing with JSON/XML aren't using View for the content anyway. Probably worth mentioning in the User Guide either way. |
Two of them: I've implemented View Components in Bonfire, but they require on overriding the View class to work. Essentially they look for custom html tags and replace them with other content. It could also be used for a "snippet" functionality that kept small bits of text/html and will replace placeholders with it globally. I've seen this on some CMS' before. |
I agree with your assessment that View doesn't care here. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume you are still looking at some of the others. One typing change here.
Also, is there a case to be made for View::enableDecorators()
/View::disableDecorators()
?
Oh - @MGatner this should target 4.2, also, shouldn't it? |
I think everything is accounted for now, though I had to skip the pre-commit checks since there are two PHPStan errors for me locally with the latest develop in |
My initial instinct is not really, but both you and IReds brought something similar up so maybe. :) |
php-cs-fixer check failed, but it seems php-cs-fixer's 1) CodeIgniter4/tests/system/Cookie/CookieStoreTest.php (phpdoc_types_order)
---------- begin diff ----------
--- /home/runner/work/CodeIgniter4/CodeIgniter4/tests/system/Cookie/CookieStoreTest.php
+++ /home/runner/work/CodeIgniter4/CodeIgniter4/tests/system/Cookie/CookieStoreTest.php
@@ -107,7 +107,7 @@
$prod = $cookies['prod']->getOptions();
/**
- * @var MockObject&CookieStore $store
+ * @var CookieStore|MockObject $store
*/
$store = $this->getMockBuilder(CookieStore::class)
->setConstructorArgs([$cookies]) https://github.com/codeigniter4/CodeIgniter4/runs/4812733176?check_suite_focus=true |
We merge 4.2 into develop! We'll see how that goes 😅 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few wording changes. And since we are >= 7.4
now: typed properties!
Co-authored-by: MGatner <mgatner@icloud.com>
Co-authored-by: MGatner <mgatner@icloud.com>
Co-authored-by: MGatner <mgatner@icloud.com>
Co-authored-by: MGatner <mgatner@icloud.com>
Co-authored-by: MGatner <mgatner@icloud.com>
Co-authored-by: MGatner <mgatner@icloud.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me! See if there are any workflow issues remaining.
…4 into view-decorators
🥳 |
Description
Created View Decorators that allow you to modify HTML generated from the View or Parser classes just prior to it being cached.
Checklist: