This issue is only to address the addition of a batch builder class.
Please check the related issues or create a new ticket for further improvements.
Problem/Motivation
The batch system is still very much array based and it is not entirely obvious how to create batch operations, see http://api.drupal.org/api/drupal/core%21includes%21form.inc/group/batch/...
Proposed resolution
Create a new BatchBuilder object to make the creation of batches easier.
An example of use would be:
use Drupal\Core\Batch\BatchBuilder;
$batch_builder = (new BatchBuilder())
->setTitle(t('Tilte'))
->setFinished('batchFinishedCallbackFunction')
->addOperation('batchOperationCallbackFunction', $callbackArguments);
batch_set($batch_builder->toArray());
Remaining tasks
User interface changes
None.
API changes
A new BatchBuilder class is to be introduced. The builder will create an array to be passed into existing functions and maintain backwards compatibility.
Comments
Comment #1
larowlanLove it
Comment #2
dawehnerSo something like this?
Comment #4
john cook commentedRe-rolled patch.
Given that we should be moving towards fully OOP, would it be a good idea to move the functionality from
batch_*()into the Batch class and make the functions deprecated?Comment #5
dawehner@John Cook
Yeah the trick of this issue will be to not rewrite everything but rather go one step forward and then find some additional steps we want to go, and well, create issues for them, so people can review them. But please please, work on it, I would be so happy about it.
Comment #6
john cook commentedComment #7
john cook commentedI've added the rest of the setters from the list specified in the
batch_set()documentation.Then I moved the functionality from
batch_set(),batch_process()andbatch_get()into the class, leaving the existing functions as deprecated with redirects to the class's implementation.I've got most of the member functions to allow for piping the operations together; e.g.
The exceptions to this are:
I made an interdiff, but that was larger than the patch :)
Comment #8
mile23+1 on OOP-ifying batch API. The approach looks good. I like the fluent interface.
Just some coding standards stuff:
Comment #9
john cook commentedFixed coding standards errors.
Comment #10
mile23Hey lookit all those services which should be injected. :-) And we wonder: Where will the injection come from? If we inject them into this object, will the batch store the original text or the translated text? Will all batches be processed with a translation service present, or when there's an active theme?
This is the classic too-many-responsibilities problem.
There should be two classes:
1) A data object which is stateful and can be created from a batch array or request/session object. It's responsible for holding the batch info, including untranslated strings from the batch array. It's probably an iterator.
2) A batch processor which is stateless, and can have services injected and can do all the translations for display. It triggers the batch tasks and then reports to the user. It could even be a service.
This way the batch definition will be stored on a more 1-to-1 basis in the session leading to comprehensibility. The definition will be easier to test for behaviors because all it does is create itself based on an array.
The processor will be reduced in complexity because it has fewer responsibilities, could be a service because its stateless, and will be easier to test.
Boo. :-)
VERY boo. :-)
The underscores at the beginning of those function names should be a clue that they're not part of the API, so move them to the processor class.
Leaving as 'needs review' because my opinion probably isn't the be-all end-all. :-)
Comment #11
john cook commentedI've created a new processor class and moved the processing and injections into there. I've left wrappers in
\Drupal\Core\Batch\Batchto keep chaining.The bad
$queue = _batch_queue($batch_set);and nastyrequire_once DRUPAL_ROOT . '/core/includes/batch.inc'; _batch_process();are still there because they are called by Form API and the install process directly, so Form API & install process will need to be rewritten before the functions can be moved into a class as protected/private methods.Comment #13
dawehnerNice progress here! One thing which would be great is to convert an existing example (a test one) to use the new value object to show how the DX improved.
Here is just some small other feedback
Keeping that method around seems a bit pointless, the method in the batch queue controller should be totally enough.
Comment #14
john cook commentedI've removed
process()from/core/lib/Drupal/Core/Batch/Batch.phpand added an example of how to use the class, as suggested be dawehner in #13.Also, the output has been changed so the tests pass.
Comment #15
john cook commentedA couple of changes to the example.
Comment #16
john cook commentedMoved the functionality of
_batch_queue()intoBatchQueueController::getQueueForBatch()but left a wrapper.Comment #17
phenaproximaI like this patch. A lot. I have found a number of code style and relatively minor issues, though...
IMHO this should be callable, not string.
All of these need a description and @var type annotations.
Missing (optional).
Can be shortened slightly:
$this->setTitle($title ?: 'Processing'). It grinds my gears that the Elvis operator is so infrequently used in core...Missing (optional) description.
There doesn't seem to be much of a point to this method -- it's virtually identical to calling
new Batch($title). What value is added by having this static factory method?Should be callable, not string.
$callback should be type-hinted as callable.
It doesn't set any default except in __construct() :) And the default string is not translated, so this needs to be adjusted.
As with setInitMessage(), this method does not set a default (and the default set by the constructor is not translated). So the comment needs fixing.
Same problem as with setInitMessage and setProgressMessage.
IMHO this should validate the file and throw an exception if it doesn't exist. Garbage in, exception out! :)
s/css/CSS.
Should be string[].
s/css/CSS.
Is it still possibly to add single CSS files to a page? Don't we need to use libraries now?
Having a default value for this method obviates the need for the method at all :)
$this->progressive has no default value, so the comment is not accurate.
I think this should call class_exists() on $class, and throw an exception if the class is unavailable.
Should be callable.
Both parameters should be type hinted.
Why bother having both this and setCss()? Seems redundant.
Er...wouldn't it just be better to give these class members default values of []?
Core generally does not use private, so both of these should be protected. They also need doc comments and @var type annotations.
Nit: there's an extra line of whitespace here.
Nit: extra line of whitespace.
Should be callable.
$redirect_callback should be type hinted.
Nit: extra line of whitespace.
Use \Drupal::root() instead of DRUPAL_ROOT.
Shouldn't this be replaced with a method call on the batch object?
Comment #18
john cook commentedThe functionality of
_batch_process()has been moved toBatchQueueController::processQueue(). The required support functions have also been moved intoBatchQueueController.Comment #19
john cook commented@phenaproxima
In responce to points in #17
create()method allows for code likeBatch::create('title')->set...and is a bit neater than(new Batch('title'))->set...It was decided in #10 not to have translated strings in
Batchobjects and delay this until they are processed inBatchQueueManager::process(), so they do eventually get translated.setCss()is used to add a few files at a time whileaddCss()is a single file.setCss()can be used to add most of the files withaddCss()to add optional additional files.setCss()can also be used to clear the list of files. Also, if one CSS file is to be added, saves having to create an array leading to cleaner code :)Batchobject. This ensures that the keys are present.Comment #20
phenaproximaCallable is a weird meta-type -- it can be a string, an array representing a class or object method, or a closure. If it's a string, it's assumed to be the name of a function. So type hinting it as callable should still behave exactly as expected.
As far as serialization goes, it should be pretty safe...but there is no reason why Batch could not implement Serializable if it needs more control over this.
All this being said, there is nothing really to prevent calling code from passing in callables anyway -- I'm just wanting it to be declared explicitly in the doc comments.
Comment #21
john cook commented@phenaproxima still, it's out of the scope of this patch. I'm trying to replicate existing functionality first, then create new issues to change all the things later. I'm adding this to the "List of Things to Do" along with a few other things I've discovered.
Comment #22
dawehnerThis is absolutely the only way to stay sane and get anything done. This is purely about that kind of improvement. Once its in place we can work in parallel on improvements on the underlying bit. I think @phenaproxima totally understands that.
@John Cook
Please keep up with the amazing work here! I'm really positive surprised that we have momentum here.
Comment #23
john cook commentedAddressed the following points for #17 - 2, 3, 4, 5, 13, 14, 15, 18, 24, 25, 26 and 29.
Comment #24
dawehnerHere is just some drive by feedback. As said earlier, great work!
Let's use camelcase here. Is there any reason to not do that?
Are we sure this functionality actually works now that we kinda require libraries all the time?
What about not making title optional but rather have one create() and one createFromTitle method?
Could we use the setter instead here as well?
Should we recommend people to use the batch queue controller directly instead? I'm just curious here.
I'm wondering whether we could get rid of it being static by moving this controller into the container? Would that work?
NOte: We could use Batch::class and BatchMemory::class here.
Comment #25
john cook commentedSome of the points in #24 are to do with the batch processing part (most of batch.inc). I'm leaving that to be tackled at another time otherwise it would be a very large patch. It's already a long way from "a small DX improvement" :)
The processing still uses arrays while processing and the non-camelcase properties (1) and setting of properties (4) are to make the code to go from array to object and back more compact; I'll change these though.
I don't know wether css will work in the same way (2) but the processing side would need to be re-written to account for libraries. Would removing the "css" part be an API change/break?
I think that having enqueue() as a wrapper function (5) creates nicer code.
Batch::create()->...->enqueue();instead ofBatchQueueController::enqueue(Batch::create()->...);and$batch = Batch::create()->...; $batch->enqueue();instead of$batch = Batch::create()->...; BatchQueueController::enqueue($batch);.I'll improve the comments for the related functions but I'm not sure which option is "best" and, therefore, should be recommended.
Would that be the Drupal class for the container (6)?
I'll get on with the other things in the list first and return to these ones.
P.S. I current have a list of 23 further issues for when this one is committed ;)
Comment #26
john cook commentedA few tweaks from #24.
create()method and added a new function,createFromTitle(), which does take $title as a parameter.enqueue()functions of Batch and BatchQueueControllerclassproperty of\Drupal\Core\Queue\BatchandDrupal\Core\Queue\BatchMemoryand used the simple name of the classes. (Although there is some trickery involved as there are two Batch classes under different namespaces.)Comment #27
john cook commentedThe properties have been made camelcase and the setters have been updated. Also, the to/from array methods have been updated.
Comment #28
john cook commentedComment #31
pingwin4egRequeued for testing w/ 8.4.x
Comment #32
dawehnerIt would be great if someone could update the issue summary
Comment #33
john cook commentedI've updated the summary in response to dawehner in #32.
Also, I've added the Needs reroll tag in response to the test queued by pingwin4eg in #31.
Comment #34
dawehnerThank you @John Cook!
Comment #35
jofitzRe-rolled.
Comment #37
larowlanFixed the failing test, and the PHPCS issues.
I agree with #24.6 - I think we should move this to a service and get rid of the static access.
Thoughts?
Comment #38
larowlanFirst crack at removing statics, will continue later.
Comment #40
larowlanComment #41
larowlanComment #44
larowlanI have seen inside the rat's nest
Comment #46
larowlanComment #48
larowlanmodule install uninstall test not finishing
looking forward to debugging that as much as you'd look forward to poking yourself in the eye with rusty fork
Comment #49
john cook commented@larowlan, while I think it's good to move batch API in to being a service, it looks like you're having a bit of trouble doing that.
I would suggest going with the patch in #37 and creating a new ticket for the service implementation.
I've got a list of follow-up tasks that can be done after this issue is merged, although I have to look through it to find out what are still issues.
For reference, here's the list of extra task so far:
Comment #50
dawehner@John Cook
This is quite an epic list. Thank you! Do you mind creating these follows up
Comment #51
larowlan@John Cook
I'm happy to do the non-static in a follow-up *if* we don't mark batch_get/batch_set et al as deprecated.
There is no sense in marking them deprecated in favour of BatchQueueController::{methods} because we'll be deprecating them too.
So in summary, happy to do non static later if
* we don't deprecate existing functions
* we mark the static methods as internal/deprecated because we're going to remove them
Thoughts?
Comment #52
john cook commented@larowlan, I agree. We should remove the deprecation from _batch_*() and have the static methods marked as internal.
@dawehner, I'll create issues for the list in #49 today.
Comment #53
larowlan@John Cook - are you able to re-upload the static one then?
I'll open a new issue to continue with the container approach.
Comment #54
john cook commentedI've re-uploaded the patch from #37 and will fix the issues from #51 shortly.
@larowlan, there is a new ticket, Implement batch api as a service, to continue the work of converting batch API to being a service.
Comment #55
john cook commentedI've removed the deprecation messaged from batch.inc and added them to BatchQueueController. This is in preparation for Implement batch api as a service.
Comment #56
john cook commentedI've fixed the coding standards issue from the test report in #55.
Comment #57
dawehnerWe need a change record which describes the new API.
Comment #58
john cook commentedI've created an initial draft change record as requested by dawehner in #57.
Comment #59
larowlanthanks John
Comment #60
moshe weitzman commentedSure would be nice to get this in soon. Anyone available to review?
Comment #61
mile23Functions with _names are supposed to be internal, so we should either remove them or deprecate them if we're keeping them and they've got a replacement method.
The patch removes some other _functions outright, which might be the right thing to do, but we should be consistent. I'm not sure which way to go with this.
Needs branch update.
Comment #62
dawehnerNitpick: Let's use
static::create()insteadThis could be inlined into one if.
Does this really have to contain static stuff? Could this be rather a service in the container with normal properties? I tried to find an answer for this question, but could not.
When this would be no longer a static method, these dependencies could be passed into the service.
Comment #63
john cook commented@Mile23 The _name functions are left on purpose. This is to be fixed the follow-up issue #2875151: Implement batch api as a service. See larowlan's comment #51 for the basis of this decision.
Comment #64
dawehner@john@johncook.me.uk
Good point. Can you please document these kind of decisions in the issue summary. This helps people to review it, a LOT.
On top of that, do you point adding an @internal to this class for now, so that its clear we don't support this particular API?
Comment #66
john cook commentedA re-roll of of patch #56 as there was a change in comment of
batch_set().Comment #67
john cook commentedUpdated summary.
Comment #68
john cook commentedAfter talking to dawehner at DrupalCon Vienna, we agreed to not deprecated the existing functions but to mark the new
Drupal\Core\Batch\BatchQueueControlleras internal. This is because the queue processing is to be moved into a service and may change.I've marked the
_batch_*()functions as internal as well as the class.I've also done a little code clean-up and implemented changes from comment #62 (specifically points 1 and 2).
Comment #69
john cook commentedAs this patch changes procedural code to object orientated, I've added the need for sign-off from a framework manager. BDFL sign-off may also be needed.
Comment #70
kim.pepperCan we consider type hinting on
callableinstead ofstringfor the callbacks?Comment #71
john cook commentedHi @kim.pepper, thank you for your suggestion.
This patch is to get the existing functionality into an object oriented formated code.
Having the callbacks type-hinting as callable is already outlined in #2875316: setFinishedCallback should accept a callable and #2875317: addOperation should accept a callable.
Comment #72
kim.pepperThanks John Cook. I should have read through the issue history before posting that comment!
Comment #73
claudiu.cristeaI didn't had time to read the patch in detail but I think we should separate the Batch API for its UI implementation so that the API could be used in Drush or everywhere. Then the form implementation could be only a use case of the Batch API. Not sure that the patch is doing this decoupling.
Comment #74
john cook commentedHi @claudiu.cristea
The Batch API is designed to provide UI feedback to the user and prevent timeouts when processing long running tasks. The UI, of course, isn't available when using drush, drupal console, or cron runs and timeouts don't really apply. In these cases the Queue API should be used instead.
I'm starting to think about refactoring Batch API to use the Queue API instead of implementing it's own version, but this is beyond the scope of this issue.
Comment #75
john cook commentedAs there a few people asking for enhancements outside the intended scope of this issue, I've updated the summary to explicitly state what is to be covered at the top.
Comment #76
john cook commentedI noticed that the comment for methods in the new class were copied with the
@depricationtags. I've replaced these with@internaltags instead.Comment #77
catchComment #80
john cook commentedSetting back to “needs review” because of false test fails.
Comment #82
volegerI guess it should be type of
stringMissed
arraytype hint for$argumentMissed
arraytype hint for$batchReturn type should be updated regarding
self::finishedProcessing()return types.$old_setdefined in the while loop, so, potentially,variable
$old_setmight have not been definedThe same with
$finishedvariable.The same with
$labelvariable.Comment #83
jofitzAddressed all of the points raised by @voleger in #82.
Comment #84
borisson_Nits, these are mostly opinion, so feel free to ignore, that's why I'm not setting this back to Needs Work.
I was going to mention that this should be a value object instead of a domain object.
But after reading a couple of stack overflow articles about it, I'm not sure anymore.
However, when I use ag to search for domain object vs value object we get very different results.
I think that's sufficient data to make this
value oject?I don't see any option to set the title in the ::create method, but it is documented like it is possible.
I think this should be
createWithTitleinstead ofcreateFromTitleto be more specific.I don't understand why the second call uses
addCssinstead of->setCss([$batch_definition['css']);.I think even better would be to use addCss for both, that way we can add default css later and it won't be overwritten.
I think this can be rewritten to remove the
else.I think we should rewrite this as:
if ($name === NULL && $class === NULL) {When I check for is_null vs == NULL with ag, we see that == NULL is used a lot more.
44 matches359 matchesI'm not sure we should call this a Controller, then again I don't think Manager or Service makes it better.
Comment #85
john cook commentedThank you to @voleger and @borisson_ for reviewing. And thank to @Jo Fitzgerald for the patch.
To address borisson_'s comments.
1. Dictionary.com defines domain as
It is the realm of responsibility that I want to get across with the comment. Also, the class does not only store values, it also stores process (as operations), so 'value object' isn't extensive enough.
2. I've remove the title from the example code.
3. The only use of
createWith...()I can find iscreateWithSampleValues(), all othercreate...()functions arecreateFrom...(). Because of this I want to leave it ascreateFromTitle()for code consistency - unless there are other reasons to change it.4. It was the comments that I was working from to create the patch. The comments were changed in #2600012: 'css' key in batch_set is no longer used. Apparently, the CSS additions didn't work anyway. #2378095: Convert all remaining attached individual CSS/JS assets to attached asset libraries changed the Batch API to use libraries. So now the CSS setters need to be replaced with library setters.
5. I've rewritten the
setQueue()to use the quick return and ...6. to use
=== NULL.7. It's not a controller in the MVC sense, but it does control stuff. It's definitely not a service either. Maybe manager would work? I'm looking for further input before changing this.
Comment #86
john cook commented@borisson_ pointed out some changes to be made in point 4 of #84.
After a bit of discovery, I found that CSS is no longer used and libraries have been implemented by use in
_batch_page(). This needs to be reflected in the patch for this issue.TODO:
Remove the CSS based functionality from the
Batchclass and replace it with the equivalent 'library' code.createFromArray()should useaddLibrary()for all cases.addLibrary()should be able to accept a single library (string) or multiple libraries (array).Comment #87
john cook commentedI've changed the
Batchclass to accept libraries instead of CSS files.From @borisson_'s suggesting in comment 84 point 4,
createFromArray()now usesaddLibraries()for all cases.addLibraries()can now accept strings and arrays of strings to make it more adaptable.Comment #88
john cook commentedAfter talking to @alexpott, it has been decided to only include the
Batchobject in this issue.There would need to be a process to convert a
Batchobject into an array for use by the processing functions.The processing of the batches is to be moved to other issues.
Comment #89
john cook commentedA new patch that is just the
Batchclass.Comment #90
john cook commentedChanged to 'needs review'.
Comment #91
alexpottLet's call this a BatchBuilder. Since this is a mutable object and we really don't want the Batch objects to be mutable once they are being processed.
Why not just default values?
Not necessary. Or maybe create could take some of the common defaults.
Not necessary.
This isn't need now.
Let's typehint with Callable
This is not necessary can just cast to an array. So
Are we sure about + here? don't we mean array_merge()?
enqueue is a weird name. How about addToQueue()? Or just queue()?
Is this required? I.e should we error if there are no operations.
Comment #92
alexpottComment #93
john cook commentedTo address the points from @alexpott in comment 91:
1. I've renamed the class and interface to
BatchBuilderandBatchBuilderInterface.2. The default values are now set in the property definitions.
3, 4, 5. The
__construct(),create(),createFromTitle(), andcreateFromArray()methods have been removed.6.
setFinishCallback()andaddOperation()methods both havecallabletype hints.7.
addLibraries()now casts to an array and usesarray_merge.8. The queuing method is now called
queue().9. The batch operations could be set like:
The code that gets the list of
$idsmight return an empty array, so I don't think that theBatchBuildershould set restriction on this (by throwing exceptions) but needs to cope when no operations are set. Maybe logging when there are no operations in the batch would be enough?There is no interdiff as the files were renamed, so the interdiff would be the old files removed and new files added.
Comment #94
john cook commentedTest still need to be added.
Comment #95
borisson_git diff -Mhelps with finding renames. It doesn't always work - but in this case, I think it would have.Comment #96
john cook commentedThanks @borisson_. It worked!
One interdiff for 89 to 93.
Comment #97
john cook commentedI've created some tests for the
BatchBuilderclass.Comment #98
john cook commentedI've removed the
queue()function and changed the class's comment to instruct developers to usebatch_set($batch_builder->toArray());The
setQueue()function has been changed so that it checks for the class with the passed in name, throwing an exception if it cannot be found. The comments for the method have been cleaned up.Comment #99
dawehnerWhy would we have default options here? Wouldn't calling this function be pointless without an argument?
I'm curious, why would you swap this class out?
Comment #100
john cook commentedWith regards to @dawehner's comments in comment #99:
1. I was thinking about clearing settings that had been passed in already, but if you really need to do that you can pass in an empty array. I've removed the default option.
2. Swap it out for tests? But then the same question. The builder doesn't do anything that would not be available during unit tests. In any case, I've removed the interface.
Comment #101
alexpottThis should be a translatable string.
This too.
Just realised this defaults should all be translatable strings so they are going to need setting in the constructor.
These should all accept string or translatablemarkup.
Let's convert this into a proper API docs list.
Normally
Defaults to...on a @param doc is about what the param defaults to. That's not the case here so it looks weird.testDefaultValues() ... the Sensible is unnecessary.
There's $this->assertInternalType() or $this->assertArrayHasKey().
$this->assertArrayNotHasKey()
Generally we skip the 'Title has been set.' stuff in PHPUnit cause the assert and test method name is enough.
Let's assert what is in the array here.
Comment #102
andypostTo make messages translatable I think that better to add constructor which sets defaults and allows to accept overrides for this defaults
Example of usage also needs update
messages should be translatable
Comment #103
john cook commentedI've changed the title and messages to be
stringorTranslatableMarkup, updating the example code and setters as necessary. A constructor now sets the default values for the translatable properties.The comments have been shuffled so that the 'default' values are not in the
@paramdocs.The tests have been updated to check for
TranslatableMarkupvalues, the arrays are fully checked rather than parts of them, and better asserts have been used. The messages have been removed from the asserts.I think this addresses everything from @alexpott and @andypost's comments.
Comment #104
john cook commentedI've added a
build()method to the batch builder.At the moment this wraps the
toArray()method, but in the future it could return an immutable object as the representation of a batch.Comment #105
alexpottThis is not quite right now. I think it is okay if this just says
Builds an array for a batch process.- we can update it as the code changes if this eventually builds immutable objects.or can be autoloaded, for example, static methods on a class, then this does not need to be set.in on page callshould bein a single run. We shouldn't assume a "page".This should be something like:
(optional) A Boolean that indicates whether or not the batch needs to run progressively. TRUE indicates that the batch will run in more than one run. FALSE indicates that the batch will finish in a single run. Defaults to TRUE.Also we should file an issue (or check for one to fix the docs in batch_set() which incorrectly say the default is FALSE.
How would this remove an override?
We can ensure that the class implements this interface in the method using http://php.net/manual/en/function.class-implements.php
This should be tested.
Just pondering is this actually necessary - are there example in core where this would be useful? Off hand I couldn't find a single thing in core that would use this. I can't find anything that'd use setLibraries() but that seems useful.
This feels premature. Let's wait till we do the next change.
No longer needed.
Ooops is should be spelled Oops :)
Let's also add
'library1'to this so we test thearray_unique()Comment #106
john cook commentedFrom alexpott's comments in #105
2, 3, 4, 5. The comments have been changed.
6. I've removed the text that says that you can remove a queue class.
7. A check has been put in to ensure that the class passed in implements the correct interface. There's also a test for this.
8. A test for a missing class has been added.
9. From a new instance, both addLibraries() and setLibraries() do the same thing. The difference between the two is:
Maybe removing setLibraries() would be better?
I couldn't find a use case in core either.
10. I was thinking about future issues to change implementations to use toArray() and then changing them again to use build(); adding build() at this stage would remove the need for the second set of issues. I've removed build() for now.
11. Comment has been removed.
12. Spellings fixed.
13. Added
'library1'to the test.Comment #107
alexpottYep lets remove addLibraries().
Also the change record needs an update - https://www.drupal.org/node/2875389
Comment #108
john cook commentedI've removed the addLibraries() method.
The change record has been updated.
And I've updated the summary of this issue.
Comment #109
alexpottUnfortunately #108 lost the test coverage of setLibraries().
Also as core framework manager I sign off on this addition to the API. It makes building a batch way easier and is the first step in modernising the batch code.
Comment #110
john cook commentedOops. I got carried away with the delete key.
The test has been put back.
Comment #111
john cook commentedCondensed the test for setLibraries().
Comment #112
jofitzAddresses @alexpott's feedback from #105 so setting to RTBC.
Comment #114
MixologicTemporary testbot hiccup.
Comment #115
alexpottAdding review credit for people who left reviews that influenced the direction of the patch.
Comment #116
alexpottCommitted ac703dc and pushed to 8.6.x. Thanks!
Comment #119
fgmDon't you think it could be part of 8.6.0 highlights ?