Open rvodden opened 7 years ago
When we've sorted this out I'll have a go at #52 ;-)
Actually - I've had a poke around and I understand the driver creation process and the element injection a bit better now. We are, I see, already using symfony to inject the Element
classes so it would just be a case of tweaking that injection so it was type safe. A more traditional injection pattern like constructor injection may work well here.
@rvodden Do you think we can make this change in a 1.x release (without breaking backwards compatibility) as this is basically internal/plumbing changes?
Yeah I think we can. Let me start it off, if I do it for one element it will give me an idea of timescales.
Thanks
I'd like to get moving on this. @rvodden if you've no time or interest, mind if I take a first pass at it? I'll need to run it by you several times I suspect, but I can make a start.
Let me push up my branch with my WIP - I've got a skeleton which you can base your stuff on
@rvodden if you're going to try to pick this up again, may I suggest starting over with a new branch, and just trying to get small sections complete, rather than attempt to swap everything at once and then run out of steam?
Potential Problem
At the moment all the
Element
classes implementElementInterface
and extend fromBaseElement
. This presents a number of issues, none of which in isolation are a big deal, but in conjunction may mean we want to look at refactoring.Not all
Element
classes implement every operation. This is dealt with at the moment by havingBaseElement
throw anUnsupportedDriverActionException
by default. This doesn't distinguish between features which happen to not be implemented by the driver (like database transactions in WPCLI), and which cannot be implemented by design (for example, acreate
method inCacheElement
is never going to make sense).Linked to the above, there are operations which are driver specific and need to be held in the
Element
classes which don't fall neatly into thecreate
,get
,update
,delete
, model. For examplegetPermalink
inContentElement
requires a different implementation for WPCLI and WPHP, and only applies to content so definitely lives inContentElement
.The
Element
classes are coupled to the driver at runtime using an array ofElementInterface
. This is great because it keeps the coupling loose. The flip side is that PHP doesn't understand all the details of theElement
classes. For example, I can't see the phpdoc of anElement
class in the IDE; simlarly argument exceptions won't be caught until runtime etc. The screen shot below shows phpdoc working for getDriver(). If you move the mouse pointer right so that it's overcontent
no such tooltip appears. I figured that didn't need another screen shot ;-)Some of the
create
,get
,update
,delete
methods feel a little forced. For example inDatabaseElement
,import
andexport
feel more descriptive to me thanget
andupdate
. Similarly inCacheElement
,clear
is more meaningful thandelete
. In both cases there are alias methods, however a solution which doesn't require aliasing will make code easier to diagnose and reduce the complexity of the classes.Ensuring that features are implemented consistently is manual at the moment with out much help from php. #126 is an example of this where the
getUserByLogin
method ofUserElement
in WPPHP and WPCLI were returning different types. This logic has since been moved toUserAwareContextTrait
as it was driver agnostic. I feel the principle still stands.Documentation is duplicated across the drivers. For example, the phpdoc on
UserElement->create()
is identical in WPPHP and WPCLI. This is because its documenting the API which is being implemented rather than the implementation.At the bottom of the list is that Scrutinzer and other static code inspection tools complain when you call a method implemented in a concrete class which is referenced by a variable declared as an interface which does not declare that method. The error looks like:
Accessing cache on the interface PaulGibbs\WordpressBehat...\Driver\DriverInterface suggest that you code against a concrete implementation. How about adding an instanceof check?
I'm certainly not going to lose sleep about SCI, but it usually has errors for a reason. I think I've captured most of those reasons in the points above, but there may well be others.
Requirements
As I said at the top, none of these issues are a particularly big deal, so any proposed solution should deal with a decent number of them to be worth implementing. This means we're looking for something which:
CacheElement->create()
should fail at compile time.Element
classes remains, however methods likegetPermalink
should be catered for.DatabaseElement->export()
should be standalone and not an alias forget()
.Solution
I propose changing the driver class hierachy a bit. Right now everything descends from
BaseElement
where in practice there is very little common code between all theElement
classes. I suggest we acknowledge that eachElement
is different and author Interfaces for each of theElement
classes. These will be kept outside of the drivers. We can then have WPCLI and WPPHP implementations of each of these interfaces.Using a set of
Element
specific interfaces like this will mean that unrequired methods can just be left out, nuanced methods can be included, more descriptive names can be used where preferred, drivers inconsistencies will be detectable, and there's a nice neat home for API documentation.That deals with everything except runtime composition. I want to have a play around with a few options for doing this, but I think getting Symfony to inject
Element
classes into the drivers, in the same way that the PageObjects are injected into the Contexts, is probably the way to go. The other option is to effect changes toWordpressDriverManager
but I think we'll just end up implementing our own DI framework - which is daft when we've got one already.