This new issue continued from post - https://www.drupal.org/node/2656548#comment-12182491.

First major problem observed after my move to PHP71, easily reproduced in my configurations.

Create a book node. Add some children. Try to edit and save the book node and I get this error...

Warning: Illegal string offset 'weight' in drupal_array_set_nested_value() (line 6781 of /..../includes/common.inc).
Error: Cannot create references to/from string offsets in drupal_array_set_nested_value() (line 6781 of /..../includes/common.inc).

Drupal does not white screen, but the page presented is the basic error only screen. I was initially concerned thinking that I broke things while creating book node content programmatically. However, the error occurs the same whether I create the book content manually or programmatically... and also when editing/saving every one of my book node content pages.

Also curious is that the error does not occur when logged in as user 1, but does occur when logged in as another user.

And likewise when I drop back to PHP56 the error goes away.

I am also using the course module and book_made_simple module which both tap into book hooks and may be contributing factors.

Articles online say this this error is usually caused by attempting to reference an array key index on a simple string.

What additional information can I provide to help discover the solution?

CommentFileSizeAuthor
#25 book-access-2897415-25.patch434 bytesDavid_Rothstein
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

webservant316 created an issue. See original summary.

webservant316’s picture

Issue tags: -php71 +PHP 7.1, +PHP 7.0 (duplicate)
webservant316’s picture

Issue summary: View changes
cilefen’s picture

Component: base system » book.module
Issue tags: -book
Parent issue: » #2656548: Fully support PHP 7.0 in Drupal 7
webservant316’s picture

Title: php 7.1 error with book node edit, Illegal string offset 'weight' and Cannot create references to/from string offsets in drupal_array_set_nested_value() line 6781 » php 7.1 error with book node edit, Illegal string offset 'weight' and Cannot create references to/from string offsets in drupal_array_set_nested_value() line 6781 /includes/common.inc
webservant316’s picture

@cilefen switched the component from 'base system' to 'book.module'. I will not change that back, but I was uncertain what the problem really was and was not convinced that there was an actual problem with the book.module, but instead that PHP71 requires stronger or more accurate variable 'typing' that must be fixed in the general purpose function in common.inc, drupal_array_set_nested_value().

Since the problem goes away when switching back to PHP56 I thought perhaps there is no real problem in the 'book.module' but instead in the base system.

Ayesh’s picture

Thanks for raising this issue. I tried to reproduce this (PHP 7.1.7, Drupal 7.56, with book_made_simple enabled, as a non-admin user), but I could not.

// PHP auto-creates container arrays and NULL entries without error if $ref
    // is NULL, but throws an error if $ref is set, but not an array.
    if ($force && isset($ref) && !is_array($ref)) {
      $ref = array();
    }

Above is from drupal_array_set_nested_value(). Notice the comment.
If you try to set/get an array index from a string, you will get this error, it does not seem to be version specific.

It looks like there is a problem with the book order array that it expects a "weight" key but it only got a string (most likely an empty string). Can you try to isolate the problem with book made simple module disabled, as admin user? A backtrace would be a lot of help (you can install the Devel module and set error handler "Krumo backtrace in the message area" in Devel settings in addition to the standard Drupal handler).

cilefen’s picture

Well, book nodes have weight but not ordinary page nodes so in a case like this I blame the book module first. I don't know how the fact that downgrading PHP fixes it is more evidence pointing to the base system.

But yes if a function in common.inc is wrong it will be base system.

webservant316’s picture

Okay sorry to raise the alarm. This problem is caused by the book_made_simple module. That module offers permission to hide the book weight field in order to simplify the UI for certain users. Apparently when doing this the book weight field does not then properly default.

I hope to look into this further and post back here and create an issue at book_made_simple once I figure out why this is the case and why it works in PHP56 and not in PHP71. It may still be that the drupal_array_set_nested_value() could be improved to handle cases when contrib modules do not properly initialize node variables.

Ayesh’s picture

Project: Drupal core » Book made simple
Version: 7.56 » 7.x-3.x-dev
Component: book.module » Code

I think we should still try to fix the problem.
I moved the issue to the book made simple module so its maintainers can take a look. I'll try to see what could be the problem too.

webservant316’s picture

I think it should be easily reproduced by anyone by installing book_made_simple and then visit the perms page and restrict the book_made_simple perm 'User will have all rights on books' from a user that has the right the create books. When this user then tries to edit/save a book the error will surface in PHP71 but not PHP56.

webservant316’s picture

The foul play happens in book_made_simple.module in book_made_simple_form_alter() beginning line 240.

