Support for Drupal 7 is ending on 5 January 2025—it’s time to migrate to Drupal 10! Learn about the many benefits of Drupal 10 and find migration tools in our resource center.
Problem/Motivation
Currently the migrate\process\Log
class is only able to log string values, not even NULL
are accepted, this is pretty limiting because often the log plugin can be used for quick debug of complex migrations.
The solution is imho simple, if the value is not a string pass it through print_r
and store the dumped value.
Proposed resolution
- Use print_r when the value
- is an object with a toString or toArray or __toString method
- is a string, numeric or array
- Use var_export for NULL and boolen
- use sprintf for floats
follow up made to improve logging for XML #3182296: Improve logging of SimpleXML elements in the Log process plugin.
Remaining tasks
Commit and smile
Comment | File | Size | Author |
---|---|---|---|
#74 | 3047328-74.patch | 9.4 KB | quietone |
#74 | interdiff-71-74.txt | 2.65 KB | quietone |
#71 | 3047328-71.patch | 9.16 KB | quietone |
#71 | interdiff-63-71.patch | 1.85 KB | quietone |
#63 | 3047328-63.patch | 9.2 KB | quietone |
Comments
Comment #2
esolitosPatch with updated tests attached.
Comment #3
esolitosComment #4
efpapado CreditAttribution: efpapado at Ramsalt Lab commentedI suggest that you take advantage of the
__toString()
method that an object might have.According to the comments here https://www.php.net/manual/en/function.is-string.php#121637 the
is_string()
will returnFALSE
even if there's a__toString()
I'm proposing a small improvement.
Comment #5
quietone CreditAttribution: quietone as a volunteer commentedNice idea and tests updated, great.
This can be simplified with var_export($value, TRUE). For examples, see the Concat and Explode process plugins in the migrate module.
Comment #6
vadim.hirbu CreditAttribution: vadim.hirbu at FFW commentedFixed php and code sniffer errors. Use
var_export
inside ofprintr()
.Comment #7
esolitos+1 on the `var_export`, seems the right idea.
We should probably add a test for this case as well.
Using
TranslatableMarkup/FormattableMarkup
should work just fine, right?Comment #8
quietone CreditAttribution: quietone as a volunteer commentedSeems like a good idea to me.
Changed the name of the data provider to not begin with 'test'. Added a simple test with an object. Added some docs.
Comment #9
mikelutzThis would rather break the plugin's purpose (and almost all migrations using it) as it's currently implemented. The purpose of this plugin is to take an input value, log it, and then pass it through unchanged. Now the patch is changing it, and returning the changed (exported string) value. So any chain of process plugins that use non string values that have the log plugin injected somewhere in the middle would break.
The plugin docs state:
Yet the new test specifically tests that the values are being changed.
At a minimum, the plugin need to log the exported value, but still return the original value
Comment #10
heddnAdditionally:
For kicks and giggles, can we add scenarios for a Url, Node and FormattableMarkup?
Here and below, can you we add array keys that correspond with the variable names passed into
testLog
? Which would maybe make us want to rename the variables. I like$actual_message
and$expected_message
.Comment #12
Wim Leers#9 and #10 contain sufficiently clear instructions for a novice to take this on I think :)
Comment #13
quietone CreditAttribution: quietone as a volunteer commentedAdding DrupalSouth 2019 tag
Comment #14
dinarcon CreditAttribution: dinarcon at Agaric commentedI am working on this as part of the DrupalSouth 2019 contribution day.
Comment #15
dinarcon CreditAttribution: dinarcon at Agaric commentedHi,
The attached patch includes suggestions from #9 and #10. One thing to note is that, if the `log` plugin returns the value verbatim, the test might not be accomplishing its intended purpose. As of now, the test checks if the returned value is the same as the one sent to the plugin. But I think the intent of test is checking for the *logged* value, which is different from the *returned* value. Would we have to actually read the messages table?
@heddn, when you say add scenarios for Url, Node, and FormattableMarkup do you mean Drupal\Core\Render\Element\Url, Drupal\node\Entity\Node, and Drupal\Component\Render\FormattableMarkup? If so, could something like Drupal\Tests\migrate_drupal\Traits\CreateTestContentEntitiesTrait for creating the test node?
Comment #16
heddnre #15:
Yes, the test needs to load the message results and review those.
The scenarios I was thinking of was just objects. These objects (looking at node) do no need to be saved objects.
For Url, I was thinking
Drupal\Core\Url
. The reason I proposed these, is it 1) tests things that are commonly logged and 2) shows how the logger can be used.Comment #17
mikelutzTo be clear, the test should ALSO STILL test to make sure the returned value is unchanged, so we don't inadvertently commit something like #2 in the future.
Comment #18
quietone CreditAttribution: quietone as a volunteer commentedThanks dinarcon.
This has fixes for #16 and #17. The log plugin fails on the var_export when the input value is a Node object. A problem I haven't looked into yet. know a way around that?
Comment #20
quietone CreditAttribution: quietone as a volunteer commentedSome much needed cleanup ( I am finding it a challenge to work on broken screen).
Comment #22
aleevasUnfortunatelly I spent some time, but I didn't find how to solve this problem:
The log plugin fails on the var_export when the input value is a Node object. A problem I haven't looked into yet. know a way around that?
Maybe we can use serialize or json_encode instead var_export?
Comment #23
heddnIf the object implements EntityInterface, then it should call
toArray()
before it var_dumps().Comment #24
quietone CreditAttribution: quietone as a volunteer commentedAh, that is what I was looking for, Should be all fixed now.
Comment #25
heddnMaybe try adding a try/catch \Throwable for the var_export in case things go south with other objects?
Comment #26
iyyappan.govindHi All, I have the added
try
andcatch
blocks forvar_export
. Please review my patch.Comment #27
jonas139 CreditAttribution: jonas139 at iO commentedReviewed the patch and tests, seems okay for me!
Comment #28
quietone CreditAttribution: quietone as a volunteer commentedJust fixing a coding standard error.
Comment #29
hash6 CreditAttribution: hash6 at QED42 for Drupal India Association commentedComment #30
hash6 CreditAttribution: hash6 at QED42 for Drupal India Association commentedThanks @quietone for the minute observation, It follows the
rule.
+1 RTBC
Comment #31
hash6 CreditAttribution: hash6 at QED42 for Drupal India Association commentedComment #32
hash6 CreditAttribution: hash6 at QED42 for Drupal India Association commentedComment #33
iyyappan.govindComment #34
alexpottWhy the try catch here? Looking at the docs on I can't see when one would be thrown - https://www.php.net/manual/en/function.var-export.php.
I think this can be written with less logic as:
Comment #35
quietone CreditAttribution: quietone as a volunteer commentedThe try/catch was suggested by heddn in #25.
This patch makes the changes suggested in #34. The only question that remains is about the try/catch.
Comment #36
alexpottWell did the try catch actually catch the error eluded to in #22?
Comment #37
heddnI don't recall all the details from #22. But catching throwable (instead of exception) will also catch errors/warnings. I seem to recall that would be helpful in the case of what was fixed in #26.
Comment #38
quietone CreditAttribution: quietone as a volunteer commented#34. Reworked the flow of the patch.
#36. the error referred to in #22 is resolved by converting the input to an array ($value->toArray()) before using it in the var_export.
#37. This patch changes the catch to a catching a throwable.
Comment #39
apadernoComment #40
mikelutzI'm setting this back to NW. Trapping errors like that is a bad code smell to me. First, I'm not sure how a node could ever end up as an input to a process plugin, but I'll give that it's technically possible in custom code. Second, I don't see that var_export triggers any errors according to the documentation, so the fact that we are trying to trap one seems off.
I would rather test for and export things we can, and log the fact that we have an unloggable value passed for things we can't. My biggest concern would be SimpleXmlElements and such from the contrib xml source.
Comment #41
quietone CreditAttribution: quietone as a volunteer commentedOK. New version that will log anything that is not an object and cleanup the process plugin. Also, added a test for boolean and updated the patch to 9.1.x.
Comment #43
apadernoThe only test is failing because a value isn't truncated to x digits.
Comment #44
quietone CreditAttribution: quietone as a volunteer commentedYes, fun having different test environments. That works just fine locally.
Added an sprintf for the float instead of using var_export. Then reworked the logic of the process plugin
Comment #45
mikelutzCloser, but we can handle more stuff than this. Things that are strings or numeric should be able to to be directly logged. Objects implementing __toString should have their strings logged, arrays should be print_r'd etc. SimpleXmlElements should be tested for, becasue migrate_plus can provide them. we can't really log them, but we should test that we output something along the lines of "logging attempted for an xml object, but that is not serializable" or something along those lines.
Comment #46
quietone CreditAttribution: quietone as a volunteer commentedThis is going round in circles.
Do we want to use var_export or print_r? SimpleXmlElements has a __toString method so why not log it?
Comment #47
quietone CreditAttribution: quietone as a volunteer commentedI wanted to improve the test so here is another version.
Comment #48
esolitosGreat work on this!
Small note:
Instead of simply saying "Unable to log ..." we could add some extra info, like the object type, which seems the only case in which the value would not be logged.
This is basically nitpicking so i'll just mark this as RTBC, patch works as expected.
Comment #49
alexpottprotected
The URL object implements toString() not __toString() cause reasons. I think given how common URL objects are it might be worth detecting toString() and calling it.
This test might be better implemented as a data provider.
Comment #50
quietone CreditAttribution: quietone as a volunteer commented1 Fixed
2. I agree
3. Fixed
4. This did have a dataprovider but that doesn't allow for the creation of Nodes (the data provider runs before setup) so there had to be two test methods and I didn't see the point of multiple methods.
Comment #51
alexpottUnfortunately this won't work. We'll need to call toString() ourselves because PHP won't call it automatically. It's not magic.
Comment #52
alexpotthere we should have the url hardcoded because it makes the test easier to read.
Comment #53
benjifisherI am setting the status to NW for #51 and #52.
Comment #54
quietone CreditAttribution: quietone as a volunteer commented@alexpott, thanks for the reviews.
This changes the url creation and test.
Comment #55
benjifisherMy wife happens to be watching The Princess Bride right now, so I have to paraphrase a famous line:
I am referring to these lines (after applying the patch):
This is why PHP has a bad reputation:
Since earlier lines of code pass integers and floats through
sprintf()
orprint_r()
, only my first two examples are relevant: if the value is 0 (integer) or '' (string), then the message will be set to "Unable to the [sic] log the value ...".I suggest
else { if() {...} }
into anelseif () {...}
.method_exists($value, 'toArray')
instead of$value instanceof EntityInterface
and remove theuse
statement.else { $export = NULL; }
.$export === NULL
.?
and:
. That is, add line breaks.If you do (2), then consider rearranging the conditions so that the
toArray()
test is next to thetoString()
test.Finally, the Migrate Plus module has several process plugins that pass around DOMDocument objects. We could handle those by applying the
saveHTML()
orsaveXML()
method. Given the history of this issue, I will not object if you want to defer this suggestion to a follow-up issue.Comment #56
benjifisherMy previous comment did not say anything about the tests.
I do not understand these changes:
I am not sure if it is OK to have an
@var
comment for one class and then assign an object of a child class to the variable. I am sure that if we refer to the$sourceIdValues
property, then$this->executable
has to be a MigrateExecutableTest object. If our test class were called MigrateExecutable instead of MigrateExecutableTest, then we would need to useDrupal\migrate\MigrateExecutable
as some other name … but we do not call it that.Is it important that we test with an actual Node object? We could remove the dependency on the
node
module by adding atoArray()
method to our other test class. Maybe add asaveHTML()
method, too. And then we could rewrite the test to use a data provider, as discussed in #49.4 and #50.Typo:
I would rather use explicit constant strings for the expected messages:
Can we add test coverage for the cases I mentioned in #55?
Should we clear the messages at the start of the loop instead of the end? If we convert to a data provider, do we need to do it at all?
The
$idMapPlugin
variable is a class property so that it can be set insetUp()
and used in the test method. Why not just make it a local variable in the test method?Sorry for putting the really big question at the end, but do we really need a kernel test instead of a proper unit test? If we create a mock MigrateExecutable class and a mock Row class, then we can test the
transform()
method without creating a migration for it.Comment #57
quietone CreditAttribution: quietone as a volunteer commentedThis is a work in progress. The only changes are to LogTest.php.
55.1 Added tests for int 0 and empty string
55.final paragraph. Definitely agree to a follow up! Increasing logging ability is too important to wait for additions.
56.2 That was requested by heddn in #10 and it makes sense to me. See all the second paragraph of #15 and #16.
56.3 typo fixed
56.4 Painful but done.
56.5 Added the two cases mentioned in 55.1
56.6 I don't think it matters when they are cleared. Instead of clearing the message I changed the loop so that there are
unique source id values for each row and then get the messages for those source id values.
56.7 Because then $migration would need to be a class property too.
56.8 NP. It is a kernel test because Log does
$migrate_executable->saveMessage($message)
and I don't know how to make that work in a unit test.Comment #59
benjifisher@quietone:
Thanks for the updates. I will look at the updated patch later. For now, just a couple of follow-ups:
#56.7: Couldn't we replace
$idMapPlugin
(class property) with$id_map_plugin = $this->executable->getIdMapPlugin()
(local variable)? I think the executable delegates that method to its Migration property.#56.8: I think I like the idea of having a simple Kernel or Functional test to make sure that logging is functional. But I am afraid that this patch makes the test overly complicated.
In order to test the improved functionality from this issue in a unit test, we just have to pass various parameters to the public
transform()
method. The Row parameter is easy:$row = new Row();
. So the hard part is the MigrateExecutableInterface object.One option would be to create a TestMigrateExecutable class that could be used in various tests. For this test, would just need do-nothing implementations of
import()
,rollback()
, andprocessRow()
; and a simple implementation ofsaveMessage()
. I would call that a mock executable, but maybe I should avoid that term.The other option might not work; you are more familiar with the Prophecy framework than I am. Can we use it to create an object with a
saveMessage()
method that is good enough for this test?Comment #60
quietone CreditAttribution: quietone as a volunteer commentedThis should fix all the points from #55, which were fixes to the log plugin.
#56.8 Unit test added, thanks to alexpott who explained to me how to test the log message. The Kernel test is still there for the Node and Url source values.
Still to do: #56.1, #56.7 (again). Note that I haven't had a chance to do a self review.
Comment #61
benjifisherI had a quick look at the interdiff, and it looks very good.
Since we are replacing
$value instanceof EntityInterface
withmethod_exists($value, 'toArray')
, I think we can get rid of theuse
statement.Comment #62
quietone CreditAttribution: quietone as a volunteer commentedAnother increment.
#56.7 Fixed.
And fixed coding standards and checked spelling.
Comment #63
quietone CreditAttribution: quietone as a volunteer commentedAnd finally fixes for #56.1. For that @var now refers to the correct test migrateexecutable which has a name change to TestMigrateExecutable, which is the same as in other tests that require a test migrateexecutable. I think it is easier to read as well.
And then I saw that the expected message was still using print_r (#56.4). The print_r is gone and so is the $url variable.
I expect this will be the last change I make until there is feedback.
Comment #64
mikelutzYeah, I think this covers everything. I'm glad we took the time to work through all the possible scenarios and write proper tests.
Comment #66
mikelutzRe-running test
Comment #68
quietone CreditAttribution: quietone as a volunteer commentedFailure in Drupal\Tests\ckeditor\FunctionalJavascript\CKEditorIntegrationTest. rerunning test.
Comment #69
quietone CreditAttribution: quietone as a volunteer commentedBack to RTBC
Comment #70
catchShouldn't we be asserting the string 'a test string' here?
Which would mean we should be casting the object to string here instead of print_r the raw object with no cast.
Comment #71
quietone CreditAttribution: quietone as a volunteer commentedI've made the change but then the XML output is just '\n\n'.
Comment #73
esolitosThe handling of
toString()
of SimpleXML is a fun one, while technically the output '\n\n' in the test is correct, it's not what a user would expect.I think the solution here is either to include also the object class in the message (see example below) so if a developer sees
SimpleXML
will understand that there might be more than meet the eye, or add a special handling ofSimpleXML
objects, but then how many more edge cases would we need to add?Add class name for "handled" objects.
Comment #74
quietone CreditAttribution: quietone as a volunteer commented@esolitos, thanks.
That idea works for me so here is a patch with the above suggestion. Just made a small change so only the value of $value is inside the single quote.
Comment #75
mikelutzI actually would support handing SimpleXMLElements as a special case because they are easy to create with the migrate_tools module, but I think the improvement in this patch is useful enough to commit as-is and I've created a follow up #3182296: Improve logging of SimpleXML elements in the Log process plugin. to see if we can better handle SimpleXMLElements. I really don't want to hold this up any more and would prefer to iterate on it later if there is a need. This patch more than covers the 80% use case threshold that I look for in improving current systems.
Comment #76
quietone CreditAttribution: quietone as a volunteer commented@mikelutz, thank you! I agree get these useful changes in asap and then work on improvements.
Comment #77
quietone CreditAttribution: quietone as a volunteer commentedComment #78
alexpottCommitted fad126b and pushed to 9.2.x. Thanks!
Comment #80
mondrakeThis broke PHP 8 testing, I am afraid.
Comment #81
alexpottGiven that this is about checking is something is an object and we already have
is_object($value)
in the function I did a small hotfix.Committed 81eb563 and pushed to 9.2.x. Thanks!
Comment #83
quietone CreditAttribution: quietone as a volunteer commented@alexpott, thanks.
Comment #84
benjifisher@alexpott:
Thanks for the fix. I did a sanity check, and it looks good.
I also noticed these lines at the end:
How do you want to handle the extra
$class_name =
?(I just marked the existing follow-up as un-postponed.)