NOTE: A review of the issue queue and multiple efforts to search via Google did not turn up any relevant results to my issue. All issues that came up regarding this were from years ago and not relevant to unsupported operand types.
Running PHP 5.5.14 with Drupal 8 locally. Requirements state PHP 5.5.9 and above so I should be covered. I was receiving the below fatal error after migrating a site from D6 to D8. Sorry I don't have exact steps to reproduce, but I think the issue is a pretty clear one just reviewing the code.
Fatal error: Unsupported operand types in /Users/jessenicola/projects/jessenicola.com/docroot/core/lib/Drupal/Core/Utility/LinkGenerator.php on line 153
The code on line 153:
$attributes = array('href' => '') + $variables['options']['attributes'];
As you can see, it's trying to bring two arrays together just using +, when array merge seems to really be the correct way of accomplishing this.
This code resolves the issue for me:
$attributes = array_merge(array('href' => ''), $variables['options']['attributes']);
Steps to reproduce are rather prohibitive for the casual issue queue goer, but are as follows:
- Create a Drupal 6 site.
- Use CCK, and link modules.
- Create a content type with links.
- Create content with links (Devel generate is advised).
- Port site from D6 to D8.
- Attempt to view content, enjoy fatal error.
Comment | File | Size | Author |
---|---|---|---|
#2 | link_gen_array_merge-2596937-1.patch | 704 bytes | jnicola |
#36 | 2596937-36.txt | 3.09 KB | lokapujya |
#59 | link_gen_array_merge-2596937-59.patch | 723 bytes | floydm |
#64 | 2596937-64.patch | 630 bytes | dawehner |
#65 | array_merge_failure_on-2596937-65.patch | 719 bytes | wolffereast |
Comments
Comment #2
jnicola CreditAttribution: jnicola as a volunteer commentedThis patch resolves the issue for me and seems to be the correct way to handle bringing two arrays together.
Comment #3
jnicola CreditAttribution: jnicola as a volunteer commentedComment #4
dawehnerYeah I think when we fix something here we might should have a test for it.
Comment #5
longwaveI am not even sure why we need a placeholder for 'href' in the array, as it is explicitly set in both branches of the subsequent if() statement anyway.
The actual problem here seems to be that $variables['options']['attributes'] is something other than an array in this case.
Comment #6
jnicola CreditAttribution: jnicola as a volunteer commentedI do agree that there is an issue where $variables['options']['attributes'] is something other than an array.
I do think my solution resolves for this though, as array_merge handles the situation gracefully while "+" errors out due to mismatched types.
As for creating a test for this... I've never made one before! Could I partner with some one to work on a test for this?
Comment #7
dawehnerThat seems to be a workaround to be honest. If someone passes in crap data it is totally fine to have a notice, IMHO.
Comment #8
mashermike CreditAttribution: mashermike at Genuine commentedUp until line 149 the $variables['options']['attributes'] is guaranteed to be an array because it was explicitly set above. But on line 149 there is a link alter called which presumably the $variables['options']['attributes'] could be set to anything. I think that making the assumption that 3rd party developers have not altered the data "incorrectly" is maybe a bad idea, but it could also be difficult to react to all possibilities. Every call to any item in $variables could return an error if it was unset. I can't imagine that this is the first time Drupal has had to deal with this issue. Is there a standard way of handling this?
Comment #9
jnicola CreditAttribution: jnicola as a volunteer commentedJust to be clear, dawehner, you say "it is totally fine to have a notice". I'm getting a fatal error. I agree, notices and warnings for meh data makes sense.
I am seeing this warning now in it's place in a few select places howeve (quite literally just finding this):
Warning: array_merge(): Argument #2 is not an array in Drupal\Core\Utility\LinkGenerator->generate() (line 154 of core/lib/Drupal/Core/Utility/LinkGenerator.php).
So offhand it would seem my solution is in alignment with your humble opinion, as it complains, but not fatally, and proceeds to work.
Comment #10
dawehnerIf the developer made a mistake IMHO telling the, is absolutely the right thing to do.
If you care about those level of things on production you can turn it off, but the more we tell developers the better.
For this particular issue it might make sense to use an assert() here.
Comment #11
jnicola CreditAttribution: jnicola as a volunteer commentedIt seems pretty clear to me that this is a great case for a warning. It covers informing the developer, can proceed without downstream issues, and still allows the site to continue onwards.
Can you elaborate on a reason why this should be a fatal error instead of warning?
Comment #12
dawehnerWell fair, I think we should add an asset which then informers the developer and stops early.
Comment #13
jnicola CreditAttribution: jnicola as a volunteer commentedSo the combined attributes aren't essential to a link as far as I can tell. Is there a reason to halt anything just because a non-essential piece isn't in play?
I think the warning route really just makes sense given that the combined attributes are non essential.
Comment #14
dawehnerJust moving ...
Comment #15
longwaveWhat is the value of $variables['options']['attributes'] when it is not an array? What are the other properties of the link that is being generated? If this is caused by a migration from D6 then this suggests a bug in the migration code that should be fixed at source. Is it possible to trigger this from a fresh install of D8 without writing custom code?
Comment #16
ultimikeI hit this exact same issue and tracked it to an issue with the migrating of link fields from Drupal 6 to Drupal 8.
I used the migrate_plus /upgrade to migrate a D6 site into D8. One of the content types that was migrated had a link field. Post-migration, in Drupal 8, when the node is rendered, I used my debugger to find that the value of $variables['options']['attributes'] was "false" - clearly not an array. This is what leads to the error on line 152:
$attributes = array('href' => '') + $variables['options']['attributes'];
I'm guessing that there is an issue with link migrations where if there are no attributes on the source site, the default value is "false" rather than an empty array.
As further proof, if I edit and save the same node in D8, the error goes away (I'm assuming that D8 correctly sets it to an empty array).
-mike
Comment #17
jnicola CreditAttribution: jnicola commentedIt's nice to know you're not the only one encountering such a problem :)
I'm still not sure why the code change I proposed isn't acceptable. Attributes are non essential and there's no reason they should throw a fatal error when a warning can take it's place.
The migration should ALSO be fixed, but I think this also points out a very clear problem with the code making it fragile.
Comment #18
ultimikeComment #19
gnugetI think that line of code is wrong even if it is not checking if the
$variables['options']['attributes']
is an array.Accord with the Array operators.:
If we have something like this:
Then the
$attributes
variable will have these values:So, it basically is overwritting the href value of the original attributes with an empty string, so indeed this needs to be an array_merge or switch the order of the params.
You can see the proof here: https://3v4l.org/W6J8f
I want to work on this, so I will assign the ticket to me.
UPDATE: Ignore this comment, the href value is get it from the $url object a few lines below, that line just make sure to that key exists in the array.
Comment #20
gnugetComment #21
jnicola CreditAttribution: jnicola commentedI sure hope this gets fixed! It would be nice if you used my patch so as I could get a commit on Drupal core as well :)
Comment #22
gnugetI studied the code and these are my findings:
Let's assume to we have
false
as the value of$variables['options']['attributes'];
With our current code we get this:
I think this is not the ideal behaviour, because this kill our site.
This is what we get using the patch of [#2]:
We still get warnings but the links are render fine, so while this is better (because we can just hide the errors in our production site) I think this is not ideal either.
My proposal is just to get rid of the the href place holder:
The
array('href' => '')
is not even necessary because below that attribute is defined:So removing that placeholder we won't get any warning/fatal errors, and the links without attributes will just render fine and all will continue working as expected.
I just attached a patch and the interdiff to this comment with my proposal.
Also, I have an idea about how to write the test but first I want to be sure if this is the behaviour to we want.
Comment #23
gnugetI will change temporarily the status of the issue to see if the testbot have complaints with my patch.
Comment #25
gnugetThis fails because the order of the attributes changes :-/
Update-rewrite the tests to me is a big change and now I think I would go with #2
Comment #26
jnicola CreditAttribution: jnicola commented#2 sure does the trick and rather simply at that :) I'm obviously biased though!
Comment #27
el1_1el CreditAttribution: el1_1el commentedhad the same problems after a d6 migration.
patch 2 allows me to run drush ms without error or warning.
patch 22 creates new issues:
Illegal string offset 'href' LinkGenerator.php:159 [warning]
Invalid argument supplied for foreach() Attribute.php:87 [warning]
Comment #28
Rewted CreditAttribution: Rewted commentedDrupal 8.0.5 getting a similar error on every page load.
PHP Fatal error: Unsupported operand types in /var/www/d8/core/lib/Drupal/Core/Utility/LinkGenerator.php
Comment #29
chrisroane CreditAttribution: chrisroane commentedConfirmed that Patch #2 solved the issue for me. I had upgraded from D6 to D8. Thanks!
Comment #30
jnicola CreditAttribution: jnicola commentedMarking this to "reviewed and tested by the community" cause we've now had three people review this as fine in a row now. It's an issue that exists, this is a solution, and the initial code to begin with is vulnerable to critical arrays where the solution in patch #2 throws an appropriate level of notice concerning the issue. There is no reason that has been presented that this needs to be a critical error instead of a warning or notice.
Comment #31
alexpott@jnicola it's not best practice to rtbc your own patches, as that misses the idea that stuff is reviewed and tested by someone other than the patch creator. Also we need a test to ensure we don;t introduce this bug again.
Comment #32
jnicola CreditAttribution: jnicola commentedCan you link me to something stating the mandatory need with no exceptions for a test? Reproducing this has proven rather difficult as many steps are involved. The mutlitude of steps involved are common in D6 to D8, but again difficult to capture in a test.
I suppose good point on marking your own thing as reviewed and tested by the community. If we can resolve the above I'll request one of the previous people with whom this worked adjust it.
I'm still perplexed this is held up. It's such a simple fix, with no defense what so ever that the current code is of any superior merit.
Comment #33
floydm CreditAttribution: floydm at Affinity Bridge commented+1 on "this patch is necessary and good, ship it" and +1 failing to see why the lack of a test would hold off a minor syntax change that fixes a fatal error effecting numerous users. There is no change in behaviour or interfaces here.
Comment #34
jhedstromThe proper test for this would be to create a situation where
$variables['options']['attributes']
is not an array, then the fatal error would be demonstrated. I'm not sure of the utility of such a test though, as even with this fix in place, the test will fail sincearray_merge
will throw a warning.Comment #35
gnugetComment #36
lokapujyaTried adding a test like in #34. Quickly copied one of the other tests and started modifying.
EDIT: (to explain the sample)
Here is the line that sets the $variables:
$this->moduleHandler->alter('link', $variables);
The reason that I didn't try to mock the alter() function is that I don't know if it is possible to alter the $variables parameter in a mocked function. That is why I added a new function:
$variables = $this->moduleHandler->returnMyself($variables)
;In normal execution, the new function doesn't alter
$variables
. For the test, it will be mocked and will set$variables['options']['attributes']
to a non array.Some problems:
1. Had to add a function to moduleHandler so that there was a way of altering $variables. The function returns the object passed, but for tests the function gets mocked and replaces
$variables['options']['attributes']
with a non array. There is got to be a better way (to modify the $variables variable).2. It's not completing tests. For now, I have that part that sets it to a non array commented out, so it should return itself, but it still fails to finish the test.
3. Even if that test were working, it's going to throw a warning.
4. This part might be the wrong way to mock the function:
->will($this->returnCallback(function($object) {
Comment #37
jnicola CreditAttribution: jnicola commentedSome one other than me mind just marking it tested and reviewed by the community? We've investigated the possibility of a test, it's not possible. This clearly fixes a fatal error and clearly fixes a syntax error to throw a far more appropriate warning/notice than an error.
Comment #38
alexpottSee https://www.drupal.org/core-gates#testing and given that we're exchanging a fatal for a warning it seems all we are doing is treating symptoms rather the cause. Can someone post some steps to reproduce - i.e. how can we cause this problem on a real Drupal site?
Comment #39
jnicola CreditAttribution: jnicola commented@alexpott: Can you specifically come up with any reason why this code should throw a fatal error and not a warning?
I would argue that this code SHOULD throw a warning. A warning surely belongs here for this non-essential value. No one has postured a reason for a fatal error when the attributes (a non-essential value) are empty. Pretty damn confident it's because there is no good reason!
While the issue triggering this specific problem occurs somewhere further up the line, this issue has no demonstration of any reasonable cause to retain the current code (performance, coding standards, etc).
I would argue that no, it is not treating the symptom, and not the cause, it is addressing a coding syntax error that is prone to non-essential fatal errors. Who is to say that fixing the cause of this particular issue won't later result in some other piece of code triggering the same issue later down the line?
Steps to reproduce are rather prohibitive for the casual issue queue goer, but are as follows:
Comment #40
alexpott@jnicola thanks for to reproduce this hints that we have a problem with the Drupal 6 to 8 migration - we need to work out what's going on there. This is an important problem to fix.
Comment #41
floydm CreditAttribution: floydm at Affinity Bridge commentedA different though similar fatal error in D6->d8 link migration: https://www.drupal.org/node/2698083
Comment #42
jnicola CreditAttribution: jnicola commented@alexpott I asked a direct question:
What argument can be made that this SHOULD throw a fatal error over a warning?
Comment #43
alexpott@jnicola Drupal should not produce any warnings or errors after such a migration.
Comment #44
gnuget@jnicola
The answer is none BUT we MUST fix the cause of this error not just the symptom, the first thing we need to do here is make a failing test for expose the bug and start working in a patch for fix it.
Comment #45
jnicola CreditAttribution: jnicola commentedSo you propose instead of fixing fragile code, we retain the fragile code and instead fix what causes the fragile code to break?
This is insanity. No wonder D8 took so much longer than previous Drupal versions to launch and left the gate in such a shamble.
Comment #46
alexpottSo looking at the code in CckLink migration process plugin we have:
It looks like this code is failing us.
I've installed Drupal 6, enabled CCK and link and added a link field to the page content type. I selected the least options I could - i.e. no link title and created some content with a link. I then performed the migration to Drupal 8 and the link came through and the content worked as expected.
@jnicola is there any chance you have the original database values for the link fields that is causing the issue?
Mine is...
Comment #47
catchTagging as migrate critical.
Comment #48
floydm CreditAttribution: floydm at Affinity Bridge commented@alexpott If you set the link url to "test" it'll result in a fatal error post-migration, but that is a different fatal error, the one I reported on #2698083. I verified today on 8.1 HEAD that it still exists.
Comment #49
alexpottComment #50
alexpottAfter reading #2633568: Improve method for migrating link attributes from D6 and seeing that that issue was created around the same time as this one and was committed on March 15th 2016 - maybe the migrate part of this issue is already fixed.
Personally I don't care whether we trigger a recoverable or fatal error here because anything that is populating
$variables['options']['attributes']
with something other than an array is breaking the contract of the theme function and is broken. We don't validate the returns of alter hooks in many places.Comment #51
alexpottHowever given that migrate was broken and making the alter hook return FALSE for the attributes maybe we could change the code to specifically test for that and carry on.
Comment #52
benjy CreditAttribution: benjy at PreviousNext commentedAs @alexpott says, we could fix this in core just so we don't have to write some nasty looking update hook or tell everyone to re-migrate.
I think it's most likely that #2633568: Improve method for migrating link attributes from D6 fixed the issue but it's possible that unserialize returned something truthy, but not an array. Maybe we should replace
with
Comment #53
alexpottLooking at all the code involved with this I think we should consider ensuring that attributes in an array in LinkItem::setValue() because that should fix sites which have already migrated prior to #2633568: Improve method for migrating link attributes from D6 landing and also ensure the link items present a valid options array even if the link generator is swapped out or the options are used somewhere else.
Comment #54
jnicola CreditAttribution: jnicola commentedI had considered the code in #50, however I feel strongly that was just covering up that something somewhere else was returning incorrect data. If something further up the line is wrong, but is non-essential, a warning is highly appropriate.
Comment #55
catchWe should avoid the fatal error here, but trigger_error() with a warning or throw a proper exception rather than just erroring out. Drupal 6 has similar fatal errors if the $options argument to l() is NULL instead of an array (which ironically happened a lot on sites updated from Drupal 5).
Comment #56
catchSince migrate was already fixed, removing tag.
Also moving back to routing.
Comment #57
NewZeal CreditAttribution: NewZeal at Passing Phase Web Development commentedPatch #2 works for me as well.
Comment #59
floydm CreditAttribution: floydm at Affinity Bridge commentedUpdated patch #2 to apply to Drupal 8.2.0.
Comment #60
dawehnerSo when attributes is not an array this is clearly a bug. Its kind of an implementation detail that
array_merge()
seems to work with other properties as well.What about adding a
assert()
statement to ensure that the bug is caused in testing/development environments, but maybe still use this patch, so runtime systems do stumble upon this particular bug.Comment #61
jnicola CreditAttribution: jnicola commentedAssert would appear to work, but is additional unnecessary code (a slight performance hit and cruft) just to work around a problem Drupal as a community has created it would seem. It also appears that it would cover up a very sensible warning that belongs there.
I would just like to clarify that I think array_merge() works by more than just happenstance. PHP isn't strict typed as we all well know, so when using operators PHP attempts to make assumptions based on what types it has been presented. Array merge however makes no assumptions, it attempts to work with two arrays, and if the second param isn't an array it appropriately handles it (warning) instead of throwing a fatal error.
This issue has existed for a little over a year now. No one has been able to make any argument as to why leaving the current operator is superior. I'm pretty confident at this point no argument can be made.
Surely there has to be some way to get code into D8 that, in the absence of a proper non-essential variable, throws a warning instead of a fatal error...
Comment #62
el1_1el CreditAttribution: el1_1el commentedThere is a way. I have to patch core on every upgrade. Good times!
Comment #64
dawehnerI'm wondering wether we should have some assertions to ensure that people don't pass in NULL or so in there?
Comment #65
wolffereast CreditAttribution: wolffereast commentedTriaged.
Patch no longer applies to 8.4. Rerolled.
Comment #66
malcomio CreditAttribution: malcomio as a volunteer and at Capgemini commentedComment #69
drupalfan81 CreditAttribution: drupalfan81 commentedGod, reading through this post is depressing. I have let my D6 site sit on D6 forever because D7 migration was awful and just didn't work for me. With Google no longer feeding me traffic because i don't have mobile ready site, because it's sitting on D6 and I can't be bothered to make a poor investment into an unsupported platform, I decided to try upgrading to D8 this past week.
Used Drush to move all 5000 nodes of my content type which includes a Web Link field. Half of my content views just fine, the other half throws the white page, and with error messaging on, reveals:
Fatal error: Unsupported operand types in /var/myserver/path/core/lib/Drupal/Core/Utility/LinkGenerator.php on line 153
Has this issue still not been fixed? I honestly can't be bothered to go through and rebuild this site from scratch. If I have to do that, I'm thinking to just use Node_export on D6 to export everything as CSV files and have a go at Joomla. I was a big user of D6 and had many sites built on it, it just seems things have been down hill from there. Sad ;(
Comment #70
el1_1el CreditAttribution: el1_1el commentedJust run the patch. I'm still using a modified version of git apply link_gen_array_merge-2596937-1.patch that i run on every upgrade. gone from 8.0x->8.3x without issue
Comment #71
jnicola CreditAttribution: jnicola commented@drupalfan81 I entirely feel your pain and frustration. I created this issue and posted a solution for this that is entirely sensible and functional two years ago now! There's absolutely no reason anyone has quantified in two years for the original code to be retained over the replacement code, just that the replacement code isn't good enough... which is funny because the original code is obviously/blatantly worse...
Thanks to everyone keeping the patch rolling forward as Drupal 8 progresses. Glad it remains a working solution over core for two years running.
I'm going to mark this as 'reviewed and tested by the community" again give it's now two years strong on being the goto solution. That said, I suspect it will get shot down yet again in favor of some inane adherence to strict coding practices, despite the reality being that this fixes a problem that plaguee people using Drupal longer than the person flagging it as unacceptable more than likely, heh.
Progress not perfection.
Comment #72
xjmSorry about the frustration everyone.
I'd also agree with #60; the fact that the
array_merge()
works when HEAD doesn't is a fragile way of fixing it, and the actual bug lies with the caller. (If you're one of the people applying this patch to your site, you might consider debugging where the bad input is coming from, which is also what reviewers are trying to make sure you can do.)That said, I'd consider a a hotfix and a @todo/followup for a more robust fix and the
array_merge()
, given the persistence of the issue.Even if we commit it as a hotfix, though, we do still at minimum need an automated test for what we expect to happen. This isn't inanity -- it's standard practice to ensure this doesn't break again in the future.
P.S. -- I've used Drupal for 12 years; infer what you will.
Comment #73
jnicola CreditAttribution: jnicola commentedIf you can elaborate on a way to write a test that accepts a warning over an error, I'm all ears. To my understanding Warnings/notices were unacceptable.
Comment #74
benjy CreditAttribution: benjy commentedTake a look at
\Drupal\Tests\Core\Utility\LinkGeneratorTest
which is a unit test specifically for that class, there are a few examples in there that should point you in the right direction.Given that
$variables['options']['attributes']
is initialised within the generate() method and$variables
is local to the method then it can only be someone implementing the alter hook triggered by$this->moduleHandler->alter('link', $variables);
that causes the invalid attributes.Something like this will trigger the same error in a test.
Comment #78
Nebel54I also experienced this problem during the migration from D6 to D8 (yes in 2019 :)). I created an issue/patch to prevent the migration to save an invalid string over here: #3045211
Comment #79
isa.belHello,
We were facing this issue when trying to access the sitemap page, that is being done with the sitemap module.
I've tried to apply the patch from #65, it partially solve the issue, but it was throwing this error after it was applied:
array_merge(): Argument #2 is not an array in Drupal\Core\Utility\LinkGenerator->generate()
So, I've made a change on the patch and will post it here. Hope it should be useful for someone.
Thanks
Comment #80
isa.belSorry, forgot about the space after the 'if'
Comment #81
jibranThanks, for working on the patch.
I think adding
is_array
makes sense here but changing+
toarray_merge
is not quite right. We don't always want to merge stuff e.g. https://3v4l.org/YhXaN. Let's revert it back to+
so it doesn't break BC.Comment #82
jnicola CreditAttribution: jnicola commentedPretty sure that + in the context of two arrays performs an array merge, hence we got into this whole mess because the error is that it receives two different variable types and doesn't know what to do with them (array and NULL).
I wrote a test for this a while back with some people on my team and we had it fixed. It's since been misplaced but it would be quick to rewrite. If anyone wants to point me towards how to submit tests with bug fix patch submissions, I can get something legit for this going.
Comment #83
isa.belActually the
+
and thearray_merge
are almost the same, but there is a difference between them, as pointed out on this comment on the PHP manual to this function: https://www.php.net/manual/en/function.array-merge.php#92602I've tested here and adding the check
is_array
and using+
it doesn't throw errors anymore.I've attached the patch if you want to test, for me it worked properly. Hope it does for you too.
Comment #84
ezra CreditAttribution: ezra commentedI ran into this issue on a site I inherited that a previous developer had migrated from D6 to D8. I don't know why, but it was dormant until recently when it started causing errors and white screening the offending content page. I took a different approach, cleaning up the data from the myself prompt.
I thought the command might come in handy for someone else. You might have to change the filed name to match your own DB.
UPDATE node__field_external_link SET field_external_link_title="", field_external_link_options='a:0:{}' WHERE isnull(field_external_link_title);
Comment #85
joelpittetI ran into this as well recently and I think this has been addressed in the migrate system for me at least.
The data from D6 was getting migrated with double encoding of the attributes.
I rolled back the migration and reran it and seems to be fixed now!
\Drupal\link\Plugin\migrate\process\attributes
I agree the current patch though nice, though doesn't fix the problem where the data is incorrect in the database.
I'm going to mark this as fixed/duplicate by #2897254: URLs without http:// are broken after migration from d6 or d7 and #3045211: Prevent link field migration from creating invalid link attributes
Comment #86
philltran CreditAttribution: philltran at Symmetri Technology commentedJust posting an updated patch to squash the error while I dig through the DB for the permanent fix.