book_made_simple provides permissions to take 'Book Outline' off the book content edit form vertical tabs. So that means that information is not available on the book node edit submit. However, I am not sure what the Drupal solution is for this.

Who is the guilty party?

I guess if book_made_simple takes that off the form it ought to also provide proper defaults.

However, maybe Drupal core book.module could also be better prepared for this.

Certainly, Drupal core drupal_array_set_nested_value() should also be better prepared for this and protected from contrib modules.

Still trying to understand this better, especially why PHP56 and PHP71 makes a difference.

webservant316’s picture

Okay, more info.

When book_made_simple enabled and is used to take 'Book Outline' off the book content edit form then book_made_simple_form_alter() is called and _book_add_form_elements() is NOT called from book.module. The _book_add_form_elements() function in book.module assigns two important defaults to the book content edit form which are then not assigned.

scratch that.

I viewed $form['book'] for the working and non-working case and the only difference is

working: $form[book][#type] => fieldset
non-working: $form[book][#type] => hidden

otherwise all the remaining element of $form[book] are the same.

oh, the non-working also includes an additional element $form[book][reorder], but that seems innocent enough

turning to devel next

webservant316’s picture

I have the devel backtrace, but how do I copy that from the message area to here?

webservant316’s picture

Okay I think I understand the problems now, but do not know the solution. There appears to be a PHP71 Drupal core problem and also a book_made_simple misuse of the book node edit form api.

FIRST, this code snippet works for PHP56 and not for PHP71

function drupal_array_set_nested_value(array &$array, array $parents, $value, $force = FALSE) {
  $ref = &$array;
  foreach ($parents as $parent) {
    // PHP auto-creates container arrays and NULL entries without error if $ref
    // is NULL, but throws an error if $ref is set, but not an array.
    if ($force && isset($ref) && !is_array($ref)) {
      $ref = array();
    }
    $ref = &$ref[$parent];
  }
  $ref = $value;
}

$array = array('book'=>'');
$parents = array('book','weight');
$value = 'test';
$force = FALSE;
drupal_array_set_nested_value($array, $parents, $value, $force);
print_r('Book as String');
print_r($array);

Explain the above and we will be on our way to understanding the Drupal side of this problem. What I don't understand in my use case / problem is that the $force argument is FALSE which seems to mean that really this should fail for both PHP56 and PHP71. So perhaps it is an accident that it is working for PHP56. Or maybe my PHP ini has a setting that is more permissive for PHP56.

SECOND, $array->book should be an array when this function is called, but is a string instead. As far as I understand it, $array->book is a string instead of an array because I use book_made_simple to change a book vertical tab form item to 'hidden'. Then the form is submitted and rebuilt through the following backtrace...

10: drupal_array_set_nested_value() (Array, 2 elements)
 9: _form_builder_handle_input_element() (Array, 2 elements)
 8: form_builder() (Array, 2 elements)
 7: form_builder() (Array, 2 elements)
 6: form_builder() (Array, 2 elements)
 5: drupal_process_form() (Array, 2 elements)
 4: drupal_build_form() (Array, 2 elements)
 3: drupal_get_form() (Array, 2 elements)
 2: node_page_edit() (Array, 2 elements)
 1: menu_execute_active_handler() (Array, 2 elements)
 0: main() (Array, 2 elements)

At this point since the book node vertical form elements were hidden, then the $form_state['input'] array is never assigned $form_state['input']['book']['weight']. So when drupal_array_set_nested_value() is called with the values demonstrated above... barf!

So in conclusion the Drupal include/common.inc function drupal_array_set_nested_value() performs differently for PHP56 and PHP71 in my configuration and is the reason that my use of book_made_simple when from working to not working.

webservant316’s picture

Changing line 2102 in drupal/includes/form.inc from this

drupal_array_set_nested_value($form_state['input'], $element['#parents'], NULL);

to this

drupal_array_set_nested_value($form_state['input'], $element['#parents'], NULL, TRUE);

fixes the problem and allows my use case to work in PHP56 and PHP71.

However, I don't know if that is the real fix. Drupal core experts?

cilefen’s picture

It is one of the backwards-incompatible changes or a PHP bug.

webservant316’s picture

Thanks. So any input on the best solution? Should this issue be moved back to the Drupal queue?

David_Rothstein’s picture

If it's specific to PHP 7.1 (rather than PHP 7.0) then #17 has the wrong link - should be http://php.net/manual/en/migration71.incompatible.php instead.

It's not obvious to me from that page what causes it, but #2832900: PHP 7.1 no longer converts string to arrays the first time a value is assigned with square bracket notation could be the same thing. There is clearly some kind of difference in PHP 7.1 regarding what happens if your code treats a string like an array, and it sounds like it might go deeper than what the PHP documentation says.

webservant316’s picture

Seems to me this issue needs to move back to Drupal core / base system since drupal_array_set_nested_value() is a base system function which performs differently between PHP56 and PHP71. That is a core issue. I would have moved the issue back myself, but wasn't sure which Drupal project to pick from the pull down.

David_Rothstein’s picture

I think the primary bug here is in book_made_simple - it is trying to transform multiple form elements to hidden by setting $form['book']['#type'] = 'hidden' on the fieldset element which contains them, but that doesn't really work. It should either mark each individual element as hidden, or (more likely) just set $form['book']['#access'] = FALSE to prevent all of them from appearing on the form in the first place.

For core, we could maybe use an issue to clarify the documentation, but I don't think there's an actual bug - it documents that you can get a PHP error if the $force parameter is FALSE and you pass something in with the wrong structure, and that's exactly what's happening.

In particular, I tested a few scenarios:

  1. $array = array('book' => NULL);
    $parents = array('book', 'weight');
    $value = 'test';
    $force = FALSE;
    drupal_array_set_nested_value($array, $parents, $value, $force);
    

    Works on all versions of PHP.

  2. $array = array('book' => '');
    $parents = array('book', 'weight');
    $value = 'test';
    $force = FALSE;
    drupal_array_set_nested_value($array, $parents, $value, $force);
    

    Gives an error on PHP 7.1, but works on other versions of PHP.

  3. $array = array('book' => 'something');
    $parents = array('book', 'weight');
    $value = 'test';
    $force = FALSE;
    drupal_array_set_nested_value($array, $parents, $value, $force);
    

    Gives an error on all versions of PHP.

So maybe the documentation that says this:

    // PHP auto-creates container arrays and NULL entries without error if $ref
    // is NULL, but throws an error if $ref is set, but not an array.

could be tweaked to indicate that some PHP versions are lenient with empty strings as well as with NULL - if we want to get into that level of detail?

Thanks for all the good investigating on this issue!

webservant316’s picture

if book_made_simple sets $form['book']['#access'] = FALSE will the Drupal form processor then still assign the default value for all the contained fields on update?

webservant316’s picture

Okay, understood. Thanks.

Unfortunately this module does not seem to be well maintained, so I need to decide whether to limp along with a custom patch or find an alternative.

This module has an excellent feature of presenting an add child page button at the bottom of book and child pages so that novice content creators can create books super easily. I wonder if there are other alternatives?

David_Rothstein’s picture

if book_made_simple sets $form['book']['#access'] = FALSE will the Drupal form processor then still assign the default value for all the contained fields on update?

Yes, it should automatically preserve all existing values on those fields.

David_Rothstein’s picture

Status: Active » Needs review
FileSize
434 bytes

Here's the code change as a patch. (I haven't tested it at all.)

webservant316’s picture

Thanks for the patch offer. However, after the radio silence from book_made_simple maintainers in the queue and email I decided to hack a quick fix. I just liked the access to book reordering from a tab on the book and also expanded links to add children of various contents types from the book. I never understood why Drupal core only allows you to choose one content type for the add child link when many content types might be added as a child.

Note, if the maintainers do not follow up, this module is broken for PHP71.

It is like you say this modules fault and not Drupals. However, I do still find the different performance of drupal_array_set_nested_value() between PHP56 and PHP71 disturbing. Seems like that should be visited so that the performance is the same. Really book_made_simple should have been breaking for PHP56. I wonder if any other code would be impacted when moving to PHP71.

Anyway here is my replacement code for the book_made_simple functions that I needed for any followers. I also used the 'prepopulate' module to help make my code below pretty simple.

function custom_menu() {
	$items = array();
	$items['node/%node/book/parent'] = array(
		'title' => t('Book Home'),
		'page callback' => 'custom_menu_book_parent',
		'page arguments' => array(1),
		'access callback' => 'custom_menu_book_parent_access',
		'access arguments' => array(1),
		'type' => MENU_LOCAL_TASK,
		'weight' => 40,
	);
	$items['node/%node/book/children'] = array(
		'title' => t('Book Pages'),
		'page callback' => 'custom_menu_book_children',
		'page arguments' => array(1),
		'access callback' => 'custom_menu_book_menu_access',
		'access arguments' => array(1),
		'type' => MENU_LOCAL_TASK,
		'weight' => 41,
	);
	$items['node/%node/book/add/page'] = array(
		'title' => t('Add Page'),
		'page callback' => 'custom_menu_book_add_page',
		'page arguments' => array(1),
		'access callback' => 'custom_menu_book_menu_access',
		'access arguments' => array(1),
		'type' => MENU_LOCAL_TASK,
		'weight' => 42,
	);
	$items['node/%node/book/add/poll'] = array(
		'title' => t('Add Poll'),
		'page callback' => 'custom_menu_book_add_poll',
		'page arguments' => array(1),
		'access callback' => 'custom_menu_book_menu_access',
		'access arguments' => array(1),
		'type' => MENU_LOCAL_TASK,
		'weight' => 43,
	);
	$items['node/%node/book/add/h5p'] = array(
		'title' => t('Add H5P'),
		'page callback' => 'custom_menu_book_add_h5p',
		'page arguments' => array(1),
		'access callback' => 'custom_menu_book_menu_access',
		'access arguments' => array(1),
		'type' => MENU_LOCAL_TASK,
		'weight' => 44,
	);
	$items['node/%node/book/add/webform'] = array(
		'title' => t('Add Webform'),
		'page callback' => 'custom_menu_book_add_webform',
		'page arguments' => array(1),
		'access callback' => 'custom_menu_book_menu_access',
		'access arguments' => array(1),
		'type' => MENU_LOCAL_TASK,
		'weight' => 45,
	);
	return $items;
}
function custom_menu_book_parent_access($node) {
	if (!empty($node->book['bid']) && $node->nid!=$node->book['bid']) { return TRUE; }
	return FALSE;
}
function custom_menu_book_parent($node) {
	drupal_goto('node/'.$node->book['bid']);
}
function custom_menu_book_children($node) {
	drupal_goto('admin/content/book/'.$node->book['bid'].'/reorder', array('query' => array("destination" => "node/".$node->nid)));
}
function custom_menu_book_add_page($node) {
	drupal_goto('node/add/page', array('query' => array('edit[book][bid]' => $node->nid)));
}
function custom_menu_book_add_poll($node) {
	drupal_goto('node/add/poll', array('query' => array('edit[book][bid]' => $node->nid)));
}
function custom_menu_book_add_h5p($node) {
	drupal_goto('node/add/h5p-content', array('query' => array('edit[book][bid]' => $node->nid)));
}
function custom_menu_book_add_webform($node) {
	drupal_goto('node/add/webform', array('query' => array('edit[book][bid]' => $node->nid)));
}
webservant316’s picture

Priority: Normal » Major

Changing to major since book_made_simple misuses the form api and is broken for PHP71.

dgtlmoon’s picture

#2313517: Cannot create references to/from string offsets nor overloaded objects here's a core issue reported but needs more information, it's hard to say what the cause is, but if someone could chime in on that core issue, would be interesting to hear..

dgtlmoon’s picture

@webservant316 what exactly version of 5.6 are you running? I can confirm this on 5.6.33 ..

webservant316’s picture

I am now fully on PHP71 and have abandoned the book_made_simple module. I may not be much further help here. I found it easier to maintain the small code in #26 for my needs than to continue working with book_made_simple.

Best wishes.

osopolar’s picture

Is this issue still valid? I am updating one site which uses Book made simple to php 7.1. I didn't get the reported error, although I made sure that the code $form['book']['#type'] = 'hidden'; got executed.

johnennew’s picture

Status: Needs review » Reviewed & tested by the community

Patch in #25 fixed it for me.

osopolar’s picture

@johnennew: Did you see the error before applying the patch or did you, or did you apply the patch, just in case it might be necessary and just observed the absence of the error?

MustangGB’s picture

joseph.olstad’s picture

Issue tags: +Drupal 7.66 target
RedEight’s picture

#25 fixed the issue for me as well once I finally tracked it down.

indigoxela’s picture

Patch #25 fixes the problem for php7.2, too.

Although this module is marked as getting "No further development", I still hope for a bugfix release.

mcdruid’s picture

Issue tags: -Drupal 7.66 target

As this is for a contrib module and not Drupal core, I'm removing the "Drupal 7.6* target" tag.

If I've misunderstood and it should indeed be there, it'd now be "Drupal 7.68 target".

indigoxela’s picture

I can still confirm that patch #25 fixes the php compatibility issue (this time with 7.3). Still hoping for this crucial fix to get merged and released.

laryn’s picture

Thanks to all involved in this issue over the years.
Merged in the actively-being-ported Backdrop version.