TypeRocket / core

TypeRocket core source files where all the magic lives.
https://typerocket.com
36 stars 21 forks source link

I would like to make bundles using TypeRocket #21

Closed nebulousGirl closed 5 years ago

nebulousGirl commented 5 years ago

I'm venturing into making a standalone plugin of TypeRocket and creating bundle plugins of specific functionalities to enable better code reuse.

The biggest problem I'm seeing at first look is the reliance on TR_APP_NAMESPACE to instantiate classes. This prevents the use of different namespaces in bundles and create possible name collisions.

I see that it is still the case in version 4.0.0.

Would it be possible to move class name generation to somewhere that would be less hardcoded?

Would bundling be a desirable feature in the core? I could work on it.

kevindees commented 5 years ago

Hey @nebulousGirl

I have looked into this as well and it is not possible without rearchitecting many of the core systems in TypeRocket. The best I can come up with is to have multiple autoloaders defined for the namespace.

nebulousGirl commented 5 years ago

I have come up with a solution (that I install as a MU plugin) that extends many core classes to add a filter on model and controller classnames:

//Lines that look like this:
$controller = "\\" . TR_APP_NAMESPACE . "\\Controllers\\{$prefix}Controller";
//Become this:
$controller = apply_filters('trb_resource_controller_class', "\\" . TR_APP_NAMESPACE . "\\Controllers\\{$prefix}Controller");

I then use the filter to return the correct class. It does not solve possible naming collisions, but it makes the code cleaner since I'm able to separate it in smaller chunks of related content.

It also facilitates code reuse across projects and removes the need to have multiple instances of TypeRocket in the codebase.

I will probably published this on Github once I test it more thoroughly in the project I'm working on.

kevindees commented 5 years ago

Hey @nebulousGirl

Interesting idea. Are you having the same issues with models?

Thanks, Kevin

nebulousGirl commented 5 years ago

Yes, I was showing controllers as an example.

For models:

$model = apply_filters('trb_resource_model_class', "\\" . TR_APP_NAMESPACE . "\\Models\\{$Resource}");

As of now, applying those filters are the only modification I applied to core files.

I have yet to delve into commands as part of a bundle, that's the next step. I have also not tested this with a frontend application yet, but will have to do it on my current project.

Since you are showing some interest in this, I will keep updating this issue with my issues and solutions.

nebulousGirl commented 5 years ago

To be able to set routes in each bundle (plugin), I had to put the actual routing inside "plugins_loaded" action hook in my Launcher::initCore:


    add_action('plugins_loaded', function () {
      $paths = Config::getPaths();
      $routeFile = $paths['base'] . '/routes.php';
      if (file_exists($routeFile)) {
        /** @noinspection PhpIncludeInspection */
        require($routeFile);
      }
      do_action('trb_register_routes');
      (new Routes())->detectRoute()->initHooks();
    }, 0);

I also added an action trb_register_routes to be able to hook into the route registration process.

nebulousGirl commented 5 years ago

To be able to add middleware definitions, I also added a filter in my App Kernel at the beginning of the constructor:

        $this->middleware = apply_filters('trb_middlewares', $this->middleware);
kevindees commented 5 years ago

Hey @nebulousGirl

This is now possible in the master branch. I have a few more tests to run but it seems to be stable.

Post Types and Taxonomies

// @param string $model_class
//  @param string $controller_class
tr_post_type('post')->setHandler(\PluginName\Controllers\PostController::class)

And a hook to deal with it,

do_action('tr_post_type_register_' . $this->id, $this);

Custom Routes

Control what class a resource uses.

tr_route()
  ->get()
  ->match('post/(.*)', ['id'])
  ->do('single@Post:\PluginName\Controllers\PostController::class' );
<?php
namespace PluginName\Controllers;

use TypeRocket\Controllers\WPPostController;

class PostController extends WPPostController
{
    protected $modelClass = Post::class;

    public function single($id)
    {
        return 'hi' . $id;
    }
}
Chematronix commented 4 years ago

setResponder changed to setHandler, so now:

// @param string $model_class
//  @param string $controller_class
tr_post_type('post')->setHandler(\PluginName\Controllers\PostController::class)

Also, the quoted ::class in tr_route() produces Invalid Controller Action: single@PostController:\PluginName\Controllers\PostController::class It should be removed:

tr_route()
  ->get()
  ->match('post/(.*)', ['id'])
  ->do('single@Post:\PluginName\Controllers\PostController' );

Then it works!

Chematronix commented 4 years ago

To get a view's template, I'm passing the full file path to tr_view, like so:

    $views_path = 'app/views';
    define( __NAMESPACE__ . '\VIEWS_PATH', plugin_dir_path( __FILE__ ) . $views_path );
[...]
        return tr_view( VIEWS_PATH . '/test-edit.php', compact( 'id' ) )

And to get a form to work on the frontend, I've to pass the model class to tr_form on the template:

<?php
$form = tr_form( 'test', 'update', $id, \TestPlugin\Test::class );
$form->useURL( 'put', tr_route_url_lookup( 'test-update', [ 'id' => $id ], false ) );

echo $form->open();
echo \TestPlugin\Test::getEditForm( $form );
echo $form->close( 'Submit' );
?>

or else I get Fatal error: Uncaught Error: Call to a member function findById() on null in .../typerocket/vendor/typerocket/core/src/Elements/Form.php on line 78.

Is there a better way of doing these?

kevindees commented 4 years ago

Hey @Chematronix

Is your class string correct? \TestPlugin\Test::class maybe the class does not exist? Did you add an autoloader for your class?

https://www.php.net/autoload

Thanks, Kevin

Chematronix commented 4 years ago

The code works as posted (and yes, I'm using composer's autoloader). I was just wondering if there was a way to avoid having to pass the full path to tr_view(), or the class name to tr_form(), when placing my code in its own plugin.

If I place it with TypeRocket, I can simply do tr_view('test-edit', $data); instead of tr_view( __DIR__ . '/views/test-edit.php', $data); or whatever (just found out how useless plugin_dir_path() is). And on a front-end template I can do tr_form('test', 'test-update', $id); instead of tr_form( 'test', 'update', $id, \TestPlugin\Test::class );

BTW, defining my routes in 'tr_load_routes' works, like so:

add_action( 'tr_load_routes', function () {
    tr_route()->put()->match( 'test/update/(\d*)', [ 'id' ] )->do( 'update@Test:\TestPlugin\TestController' )->name( 'test-update' );
} );

Even with TypeRocket as a mu-plugin, even though the route docs state:

Note: The tr_load_routes hook fire when typerocket is loaded so if your plugin code is loaded after typerocket your routes will not work. For example, an MU plugin install of Typerocket will not let your WordPress plugins use the tr_load_routes hook.

So you might want to remove that.

Cheers!

Chematronix commented 4 years ago

The only missing piece is how to add AuthRead to my custom type.

  1. Will adding it to 'resourceGlobal' apply it to my type, and all others?
  2. Any pointers on how can I modify or swap the Kernel from my stand-alone plugin?