Hello π,
Since the last update I have completed the porting of extension manager we released last time onto the 2.x branch.
More importantly, I've been tackling what appears to be the most challenging task yet for our 2.0 roadmap: the refactoring of the JSON:API Layer. This is a developer-facing change, so there won't be any visible alterations from the end-user perspective.
Essentially, instead of necessitating extensive boilerplate for model CRUD operations, the refactored code aims to leverage a more abstract yet flexible API.
The new API is built upon the incredible package tobyzerner/json-api-server. (For those interested, you can delve into the current - albeit somewhat dry - details of the refactor process here: flarum/frameworkissues/3964*)
To illustrate, consider the Group model. Currently, we require the following boilerplate:
Api\Controller
- CreateGroupController.php
- ListGroupsController.php
- ShowGroupController.php
- UpdateGroupController.php
- DeleteGroupController.php
Api\Serializer
- GroupSerializer.php
Api\routes.php
- ->get('/')
- ->get('/{id}')
- ->post('/')
- ->patch('/{id}')
- ->delete('/{id}')
Group\Command
- CreateGroup.php
- CreateGroupHandler.php
- DeleteGroup.php
- DeleteGroupHandler.php
- EditGroup.php
- EditGroupHandler.php
However, all of the above is replaced with a single GroupResource
class:
class GroupResource extends AbstractDatabaseResource
{
public function type(): string
{
return 'groups';
}
public function model(): string
{
return Group::class;
}
public function scope(Builder $query, Context $context): void
{
$query->whereVisibleTo($context->getActor());
}
public function endpoints(): array
{
return [
Endpoint\Create::make()
->authenticated()
->can('createGroup'),
Endpoint\Update::make()
->authenticated()
->can('edit'),
Endpoint\Delete::make()
->authenticated()
->can('delete'),
Endpoint\Show::make(),
Endpoint\Index::make(),
];
}
public function fields(): array
{
return [
Schema\Str::make('nameSingular')
->requiredOnCreate()
->get(function (Group $group) {
return $this->translateGroupName($group->name_singular);
})
->set(function (Group $group, $value) {
$group->rename($value, null);
})
->writable()
->required(),
Schema\Str::make('namePlural')
->requiredOnCreate()
->get(function (Group $group) {
return $this->translateGroupName($group->name_plural);
})
->set(function (Group $group, $value) {
$group->rename(null, $value);
})
->writable()
->required(),
Schema\Str::make('color')
->nullable()
->writable(),
Schema\Str::make('icon')
->nullable()
->writable(),
Schema\Boolean::make('isHidden')
->writable(),
];
}
public function sorts(): array
{
return [
SortColumn::make('nameSingular'),
SortColumn::make('namePlural'),
SortColumn::make('isHidden'),
];
}
private function translateGroupName(string $name): string
{
$translation = resolve(TranslatorInterface::class)->trans($key = 'core.group.'.strtolower($name));
if ($translation !== $key) {
return $translation;
}
return $name;
}
public function deleting(object $model, Context $context): void
{
$this->events->dispatch(
new Deleting($model, $context->getActor(), [])
);
parent::deleting($model, $context);
}