Releases: Underpin-WP/underpin
Fixes bugs discovered in Array Processor
Two methods in Array processor were calling the wrong array helper method. This release fixes that.
Full Changelog: 4.2.0...4.2.1
Fixes a bug related to the URL to string conversion
What's Changed
- Updates param collection to correctly set key/value pairs when converting to string by @alexstandiford in #60
Full Changelog: 4.1.0...4.2.0
4.1.0 - March 17, 2023
4.0.0 - Feb 21, 2023
Major bump because of a class name change, With_Object_Cache
is now With_Cache
What's Changed
- Feature/cache by @alexstandiford in #56
- Feature/cache by @alexstandiford in #57
Full Changelog: 3.0.3...4.0.0
3.0.3 - Feb 6, 2023
What's Changed
- Corrects return type for array processor by @alexstandiford in #55
Full Changelog: 3.0.2...3.0.3
Corrects Type Hinting for Array Helper Vars
A few methods inside the Array Helper should use mixed
instead of array
since they automatically cast these items as arrays behind the scenes.
Release 3.0.1 - Nov 18, 2022
Implements With_String_Identity and fixed an issue with the Logger class
Release 3.0.0 - Nov 12, 2022
This is a rewrite of Underpin that changes pretty much everything under the hood.
Release 2.1.0
This update simply changes Underpin to use a more-recent version of Composer's installer package.
Release 2.0.0 - Nov 21, 2021
This major update includes some significant upgrades to Underpin. Most of these things are quality of life changes, standardizing some methods for hooks, introducing PSR-4 support across the board, and making it much easier to extend Underpin itself.
For example, adding a new loader dynamically, to any Underpin plugin once looked like this:
// Add this loader.
add_action( 'underpin/before_setup', function ( $file, $class ) {
require_once('path/to/template/abstraction');
require_once('path/to/template/factory');
require_once('path/to/template/loader');
Underpin\underpin()->get( $file, $class )->loaders()->add( 'templates', [
'registry' => 'Underpin_Templates\Loaders\Templates',
] );
}, 10, 2 );
This has been simplified to this:
Underpin::attach( 'before_setup', new Loader( 'templates', 'Underpin_Templates\Loaders\Templates' ) );
Replaces Hooks & Filters With Internal API
Instead of using add_action
, do_action
, add_filter
and apply_filters
, This update uses a
robust observer pattern. This standardizes the
extending process for plugins, provides more-robust priority settings, and encapsulates the action that runs in a class
that can be extended. This impacts all existing actions, as well as how middleware patterns work.
Another benefit of this, is that any observer is associated with a class instance. This localizes the hook, and drastically reduces the chance of naming collisions.
Logger::attach('log:item_logged', new Observer(/**/));
VS
add_action('underpin/logger/item_logged', function(){/**/});
In the examples above, anyone can create do_action('underpin/logger/item_logged')
and hook into that, whereas Logger::notify
can only be ran inside of the class. In most-cases, this is what you want, and it prevents accidental conflicts.
Another benefit of this method is it is possible to enforce formats for returned values.
// WordPress
$arbitrary_argument = 'arbitrary_argument_value';
$value = apply_filters('filter_name', [], $arbitrary_argument); // $value can literally become anything
// WordPress - I can break the rules and update the filter.
add_filter('filter_name', function($value, $arbitrary_argument){
return 'I have broken the filter by changing it into a string. MUAHAHAHA';
},10,2);
var_dump($value); // dumps "I have broken the filter by changing it into a string. MUAHAHAHA"
/**
* Underpin Example
**/
//Underpin (running inside any class that supports filtering)
$value = $this->filter('filter_name', new Accumulator( [
'default' => [], // Start the filter out as an array
'arbitrary_argument' => 'arbitrary_argument_value', // Pass this value directly
'valid_callback' => 'is_array', // Only allow the filter to be updated if it becomes an array.
] ) )
// Assume $class is an instance containing the filter above.
$class->attach( 'filter_name', new Observer( [
'id' => 'nice_change',
'update_callback' => function ( \Underpin_Templates\Abstracts\Template $template, \Underpin\Factories\Accumulator $accumulator ) {
$accumulator->update(array_merge($accumulator->state, ['look_a_new_param' => 'with a value']));
},
] ) );
// This will NOT work because the valid callback will fail. This filter will be ignored.
$class->attach( 'filter_name', new Observer( [
'id' => 'evil_change',
'update_callback' => function ( \Underpin_Templates\Abstracts\Template $template, \Underpin\Factories\Accumulator $accumulator ) {
$accumulator->update('I WILL BREAK THIS TOO! (or will I?');
},
] ) );
var_dump($value); // dumps ['look_a_new_param' => 'with a value']
This also makes the decision list loader obsolete, instead using this API.
// Underpin 1.*
plugin_name()->decision_lists()->add( 'example_decision_list', [
// Decision one
[
'valid_callback' => '__return_true',
'valid_actions_callback' => '__return_empty_string',
'name' => 'Test Decision',
'description' => 'A single decision',
'priority' => 500,
],
// Decision two
[
'valid_callback' => '__return_true',
'valid_actions_callback' => '__return_empty_array',
'name' => 'Test Decision Two',
'description' => 'A single decision',
'priority' => 1000,
],
] );
// Underpin 2.*
plugin_name()->loader_name()->attach( 'decision_id', [
// Decision one
new Observer( 'decision', [ // A custom middleware action
'update' => function ( $class, $accumulator ) {
// Condition in-which this should run
if($condition){
// Update the accumulator state. This sets the value when the decision is returned
$accumulator->set_state()
}
},
'priority' => 10, // Optionally set a priority to determine when this runs. Observers are sorted by deps, and then priority after.
'deps' => ['observer_key', 'another_observer_key'] // list of decisions that should be checked BEFORE this one.
] ),
// Decision two
new Observer( 'decision_two', [ // A custom middleware action
'update' => function ( $class, $accumulator ) {
// Condition in-which this should run
if($condition){
// Update the accumulator state. This sets the value when the decision is returned
$accumulator->set_state()
}
},
'priority' => 10, // Optionally set a priority to determine when this runs. Observers are sorted by deps, and then priority after.
'deps' => ['observer_key', 'another_observer_key'] // list of decisions that should be checked BEFORE this one.
] ),
] );
Loaders now use a syntax consistent with the rest of Underpin.
// Underpin 1.*
plugin_name()->loaders()->add('name',['registry' => 'Loader_Class']);
// Underpin 2.*
plugin_name()->loaders()->add('name',['class' => 'Loader_Class']);
Logger is now static
The logger was originally built into each instance as a loader, but this caused a lot of race-condition issues that made
it impractical to keep it that way. Because of this, the logger loader has been moved into its own static instance, and
all logger commands can be accessed statically.
// Underpin 1.*
plugin_name()->logger()->log();
// Underpin 2.*
Logger::log();
This does mean that the logged events for all plugins exist within the different event types. If you want to separate
your logged events, you'll need to register your own logger event type and use that. This is just another Loader
Registry so you can treat it exactly like you do any other registry item. See #Loaders for more info.
Logger::instance()->add('custom_event_type','Event_Type');
underpin()
function removed
The underpin()
function has been removed entirely. Once I made the logger static, I realized that the entire reason why this instance exists at this point was to create an un-necessary layer to the Logger. Due to this, I opted to remove it.
If you're extending all plugins, you can use attach
Underpin::attach('init',new Observer([
'update' => function(Underpin $instance, Accumulator $args){
// Do an action. $instance is the current plugin's Underpin instance.
}
]));
If you absolutely have to have an instance of underpin
or something to attach your item to, you should create a new instance of Underpin.
PSR-4
Everything in Underpin now uses PSR-4 standards. This should pave the way to make it possible to compile underpin using compilers like Mozart. Because of this, some namespaces have changed. Most-notably, all Underpin_*
namespaces have changed to use Underpin\*
to be more PSR4 compliant, and nudge Underpin toward using
PSR-based compilers to make distributing plugins easier.
// Underpin 1.*
use Underpin_Scripts\Loaders\Scripts;
// Underpin 2.*
use Underpin\Scripts\Loaders\Scripts;
Fields API Removed
The fields API has been removed, and is intended to be extracted in a separate repository sometime in the future. If your system relies on the fields API, you'll want to stay on Underpin 1.* until this is fully baked.