Skip to content

Releases: Underpin-WP/underpin

Fixes bugs discovered in Array Processor

31 Mar 04:27
Compare
Choose a tag to compare

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

23 Mar 11:30
febb0be
Compare
Choose a tag to compare

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

17 Mar 15:29
b69f3d7
Compare
Choose a tag to compare

What's Changed

Full Changelog: 4.0.0...4.1.0

4.0.0 - Feb 21, 2023

21 Feb 11:39
639097c
Compare
Choose a tag to compare

Major bump because of a class name change, With_Object_Cache is now With_Cache

What's Changed

Full Changelog: 3.0.3...4.0.0

3.0.3 - Feb 6, 2023

06 Feb 12:52
c6c2ea4
Compare
Choose a tag to compare

What's Changed

Full Changelog: 3.0.2...3.0.3

Corrects Type Hinting for Array Helper Vars

02 Feb 11:27
024f812
Compare
Choose a tag to compare

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

18 Nov 23:28
Compare
Choose a tag to compare

Implements With_String_Identity and fixed an issue with the Logger class

Release 3.0.0 - Nov 12, 2022

12 Nov 17:45
faa70cb
Compare
Choose a tag to compare

This is a rewrite of Underpin that changes pretty much everything under the hood.

Release 2.1.0

25 Mar 10:52
Compare
Choose a tag to compare

This update simply changes Underpin to use a more-recent version of Composer's installer package.

Release 2.0.0 - Nov 21, 2021

22 Nov 03:50
Compare
Choose a tag to compare

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.