flarum / framework

Simple forum software for building great communities.
http://flarum.org/
6.28k stars 826 forks source link

PHP Extender: Serializing / forum api attributes #1624

Closed luceos closed 3 years ago

luceos commented 5 years ago

Previously in order to append attributes to the forum object in js, one would listen to the Serializing event. This event still exists mind you.

In flagrow/passport I would use this to add an attribute to the ForumSerializer that held the configured button title.

It would be useful to have an extender that allows setting attributes on returned payload. How about an Extend\Serializer class?


    /**
     * @param Serializing $event
     */
    public function prepareApiAttributes(Serializing $event)
    {
        if ($event->isSerializer(ForumSerializer::class)) {
            $event->attributes = array_merge($event->attributes, [
                'flagrow.passport.loginTitle' => $this->settings->get('flagrow.passport.button_title', 'Login')
            ]);
        }
    }
tobyzerner commented 5 years ago

FYI the long term goal for this is to have a new "schema" API to define JSON-API attributes - see the following links: https://github.com/tobscure/json-api/issues/126#issuecomment-290463959 https://gist.github.com/tobscure/5e514a700dcb471180369903ab4562c3

However this will not be ready for a while, in the meantime we can implement something like you've suggested :) Will leave the exact implementation to Franz.

franzliedke commented 5 years ago

I am still hoping we can get close to it with the extender. even before tobscure/json-api sees that new release.

franzliedke commented 5 years ago

@tobscure Now that tobscure/json-api-server is a thing... could you post a proposal of what the extender's signature could look like with the "schema" terminology?

franzliedke commented 5 years ago

(I will then try to implement that signature even with the current code - we will see whether all official extensions' use-cases can then be mapped to that concept already.)

tobyzerner commented 5 years ago

Basically:

    new Extend\Api('posts', function (Builder $schema) {
        // extend schema as per tobscure/json-api-server
    }),

We also need a way to register new resource types, which is the same but also need to specify an adapter. Maybe just a third param:

    new Extend\Api('posts', function (Builder $schema) {
        // build schema as per tobscure/json-api-server
    }, new EloquentAdapter(new Post)),
franzliedke commented 5 years ago

Cheers, will try it out. Until tobscure/json-api-server is integrated, I'll probably provide an alternative implementation of the schema builder (which maps to the current constructs), which means no type-hint in the closure for now.

luceos commented 5 years ago

My current serializing extender:

<?php

namespace Flagrow\Byobu\Extend;

use Flarum\Api\Event\Serializing;
use Flarum\Extend\ExtenderInterface;
use Flarum\Extension\Extension;
use Illuminate\Contracts\Container\Container;
use Illuminate\Contracts\Events\Dispatcher;

class ApiAttribute implements ExtenderInterface
{
    protected $mutations = [];

    public function extend(Container $container, Extension $extension = null)
    {
        /** @var Dispatcher $events */
        $events = $container->make(Dispatcher::class);

        $events->listen(Serializing::class, function (Serializing $event) use ($container) {
            foreach ($this->mutations as $mutation) {
                list($serializer, $callable) = $mutation;

                if ($event->isSerializer($serializer)) {
                    $callable = $container->make($callable);
                    $callable($event);
                }
            }
        });
    }

    public function add(string $serializer, $callable)
    {
        $this->mutations[] = [$serializer, $callable];

        return $this;
    }
}

Used in extend.php:

    (new Extend\ApiAttribute)
        ->add(BasicUserSerializer::class, Api\UserAttributes::class),

Where the second argument is an invokable class:

<?php

namespace Flagrow\Byobu\Api;

use Flarum\Api\Event\Serializing;
use Flarum\Api\Serializer\BasicUserSerializer;
use Flarum\User\User;

class UserAttributes
{
    /**
     * @param Serializing $event
     */
    public function __invoke(Serializing $event)
    {
            /** @var User $user */
            $user = $event->model;
            $event->attributes['blocksPd'] = $user->getPreference('blocksPd', false);
    }
}
franzliedke commented 5 years ago

Oh nice, that would even give us support for cases like this one.

Do we want to go with this now as a stop-gap solution? It will have to be deprecated as we move towards the "schema" approach, though.

luceos commented 5 years ago

It will have to be deprecated as we move towards the "schema" approach, though.

@franzliedke Yeah but that was to be expected, I think it's fine. The benefit of having the extenders in core (before too many extensions adopt their own) is that we can globally deprecate them first and then remove them.

tobyzerner commented 5 years ago

I think I would rather try and emulate the schema API as close as possible as a stop-gap

MichaelBelgium commented 4 years ago

I actually needed something like this! Passing the extension settings to the forum front-end.

Would be very helpful if an extender like this gets implemented or an alternative that passes settings to admin/forum front-end at the same time.

Currently this can be done in beta 11 but a lot of people wanna do the same and the event isn't the purpose of it i guess:

use Flarum\Api\Event\Serializing;
use Illuminate\Contracts\Events\Dispatcher;
use Flarum\Settings\SettingsRepositoryInterface;

class SettingsToForum
{
    private $settings;

    public function __construct(SettingsRepositoryInterface $settings) {
        $this->settings = $settings;
    }

    public function subscribe(Dispatcher $events)
    {
        $events->listen(Serializing::class, [$this, 'addSettings']);
    }

    public function addSettings(Serializing $event)
    {
        $event->attributes['mb-discussionviews.show_filter'] = $this->settings->get('michaelbelgium-discussionviews.show_filter', true);
        $event->attributes['mb-discussionviews.abbr_numbers'] = $this->settings->get('michaelbelgium-discussionviews.abbr_numbers', false);
    }
} 
franzliedke commented 4 years ago

@MichaelBelgium Thanks for mentioning it.

I must admit I've been avoiding this one a little bit, but I know that it's going to be one of the most useful extenders, at least when judging by our own bundled extensions.

The thing is, different extensions use quite a few different data sources when extending our various endpoints that I haven't yet found a nice API for it. But I know I need to tackle it soon, if we want to make any progress in the extender area at all.