underpin-wp / underpin Goto Github PK
View Code? Open in Web Editor NEWA WordPress Framework that makes building scale-able plugins and themes easier.
Home Page: https://docs.underp.in
License: MIT License
A WordPress Framework that makes building scale-able plugins and themes easier.
Home Page: https://docs.underp.in
License: MIT License
This would allow for method chaining, and could make registering things a little cleaner. Example:
underpin()->meta()->add( 'type_1', [ /*args*/])
underpin()->meta()->add( 'type_2', [ /*args*/])
vs
underpin()->meta()
->add( 'type_1', [ /*args*/])
->add( 'type_1', [ /*args*/])
Some loader items would benefit from a middleware-type pattern. This could make it possible to register things using the array method without a class in circumstances where a class is currently required.
Use case example - enqueuing a script on the front-end:
underpin()->scripts()->add( 'test', [
'handle' => 'test',
'src' => 'path/to/script/src',
'name' => 'test',
'description' => 'The description',
'middlewares' => [
'Underpin_Scripts\Factories\Enqueue_Script'
]
] );
The above example could remove the need to create a class for the test
script above. With the enqueue script middleware adding the actions needed to enqueue the script for us.
This could also lead to composer packages that contain middleware for other common things, such as setting the REST URL and a nonce in a script automatically.
require_once __DIR_ . '/vendor/autoload.php';
add_action( 'plugins_loaded', '\\Underpin\\underpin' );
Many abstracts have WP_error
- note the "e" - and lack use WP_Error;
This is because With_Static_Subject
and With_Subject
each have methods that are named identically. This causes these methods to be overridden by one, or the other, and cause unexpected behavior.
The only obvious solution is to change the name of the methods in either With_Static_Subject
or With_Subject
to make it possible to have both in a single class.
The problem:
PHP 8.1 has deprecated the option to dynamically set variables inside of a class. This can be worked around fairly simply using __set
, or creating a set param method in the data object.
This is probably related to the jQuery updates in WP Core.
This is because the $name
param is set to protected
when it must be public
. It should probably be removed from this class in general since this class extends Feature_Extension
and inherits this variable anyway.
Currently, the export method simply dumps Underpin's data as HTML. It would be useful if it were possible to export this in different formats, such as JSON, PHP, or something custom.
This could make it easier to automatically build documentation, and could also be useful for support purposes, like a system info file.
The loader registry has a method, filter
that makes it possible to filter the results of a registry easily. It would be nice if there was also a find
method that would simply return the first registry item that matches a search result.
Right now, Underpin has its own compiler package that works for Underpin, but it doesn't work for any external libraries used, such as [BerlinDB}(github.com/berlindb/core) or Background Processing. It would be better if Underpin could use the same compilation package as other WordPress-based composer packages use, Mozart.
In my brief time spent testing, I absolutely could not get it to work. Odds are pretty good this is because of the way that Underpin loads in its files, both in core and individual loaders.
I'd really like to not have to change how everything fundamentally loads if possible, but if that's necessary then I think it's a worthwhile endeavor.
This will make it possible to call loaders directly as methods, and also make it so that new loaders can be added to the registry without explicitly adding the methods to a class.
One problem with the method in-which inline instance creation works, is most IDEs have no way to know how to provide code hints for the arguments passed into the constructor. It would be nice if it were possible to register loader items using method chaining instead. This would make it possible to sanitize these items individually for children classes, and make the IDE provide the hints needed to actually set these up.
I personally find myself looking up the documentation to set up loader items all the dang time, and it would be nice if I didn't have to rely so heavily on the documentation to make sense of how to set these things up. I think a setter pattern would really help with this.
Current signature example:
underpin()->meta()->add( 'meta_name', [
'key' => 'meta_key',
'description' => 'Meta description',
'name' => 'Meta Name',
'default_value' => false,
'type' => 'post',
] );
Potential signature example:
underpin()->meta()->add( 'meta_name',(new Meta_Record_Type())
->set_key( 'meta_key' )
->set_description( 'Meta description' )
->set_name( 'Meta name' )
->set_default_value( false )
->set_type( 'post' ) );
This would provide a third way to register something. The array-based method, providing a class directly, and a setter-based method.
Some things to consider:
// Set most of the values on-registration
underpin()->meta()->add( 'meta_name', [
'key' => 'meta_key',
'description' => 'Meta description',
'name' => 'Meta Name',
'default_value' => false
] );
// Later on, after registration, set the type.
underpin()->meta()->get( 'meta_name' )->set_type( 'some_dynamic_type' );
Something I've been thinking about a lot for a few years now is how nice it would be to integrate Underpin in a way that makes re-using code between plugins easier. A way to-do this would be by building different plugin "types", wrangling up as much of the common functionality as possible, and running them through some kind-of consistent pattern that can be re-used throughout other plugins.
This could drastically improve plugin development speeds, and make working with third party plugins significantly easier.
I think Underpin needs a way to load in pre-made integration classes that contain all of the information needed to work with different plugin types in a consistent fashion. The goal would be to make it so that long as a developer sticks to the methods that are included in the Underpin integration class their plugin should be able to be compatible with all of the plugins that work with the integration for free.
From this, we could build integration packages for Underpin that pre-make the integrations form common plugins. These could be installed, and used directly.
Example:
// Submit a form using an active forms plugin
// everything after forms() would come from a composer package that extends Underpin. Probably underpin/forms-integration or similar.
$form = underpin()->integrations()->forms()->find(['active' => true]);
if( !is_wp_error( $form ) ){
$form->submit( 1, ['key' => 'value'] );
}
This would allow developers to integrate with several plugins at one time, and also prevent common pitfalls with coupling their code directly with a forms plugin directly.
This could also make it possible for theme developers to integrate with more plugins without re-writing a bunch of code in the process.
You build a plugin for a customer that integrates Ninja Forms in some cool way. Later on, you need to do something identical for another customer, only this time you have to set it up to work with Gravity Forms, instead.
To accomplish this in Underpin, I would probably create a custom loader called integrations
that would actually use whichever plugin is installed. Something like this:
// Create the integration class
abstract class Integration{
use Feature_Extension;
abstract public function get_form( $form_id );
abstract public function submit( $form_id, $data );
abstract public function fields( $form_id );
}
class Integration_Instance extends Integration{
use Instance_Setter;
protected $custom_form_action;
/**
* Block_Instance constructor.
*
* @param array $args Overrides to default args in the object
*/
public function __construct( array $args = [] ) {
$this->set_values( $args );
parent::__construct();
}
public function get_form( $form_id ){
return $this->set_callable( $this->get_form_action, form_id );
}
abstract function submit( $form_id, $data ){
return $this->set_callable( $this->submit_action, form_id, $data );
}
abstract function fields( $form_id ){
return $this->set_callable( $this->fields_action, form_id );
}
}
// Register the custom loader
underpin()->loaders()->add('integrations',[
'abstraction_class' => 'Integration',
'default_factory' => 'Integration_Instance'
]);
// Register Ninja Forms Integration
underpin()->integrations()->add('ninja_forms', [
'get_form_action' => function( $form_id ){
return Ninja_Forms()->form( 1 )->get();
}
/* Plus all the other callable actions */
]);
// Register Gravity Forms Integration
underpin()->integrations()->add('gravity_forms', [
'get_form_action' => function( $form_id ){
return GFAPI::get_form( $form_id );
},
/* Plus all the other callable actions */
]);
$ninja_forms_integration = underpin()->integrations()->get('ninja_forms'); //Do things with the Ninja Forms plugin
$gravity_forms_integration = underpin()->integrations()->get('gravity_forms'); //Do Things with the Gravity Forms plugin
// Gets the form using Ninja Forms.
$ninja_forms_integration->get_form();
// Gets the form using Gravity Forms.
$gravity_forms_integration->get_form();
This is a common scenario - building a third party plugin that needs to integrate with multiple, similar plugins. It would be nice if Underpin had some libraries pre-built to make it possible to work with these systems directly. So take all of the example code above, modify it to work as an integration, and make these work as loaders. This integration could be loaded in using composer, perhaps something like composer require underpin/forms-integration
, which would contain all of the information needed to work with any forms plugin that is registered in the system. So, as long as a developer sticks to the methods that are included in Underpin's integration class, their plugin should be able to be compatible with all of the plugins that work with the integration for free.
This would allow developers to integrate with several plugins at one time, and also prevent common pitfalls with coupling their code directly with a forms plugin directly.
This could also make it possible for theme developers to integrate with more plugins without re-writing a bunch of code in the process.
Here's a few possible patterns:
// Get the integration class to work with the plugin.
// The intent is to make it so that this class would replace the need to use any direct functions or methods inside any forms plugin
$form_plugin = underpin()->integrations()->forms()->get( 'ninja_forms' );
$form_plugin->submit();
$form_plugin->get_fields();
Since this is technically a loader registry, you could also query registered integrations, and run something on each:
// Query integrations
$integrations = underpin()->integrations()->forms()->find([
'features__in' => 'stored_submissions', // Only get form plugins that actually store submissions
]);
foreach( $integrations as $integration ){
if($integration->is_active()){
// Do something with the active integration
}
}
class Integrations extends Loader_Registry{
// Set up integrations as a loader registry. This would be built into Underpin, and be accessible via integrations()
// It would probably have a __call method similar to the base Underpin class.
}
abstract class Integration{
abstract public function is_active();
}
// Create the integration class
abstract class Form_Integration extends Integration{
use Feature_Extension;
abstract public function get_form( $form_id );
abstract public function submit( $form_id, $data );
abstract public function fields( $form_id );
}
class Form_Integration_Instance extends Form_Integration{
use Instance_Setter;
protected $get_form_action;
/**
* Block_Instance constructor.
*
* @param array $args Overrides to default args in the object
*/
public function __construct( array $args = [] ) {
$this->set_values( $args );
parent::__construct();
}
public function get_form( $form_id ){
return $this->set_callable( $this->get_form_action, form_id );
}
abstract function submit( $form_id, $data ){
return $this->set_callable( $this->submit_action, form_id, $data );
}
abstract function fields( $form_id ){
return $this->set_callable( $this->fields_action, form_id );
}
}
// Register the custom loader
underpin()->integrations()->add('forms',[
'abstraction_class' => 'Form_Integration',
'default_factory' => 'Form_Integration_Instance'
]);
// Register Ninja Forms Integration
underpin()->integrations()->forms()->add('ninja_forms', [
'get_form_action' => function( $form_id ){
return Ninja_Forms()->form( $form_id )->get();
}
/* Plus all the other callable actions */
]);
// Register Gravity Forms Integration
underpin()->integrations()->forms()->add('gravity_forms', [
'get_form_action' => function( $form_id ){
return GFAPI::get_form( $form_id );
},
/* Plus all the other callable actions */
]);
$ninja_forms_integration = underpin()->integrations()->forms()->get('ninja_forms'); //Do things with the Ninja Forms plugin
$gravity_forms_integration = underpin()->integrations()->forms()->get('gravity_forms'); //Do Things with the Gravity Forms plugin
// Submits the form using Ninja Forms.
$ninja_forms_integration->submit( 1, ['key' => 'value'] );
// Submits the form using Gravity Forms.
$gravity_forms_integration->submit( 1, ['key' => 'value'] );
Now with the example above, let's say you wanted to automatically submit a form when something else happens on your site. You don't actually care what forms plugin is used, you just want the data to be submitted.
// Helper function to get the current forms integration
function get_forms_integration(){
$current_integration = underpin()->options()->get( 'integration' )->get();
$integration = underpin()->integrations()->forms()->get( $current_integration );
if( is_wp_error( $integration ) ){
underpin()->logger()->log_wp_error( $integration );
}
return $integration;
}
// Now a form submission will occur, regardless of what forms plugin was used.
add_action( 'my_super_cool_custom_hook', function( $data ){
if(!is_wp_error(get_forms_integration()){
get_forms_integration()->submit( 1, $data );
}
} );
I'm sure there's many more.
get_forms_integration()->get_fields()
? The resulting array of fields needs to be normalized in a standard Field
object, so that the signature doesn't change between plugins. Something like get_forms_integration()->get_fields()->get_value()
should work for every single type of forms plugin. Underpin might need to have some kind of abstraction to facilitate this.plugin_name()->integrations()
, and the Integrations
class should extend Loader_Registry
so we can use helpers like find
and filter
.filter
and find
methods can query against it.is_active
, but it would be useful if the system knew that when active
is added to filter/find that it would filter those plugins out.This implementation probably needs to be coupled with a pre-built integration. This will help us figure out the nuance of working with a system like this, and also give us a concrete example of how to use/extend integrations.
It wasn't initially obvious that I needed to run composer require underpin/underpin
in the root directory of Wordpress. I had to run this in an empty folder to see what the structure was before realizing where it needed to be ran.
And now that I'm through that hurdle, I still cannot figure out where to run the individual loaders like https://github.com/Underpin-WP/admin-page-loader
.
To increase the adoption rate of underpin, it would seem that providing a clear walkthrough of each use case would help.
In this walkthrough it would be good to see what the suggested file structure should be.
As well as, seeing where you are installing each of the packages.
In the end, make your example available in a git repo so we can snag code snippets that you provided in the walkthrough.
Thanks so much. I'm looking for a rapid development framework to make Wordpress a bit more bearable. I'm hoping that this is it, but struggling to reverse engineer the assumptions made.
@inheritDoc
may be deleted in 2020.
Plus we have some @var inheritDoc
to be removed too.
It would be nice to add a set
method to the accumulator that, unlike update, will only change the value if it is not set.
This could simplify many logical scenarios where something is being extended using apply_filters
, but the update only needs to be made if nothing else is currently set.
For example:
// GIVEN
$item = new class{
use With_Subject;
public function get_user_photo($id){
return $this->apply_filters( 'user:photo', new Accumulator( [
'default' => null, // Default value is null
'state' => [ 'id' => $id ],
'valid_callback' => fn ( $value ) => is_string( $value ),
] ) );
}
}
// NEW
$item->attach( 'user:photo', new Observer( 'key', [
'update' => function ( $instance, Accumulator $accumulator ) {
// Only sets if the state hasn't already been set.
$accumulator->set( Integrations\User::class );
},
] ) );
// OLD
$item->attach( 'user:photo', new Observer( 'key', [
'update' => function ( $instance, Accumulator $accumulator ) {
if($accumulator->get_state() === null) $accumulator->update( Integrations\User::class );
},
] ) );
Registering custom post types and taxonomies each have a lot of arguments that could have a more-sane defaults. For example, there are several labels that need manually set, and most of them could be automatically set with a little logic to tell these CPTs to simply concatenate a singular, or plural version of the word.
This would help with debugging and make it easier to understand why something isn't being included when it should be.
The exporter was originally created as a way to output registered items and display them directly in a text file, however, as I've worked with Underpin I've come to find that it would be a lot nicer if this was exported as a JSON file, instead. Not only would this be potentially useful in the future for debug purposes, but it may even make it possible to eventually generate entire plugins from a JSON file.
------ -----------------------------------------------------------------------------
Line Factories/Observers/Loader.php
------ -----------------------------------------------------------------------------
13 Access to an undefined property Underpin\Factories\Observers\Loader::$key.
14 Access to an undefined property Underpin\Factories\Observers\Loader::$args.
19 Access to an undefined property Underpin\Factories\Observers\Loader::$args.
19 Access to an undefined property Underpin\Factories\Observers\Loader::$key.
------ -----------------------------------------------------------------------------
------ --------------------------------------------------------------------------
Line Factories/Observers/Trigger_Exception.php
------ --------------------------------------------------------------------------
24 Access to an undefined property Underpin\Abstracts\Storage::$event_type.
27 Access to an undefined property Underpin\Abstracts\Storage::$item.
------ --------------------------------------------------------------------------
------ --------------------------------------------------------------------------------------
Line Factories/Observers/Trigger_Notice.php
------ --------------------------------------------------------------------------------------
22 Access to an undefined property Underpin\Factories\Observers\Trigger_Notice::$level.
28 Access to an undefined property Underpin\Abstracts\Storage::$item.
32 Access to an undefined property Underpin\Factories\Observers\Trigger_Notice::$level.
------ --------------------------------------------------------------------------------------
------ -------------------------------------------------------------------------
Line Loaders/Logger.php
------ -------------------------------------------------------------------------
377 Access to protected property Underpin\Abstracts\Event_Type::$psr_level.
382 Access to protected property Underpin\Abstracts\Event_Type::$psr_level.
387 Access to protected property Underpin\Abstracts\Event_Type::$psr_level.
------ -------------------------------------------------------------------------
It would be nice if loader registries could override the default export
method with an array-based method. Right now the only way to do this is by creating a new PHP class and manually overriding the method.
This should either use the get_key()
method, or specify one directly.
e.g. unknown ServiceProvider
class in Plugin_Builder_Provider
@alexstandiford If you are interested in more
composer require --dev szepeviktor/phpstan-wordpress
vendor/bin/phpstan analyze -c vendor/szepeviktor/phpstan-wordpress/extension.neon lib/ --level=0
It's possible to create a factory class instead of extending an abstract class when registering things. It would be nice if most of the current abstract classes had some way to construct inline with an array instead of always needing to create a class.
This would make it so that super simple registered items could be done quickly, and if it were ever necessary to turn it into a proper extended class, it could be done with minimal concerns for backcompat.
The dependency processor has a sorting algorithm that re-runs every time it is called. It would be much faster if this was stored in the WordPress object cache. Not only will this make this faster in the current call, any host that utilizes this caching mechanism will be able to speed up these actions significantly.
Right now, there are a number of circumstances where Underpin generates a hash, and sometimes that has is generated from an array of arguments. These are all normalized in their own places using some basic sorting functionality when-necessary. It would be nice Underpin had some built-in functions to handle some annoying PHP things that are common, like:
I don't know where, but number 2 in-particular will ineviatably cause issues, or because it's not being done right, we're probably running into potential for hash collissions.
Introducing these methods would make it a little easier to work with arrays and hashes while also ensuring that current hashes work consistently.
The hash should also always prefix hashes with the string underpin
at the beginning. This will allow distributed plugins that use Underpin's package command to automatically override this prefix with whatever they rename underpin
to.
Ironically enough, I also think the recursinve array sorting and data normalization should probably be stored in the object cache so it can be accessed again quicker should this function need to run multiple times with the same array.
Hello,
Thanks for creating such a beautiful framework. I would like to use it for my upcoming WordPress projects but I found some issues where I was stuck.
Example: I tried to install the "Underpin Custom Post Type Loader" ext for my plugin but it's give me a fatal error when I tried to the readme.MD introductions. when I check the core "Underpin" plugin I found that the latest version is not supported the "underpin()" function. but the ext's readme.md files are not updated or do not have details for how it's working with the latest version.
I found some issues too when I tried to load packages via composer.
Example: Unerpin doesn't have a version declared on composer but "Underpin Custom Post Type Loader" ext have set version 2.0 for Underpin https://i.imgur.com/FtlpsTw.png :(
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.