Closed sminnee closed 7 years ago
A broader alternative would be to make every DataObject & DataList a maybe, so that failed matches return an object with isNothing()
returning true, or something, but it invalidates every if (!$this->Parent)
call in user code, which seems too painful.
We can probably rely on a 3rd party module for this, but my point in recommending it is that we make it a standard pattern in favour of isset()
calls.
Interesting idea. What about $this->maybe()->Parent->Author->value()
function maybe() {
return new SilverStripe\Maybe($this);
}
This would be an opt-in change to how developers use SilverStripe, so it could land in 4.0
, 4.1
etc. It's also a good starting-point for the introduction of other Monad types, like Many
:
$this->many()
->Posts
->Comments
->Submitters
->column('ID');
// → every ID of every Submitter of every Comment of every Post related to $this
Yeah, that's better, and it can be a tiny trait.
https://github.com/pirminis/maybe-monad is close to an implementation of what I'm after, although it uses a global function to create new maybes.
https://github.com/typedphp/optional is another, and I think the author would be open to extending it or even moving the code over to a place we control...
Edit: these aren't really difficult or large libraries to maintain. I think we'd be better off having the ideal implementation rather than settling with something else.
Code quality in https://github.com/typedphp/optional is definitely better.
I'm not too worried about maintenance burden, although I like the idea of avoiding kitchen-sinking this implementation into framework. Keeping it as a separate package seems fine.
The alternative approach would be consistently return objects for the relationship traversal, in effect embedding the Maybe monad's functionality into DataObject itself. DataObject::getComponent()
already does this, so it wouldn't be entirely new.
Furthermore, if we were to preserve this behaviour in the ORM I think we'd need to tighten up on some of the semantics. Are you expected to be able to write these objects and have the database updated? What about if you traverse multiple relationships on empty objects? What about a combination of has_one and has_many relations?
The Monad approach limits you—it's explicitly read-only—but its semantics are clear.
With the monad, if you entered a relationship with no associated record, how would you then associate a new one?
Eg:
$rel = $this->Relation();
if (!$rel->exists()) {
$rel->add(RelDO::create()->write());
}
Returning a new instance the relation expects may allow a bit more flexibility with doing something like
$rel = $this->Relation();
if (!$rel->exists()) {
$rel->Title = 'blah';
$rel->write();
}
Which is nice for the lazy among us
At the moment we do this for has_one / belongs_to relations, but not with has_many or many_many relations (since a relation list can still be useful as an empty list).
I don't think that automatic instance creation over has_many/many_many makes such sense. As soon as you requested "->First()" or some item from the list, I'd expect the chain to end up with an empty Monad on the end.
Returning a new instance the relation expects may allow a bit more flexibility with doing something like
I see the reliance on this behaviour to create new related objects extremely rarely; I don't think it's a feature we need to maintain, given that most users are simply explicitly creating related objects and assigning them via foreign key assignment.
I'd prefer that related has_one() objects were true null if called directly, but could be supported via a monad for chaining (along with other relation types), and we simply require explicit assignment for relation creation.
function maybe() {
return new SilverStripe\Maybe($this);
}
👍 for @assertchris 's sugestion.
I see the reliance on this behaviour to create new related objects extremely rarely
Just because you see people do it rarely doesn't mean it isn't desirable, useful or a good way to do it.
If we have a DataObject with a relation, if we can instantiate a version of that relation through $obj->Relation()
then that means that I can decouple my knowledge about what class I need to create and add as well as any relation foreign keys that I shouldn't be explicitly setting.
Disclaimer: This is not a joke.
A discussion on #5450 about
isset()
highlights and issue. We too often end up with code like this:A Maybe monad, despite the scary name, is a solution to this. It might look like this:
Or
The latter is a bit more verbose but can be applied to any class. That said, the former could be implemented with a trait.