-
-
Notifications
You must be signed in to change notification settings - Fork 96
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
Entity & Field Plugins #157
Conversation
Wow, this is an amazing start! For resolving the tests, we'll need to pull down Drupal (not sure we need to add it as a dev dependency or not...) I'll try to give this a proper review sometime soon. |
I will try to test this later as it looks promising, for now we were in the need of adding all sorts of entities as well and we came up with this:
Example:
|
I've got an old PR for that on the extension:
jhedstrom/drupalextension#300
I'm using it as a composer patch, works OK. Doesn't use this new plugin
goodness from the driver side, uses the entity handling yo committed to the
driver a year ago.
…On 12 April 2018 at 16:32, Harings Rob ***@***.***> wrote:
I will try to test this later as it looks promising, for now we were in
the need of adding all sorts of entities as well and we came up with this:
/**
* The entities created.
*
* @var \Drupal\Core\Entity\EntityInterface[]
*/
private $createdEntities = [];
/**
* Creates an entity of a given type.
*
* @given :entity with bundle :type content:
*
* @throws \Exception
*/
public function createEntityWithBundle(string $entity_type, string $type, TableNode $table) {
foreach ($table->getHash() as $entityHash) {
$entity_data = (object) $entityHash;
$entity_data->type = $type;
/** @var \Drupal\Driver\Cores\Drupal8 $core */
$core = $this->getDrupal()->getDriver()->core;
$method = new ReflectionMethod(Drupal8::class, 'expandEntityFields');
$method->setAccessible(TRUE);
$this->parseEntityFields($entity_type, $entity_data);
$method->invoke($core, $entity_type, $entity_data);
$definitions = \Drupal::entityTypeManager()->getDefinitions();
if (isset($definitions[$entity_type])) {
/** @var \Drupal\Core\Entity\EntityInterface $class */
$class = $definitions[$entity_type]->getOriginalClass();
$entity = $class::create((array) $entity_data);
if (!$entity->save()) {
throw new \Exception(sprintf('Entity could not be saved.'));
}
$this->createdEntities[] = $entity;
}
else {
throw new \Exception(sprintf('Entity type %entity_type does not exist.', ['%entity_type' => $entity_type]));
}
}
}
/**
* Cleanup entities after scenario.
*
* @afterScenario
*/
public function cleanEntities() {
foreach ($this->createdEntities as $entity) {
$entity->delete();
}
}
Example:
And "commerce_product" with bundle "default" content:
| title |
| test |
| test2 |
—
You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
<#157 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/ADWYQVBI8U9KXtqbAkQG6sLhQRQoCNUTks5tn3OmgaJpZM4Rdln6>
.
|
I've just kicked tests off for the Drupal Extension with this branch to see what happens. https://travis-ci.org/jhedstrom/drupalextension/builds/374181918 |
Looks like the failures would be resolved by rebasing this to include the mail stuff that went in with #134. |
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.
Here's a more complete review for now. Thanks so much for all your work on this!
|
||
/** | ||
* Identifies the targets from the pool of candidates. | ||
* |
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.
Basic indentation fix.
if (is_array($targets)) { | ||
$this->targets = $targets; | ||
} else { | ||
throw new \Exception("Targets to be identified must be passed as an array with their human-friendly name as the keys and anything as the values."); |
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.
Is there a reason we can't just type-hint the function? (array $targets
)
$this->identifyByMethod("MachineNameWithoutUnderscores"); | ||
if ($mayHavePrefix) { | ||
$this->identifyByMethod("MachineNameWithoutPrefixAndUnderscores"); | ||
} |
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 logic above here could use some code comments I think. It would be hard to grok 6-months, or a year, down the road I think.
if ($this->$methodFunctionName($identifier, $machineName, $label)) { | ||
//$this->candidates = array_filter($this->candidates, function ($value, $key) use ($machineName) { | ||
// return $value === $machineName; | ||
//}, ARRAY_FILTER_USE_BOTH); |
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 looks like it's been moved down below, so can be removed here?
$this->expandEntityFields('node', $node); | ||
$entity = Node::create((array) $node); | ||
$entity = $this->getNewEntity('node'); | ||
$entity->setFields((array) $node); |
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 love how simple this becomes! 👍
$this->expandEntityFields('taxonomy_term', $term); | ||
$entity = Term::create((array) $term); | ||
$entity = $this->getNewEntity('taxonomy_term'); | ||
$entity->setFields((array) $term); |
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.
👍
/** | ||
* Tests the Driver's name matcher utility. | ||
*/ | ||
class DriverNameMatcherTest extends \PHPUnit_Framework_TestCase |
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.
Yay unit tests!
I still need to review some of the low-level implementation, but getting tests green here, and on the Drupal Extension will definitely be good :) |
I've rebased and force pushed that to the PR, so maybe the extension tests will work now. I'm working on fixing coding standards, huge amounts of nits to address there. |
I re-triggered the test run and there appears to be some interface compatibility issues: https://travis-ci.org/jhedstrom/drupalextension/jobs/374181923 |
OK, this time the rebase is good, all green locally except the expected fail. |
Now kernel tests and extension features passing locally with php5.5 & D8.5. |
Coding standards fixed. Now green and clean locally, should be green for CS here. |
My attempts to execute the kernel tests building on #188 have foundered because we have a namespace collision with core's composer.json:
hence phpunit gives me
|
I misdiagnosed the problem above, it wasn't namespace collision, it was just that Drupal doesn't autoload the KernelTests namespace normally, it relies on tests/bootstrap.php to do that. TestsI've got things working on Travis, including always running the drupal-extension features for drupal-driver PRs. Some issues:
To do
HelpI could really do with advice on these:
|
I've got plugins mostly working for D7, though not pushed here yet as there is more to do for that and some serious refactoring is needed as this tangle of classes to manage it all is too painful. |
Addresses #155. I've added a general discussion of top-level matters to that issue.
Some notes for reviewers on implementation matters...
Code flow
The code flow typically begins with a DriverEntityDrupal8 wrapper object.
This then discovers and wraps a DriverEntity plugin, and the DriverEntity plugin creates or loads and wraps a drupal entity.
The DriverEntity Plugin calls DriverField wrapper object when it wants to process field values. The DriverField wrapper discovers and process through matching field plugins.
Review order
I suggest reviewing in this order:
The bundle dance
A source of major pain in the DriverEntity wrapper is that when creating an entity we are sometimes explicitly told what bundle it should be, sometimes it has no bundle, and sometimes it has a bundle but we have to find it amongst the supplied fields. But the bundle is part of what is (optionally) used to identify the right plugin for an entity, so we don't get the definitive plugin and a drupal entity until we've resolved the bundle.
My solution is to distinguish between a provisional (bundle-agnostic) plugin loaded on a mere entity-type match, and a final plugin (which may or may not be different) that also uses the bundle when considering which plugin to use. This allows us to use plugin methods even before we have resolved the bundle, provided they are not methods that set data on the plugin that would be lost if we switched to a different final plugin. Basically it allows us to load a provisional plugin matched by entity type, ask that plugin what the bundle key and bundle key labels are for this entity type, and use that information to find the bundle field from amongst an array of bundles; once the bundle is set we hen ask for the definitive plugin with which we do much more.
The DriverField wrapper
Th DriverField wrapper does wrap a plugin, but it doesn't wrap a Drupal object in the way that the DriverEntity wrapper does. I imagine the DriverEntity wrapper objects will be stored in the Behat extension once instantiated, instead of the repository of ids we currently have, and could be used for all kind of purposes. The DriverField wrapper is much more of a single trick pony. I do wonder whether it should actually be slimmed altogether and just become a method on the DriverEntity plugin base. The danger is that would fatten up the DriverEntity plugin base, which is already complex.
ExpandEntityFields ignored base fields
The ExpandEntityFields method on the driver made use of the driver's getEntityTypes and isField. This seems to have made expanding not happen for baseFields. This behaviour is undocumented, and I could see no purpose to it, so I have not continued it.
ParseEntityFields
The ParseEntityFields method on the extension's RawDrupalContext is a rats nest of untested branches and loops, that frequently calls in to the driver's getEntityTypes and isField methods. It should probably be using a DriverEntity wrapper to call methods on a DriverEntity plugin, and we could thendeprecte the driver's getEntityTypes and isField. But I'm not sure what the plugin methods should be and figuring it out would require a complete refactoring and unit testing of parseEntityFields.
Tests
There is a failing user kernel test because of the call to validateDrupal in the driver's userCreate, discussed in #155.