-
Notifications
You must be signed in to change notification settings - Fork 154
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
[WIP][FEATURE] Allow dependency injection in case of helper classes #668
Conversation
I have also found the need to make a hacked version of that class for debugging/testing (to add whitespace and newlines to the CSS so it is readable). I found that for the extended class I would probably like the methods And a method to 'inject' the extended class. I added a protected method The drawback of doing this and making methods |
You're right about Nevertheless, I think an even better approach would be to define interfaces - i.e. in this case there should be a
I may misunderstood you, but I am not sure I get the difference between injecting an object versus injecting a class name. In any case, I prefer injecting objects as it is more versatile (e.g. think of implementer classes that have constructor arguments - Emogrifier/CssInliner couldn't handle those). |
Injecting a class name is like injecting a class factory (PHP allows class names to be passed around without the need for a specific factory class). Though a class factory would have more versatility. The point I'm trying to make is that The current design is that a new object instance is created each time it is required, and discarded after use (there is no need for it to persist). So I think if consumers were to be allowed to set some property (as well the alternative of overriding |
A few thoughts:
|
So, are we leaning towards a method to be overridden in an extended
The default implementation would also need to take that responsibility, though probably as simply as
I'm not sure that should be allowed to help. Although |
I'm confused … I was thinking about something like this: class CssInliner {
public function injectCssConcatenator(CssConcatenatorInterface $concatenator) {…}
}
class CssConcatenatorInterface implements CssConcatenatorInterface {} |
Agreed. |
And I agree that |
Ah, ok, so a method to set an object (presumably as a property), rather than a overridable method which would return said object.
Presumably this should read
It doesn't at present, but would if it were changed to use (and potentially reuse) a |
Yes, you're right. (That's what happens when I reply from a train when it's too hot … ;-) ) |
Added a test to confirm `copyUninlineableCssToStyleNode` does not have unexpected side-effects, i.e. it would produce the same result if called a second time on the same `CssInliner` instance. This will help ensure that the solution for #668 does not create future potential maintenance issues.
I've added #671 to test this. It currently passes but the current proposed implementation in the dependency-injection branch would cause it to fail, due to reusing a |
... so I think this calls for the factory design pattern. In most OO languages this would require a factory class. PHP would (as an alternative, still following the design pattern) allow a class name (of the objects to be created) to be set as a pseudo-factory without having to define/implement a factory interface - whether that would be benefitial is open to debate. |
Thinking it through, I would still revisit the idea of simple (object) dependency injection, where the to-be-injected object must implement a well-defined interface, and the idea of having a In the concrete case of |
I was also thinking that this approach would make it no longer possible to use more than one such container at the same time (if required at some future time). However, PHP’s object Thus, I wonder, if we do take this approach, should My main objection is
I could be evil and implement |
After more thinking... If we limit ourselves to current functionality, then simple object dependency injection with a reset method is sufficient. BUT if we want to make it super future-proof, then I am leaning towards injecting a factory class (of course using a factory interface - If you guys agree, I will submit a new PR implementing the factory pattern way. |
Although it involves more indirection and more interfaces, I think this would be the most future-proof. We could avoid an extra class by having $this->cssConcatenatorFactory = $this; I don't know if that would be preferred to a separate default factory class? |
While this is a neat idea, I would still vote for a separate class because of the following:
|
I am reluctant to go the factory approach now. This feels over-engineered to me in order to solve a problem we do not have (yet). Unless we really need this flexibility, please let's keep it simple. (See https://en.wikipedia.org/wiki/You_aren%27t_gonna_need_it for details.) However, if there is a valid usecase coming up in the near future where we'll need multiple |
I don't have a crystal ball. But should the situation arise it would require a new major release as a public interface is changed.
The simplest solution is to just to add to protected function createCssConcatenator()
{
return new CssConcatenator();
} Then if anyone wants to 'inject' a different object, they extend Not only is it simple, it also does not hinder future development (i.e. restricting to a single However, the gist I was getting is that you are both preferring to have some property that can be set, rather than some method that can be overridden…? |
It's just that I named the PR "dependency injection" and tried to find a way to make it work that way - without the need for sub-classing 😊 This being said, jokes aside, I do feel that the method overriding way is the simplest solution in our current case. It helps both with "resetting" the collection of rules and (eventually) use more concatenator objects in parallel too. |
I concur. |
Added a test to confirm `copyUninlineableCssToStyleNode` does not have unexpected side-effects, i.e. it would produce the same result if called a second time on the same `CssInliner` instance. This will help ensure that the solution for #668 does not create future potential maintenance issues.
@zoliszabo Do you consider this PR ready to review, is it still a WIP, or was it only to show some prototype code to discuss? |
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.
As I understand, we have subsequently agreed on a simpler solution, which is just to add a createCssConcatenator
method that can be overridden to return a different or extended implementation.
* | ||
* @var Emogrifier\CssConcatenator | ||
*/ | ||
private $cssConcatenator = null; |
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.
As I understand, we have subsequently agreed on a simpler solution, which is just to add a createCssConcatenator
method that can be overridden to return a different or extended implementation.
@@ -49,7 +49,7 @@ class CssConcatenator | |||
* | |||
* @var \stdClass[] | |||
*/ | |||
private $mediaRules = []; | |||
protected $mediaRules = []; |
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 change is fine.
Hi @oliverklee, as @JakeQZ wrote above:
I will implement the necessary changes in 1-2 days, then ask for your review. (Unfortunately, I had no time in the past few weeks to participate in the sustained work you both did here; I will try to gear up my pace the next days.) |
I will switch the default git branch from |
Recently had to briefly customize the functionality
CssConcatenator
and there was no way to do it without modifying the "core"Emogrifier.php
file in thevendor
folder.Hence my proposal to make the used "helper" classes inject-able:
CssConcatenator
;$mediaRules
property (fromprivate
toprotected
) inCssConcatenator
, so sub-classes have access to it.How to use it:
CssConcatenatorInterface
would declare two methods -append()
andgetCss()
), which will be used as the argument type of the injector method and all implementations (starting with the default ones ofc) will implement those interfaces. Otherwise all additional implementations must extend the default ones provided by Emogrifier.What do you think? Let's discuss.
Thanks!