Following yesterday's Entity Metadata Wrapper blog post and as I continued to work on this task, I noticed some duplication and found that I was repeating several of the same chaining steps in different methods in the same file. For example:
public function getFirstName(): string {
return $this
->get('profile_user_basic') // Get the pupil's profile.
->get('field_first_name')
->value();
}
private function getTeacherFirstName(): string {
$this
->get('profile_student') // Get the pupil's profile.
->get('field_class') // Get the pupil's class.
->get('field_teacher') // Get the class' teacher.
->get('profile_user_basic') // Get the teacher's profile.
->get('field_first_name')
->value();
}
In both cases, the last three lines are the same, where the same profile type is loaded, and the value is loaded from a field.
I wanted to find a way to remove this duplication whilst also making the code more readable. Ideally, this would mean adding a method like getFirstNameFromBasicProfile()
that would group the last three steps.
Extending the EntityDrupalWrapper
I've done this before, where I've created a custom wrapper class with its own methods and extends EntityDrupalWrapper
. This is how that might look:
final class PupilWrapper extends \EntityDrupalWrapper {
public function __construct(\stdClass $data, $info = []) {
parent::__construct('user', $data, $info);
}
public function getFirstName(): string {
return $this->getFirstNameFromBasicProfile();
}
public function getTeacherFirstName(): string {
return $this
->get('profile_student')
->get('field_class')
->get('field_teacher')
->getFirstNameFromBasicProfile();
}
private function getFirstNameFromBasicProfile(): string {
return $this
->get('profile_user_basic')
->get('field_first_name')
->value();
}
}
Whilst this has worked in previous situations, this time I had this error:
Error: Call to undefined method EntityDrupalWrapper::getFirstNameFromBasicProfile() in Drupal\my_module\EntityWrapper\PupilWrapper->getTeacherFirstName
This is because the get()
method is returning an instance of EntityStructureWrapper
(another class that extends EntityDrupalWrapper
) which means that getFirstNameFromBasicProfile()
is not accessible though it's in the same file.
I tried overridding the get()
method but wasn't able to get this to work.
Decorating the EntityDrupalWrapper
Another option that I tried was to follow the Decorator design pattern, and add a new class that takes an EntityDrupalWrapper
as an argument as uses it internally but doesn't extend it. Here's an example:
final class PupilWrapper {
private $accountWrapper;
public function __construct(\EntityMetadataWrapper $accountWrapper) {
$this->accountWrapper = $accountWrapper;
}
public function getFirstName(): string {
return $this->getFirstNameFromBasicProfile();
}
public function getTeacherFirstName(): string {
return $this
->get('profile_student')
->get('field_class')
->get('field_teacher')
->getFirstNameFromBasicProfile();
}
private function getFirstNameFromBasicProfile(): string {
return $this
->get('profile_user_basic')
->get('field_first_name')
->value();
}
}
In this case, the constructor argument is an instance of EntityMetadataWrapper
so that it could be either an EntityDrupalWrapper
or EntityStructureWrapper
.
Re-adding required wrapper methods
As the get()
method is missing, this would cause an error:
Error: Call to undefined method Drupal\my_module\EntityWrapper\PupilWrapper::get() in Drupal\my_module\EntityWrapper\PupilWrapper->getFirstName()
However, we can re-add it, have it get the value from accountWrapper
and return another instance of PupilWrapper
so that getFirstNameFromBasicProfile()
will be available.
public function get(string $property): self {
return new self($this->accountWrapper->get($property));
}
The value()
method is also required, but this can delegate to the decorated wrapper:
Error: Call to undefined method Drupal\my_module\EntityWrapper\PupilWrapper::value() in Drupal\my_module\EntityWrapper\PupilWrapper->getFirstName()
public function value(): string {
return $this->accountWrapper->value();
}
Conclusion
This was the first time that I tried extending Drupal 7's entity metadata wrappers in this way, but it worked well, removes the duplication and cleans up the code further.