Not sure how we never ran into this before now but we must deal with the issue in our other sources so its flown under the radar.
The fatal "Error: Unsupported operand types" happens in SourcePluginBase::next(
) when this code runs:
$row_data = $this->getIterator()->current() + $this->configuration;
That code is actually fine because we require iterators to return arrays and configuration is the plugin configuration so that should always succeed. But when you inspect the values you'll find that $this->getIterator()->current()
is an object.
So, what is the iterator creating that object? There's a lot of code around it but it boils down to this line in SqlBase::initializeIterator()
return new \IteratorIterator($this->query->execute());
Looks pretty good again but there's a failed assumption here and that's that the statement returned by execute is set to return objects:
http://cgit.drupalcode.org/drupal/tree/core/lib/Drupal/Core/Database/Sta...
protected function __construct(Connection $dbh) {
$this->dbh = $dbh;
$this->setFetchMode(\PDO::FETCH_OBJ);
}
So now the PHP Iterator is wrapping the PDO iterator which is listing Objects and the migration blows up.
Comment | File | Size | Author |
---|---|---|---|
#22 | interdiff_18-22.txt | 1.12 KB | heddn |
#22 | 2918837-22.patch | 2.64 KB | heddn |
Comments
Comment #2
neclimdulPatch which forces the fetch type on the statement sent to the migrate iterator.
Comment #3
neclimdulComment #4
quietone CreditAttribution: quietone at Acro Commerce commentedHow is this produced? I ask because commerce_migrate_ubercart extends SqlBase and in several source plugins and runs without Fatals.
Comment #6
neclimdulvery strange... just a super simple source causes this.
Test failures btw are a test violating the database interface to mock results into an iterator. Not an actual failure but a troublesome tests.
Comment #7
neclimdulSo resolved _why_ it works for commerce. The "solution" is in
SqlBase::select()
but here's the problem. Neither query() nor select() have any documented requirement on the state of the statement they return but we've implemented a requirement that they setup the query options with a fetch mode. (query isn't even documented at all other then a return type so double bad.)I don't think this is a documentation problem though. This setup smells. Most other interact with the result set from the query, the developer will fetch from one of the many fetch methods on the statement that exist explicitly to solve the fetch mode issue. Think
fetchAssoc
. The only reason we have this particular issue is we're trying to be clever/efficient and using the iterator behavior of the statement. That is fine but we need to enforce the fetch mode where we build the result set explicitly instead of relying on an implied behavior of other methods.Comment #8
neclimdulThinking about this more, specifically because the developer doesn't have the ability to modify
SelectInterface
, this could exclude a use case where the developer consumes a select query from some other query builder to populate their source. If that query builder has an object fetch mode the developer would have to do some sort of really complicated munging or entirely rebuild the query, or replaceinitializeIterator
which is a very large complicated bit of code they should probably not touch. All bad options.Comment #9
neclimdulSo... SqlBaseTest::testHighWater makes no assertions and doesn't really test anything. The only reason phpunit hadn't warned us about it is a random statement calls a mock method that counts as an assertion but has nothing to do with highwater.
I was tempted to delete the whole test but since we're not actually asserting anything about the
$query_result
this should work and is less invasive.Comment #10
phenaproximaNice find.
This, in my opinion, blocks Migrate API stability. Not sure if that means we should upgrade it to critical...I leave that decision to the committers. But I think it should be.
Comment #11
phenaproximaI'm glad to see the test passes. Great catch, and good fix. Thank you, @neclimdul.
Let's do a few things before this is RTBC:
Comment #12
rakesh.gectcrAssigning to myself for the roll
Comment #13
anpolimusComment #14
heddnComment #15
heddnHere's what I think was asked for in #11. Tests pass on local.
Comment #17
phenaproximaOne small thing:
initializeIterator() is returning an \IteratorIterator, not \ArrayIterator. Can this simply be changed to array|\Traversable? That should cover all bases.
Comment #18
heddnUpdated the docs.
Comment #19
phenaproximaLooks great. Ready to go when Drupal CI passes it.
Comment #20
Gábor HojtsyHm, now getIterator() will not get you an \Iterator?
Comment #21
heddnGood catch. I did some research of \Iterator vs \Traversable. We are actually still returning \Iterator. I'll fix that.
Comment #22
heddnIt is very obvious once you look at the docs of http://php.net/manual/en/class.iterator.php. We actually don't even support simple arrays; they must be wrapped in ArrayIterator or some such. Look at calls to getIterator(). It assumes things like next, rewind, current all exist. Or look at doCount. Where it obviously assumes an \Iterator.
Updated docs attached.
Comment #23
phenaproximaOkay, good point. Back to RTBC once Drupal CI goes green.
Comment #24
Gábor HojtsyThanks for fixing that, looks good to me now. Yay!