Resources

A resource makes a kinetis/orm entity administrable. It is a class extending Kinetis\Vector\VectorResource with a #[Resource] attribute, discovered anywhere under your application namespace.

use Kinetis\Vector\Attributes\Resource;
use Kinetis\Vector\Fields\{BelongsTo, Boolean, Enum, Id, ManyToMany, Money, Number, Text};
use Kinetis\Vector\VectorResource;

#[Resource(entity: Product::class, group: 'Catalog', icon: 'mdi-package-variant', title: 'name')]
final class ProductResource extends VectorResource
{
    public function fields(): array
    {
        return [
            Id::make(),
            Text::make('sku', 'SKU')->required()->max(40)->sortable()->searchable()->filterable(),
            Text::make('name')->required()->max(200)->sortable()->searchable()->filterable(),
            Enum::make('status')->of(ProductStatus::class)->sortable()->filterable(),
            BelongsTo::to('category', CategoryResource::class)->sortable()->filterable(),
            Money::make('priceCents', 'Price')->currencyField('currency')->summarize('avg'),
            Number::make('stock')->sortable()->filterable()->summarize('sum'),
            Boolean::make('isFeatured', 'Featured')->filterable(),
            ManyToMany::to('tags', TagResource::class),
        ];
    }

    public function defaultSort(): array { return ['-createdAt']; }
    public function softDeletes(): string { return 'deletedAt'; }
    public function actions(): array { return [PublishProducts::class]; }
    public function widgets(): array { return [ProductsByStatus::class]; }
}

#[Resource] names the entity, the menu group, an icon (a Material Design Icons name) and the title field that names a record in breadcrumbs, relations and search. slug overrides the URL name; by default the mapped table name is used with underscores replaced by dashes. label and labelSingular override the display names derived from the class.

Several resources over one entity

An entity may have more than one resource: a second view of the same rows with its own fields, policy, scope and actions, such as the demo’s profiles beside users. Each needs its own slug, since the default one, the table name, can only be taken once. Each is a resource in its own right: its policy, writes, audit trail, jobs, MCP tools and menu entry are keyed by its slug.

A relation that names its resource, BelongsTo::to('owner', ProfileResource::class), links to that one. A relation that names none — a field derived from the entity’s mapping — links to the entity’s relation target: its only resource, or, when it has several, the one marked relationTarget: true:

#[Resource(entity: User::class, title: 'name', relationTarget: true)]
final class UserResource extends VectorResource { /* ... */ }

#[Resource(entity: User::class, slug: 'profiles', title: 'name')]
final class ProfileResource extends VectorResource { /* ... */ }

Compilation fails when an entity with several resources marks none of them, or more than one, naming the entity and every resource over it. An entity with a single resource needs no mark.

A resource’s hooks and policy govern its own requests only. The demo’s UserResource refuses to delete the last admin in beforeDelete(), which a delete through profiles would not run, so profiles refuses create and delete in its policy instead.

Derived or declared fields

An empty resource derives every field from the entity’s mapping: a string column becomes a searchable Text, a backed enum a filterable Enum, a #[BelongsTo] a relation when its target has a resource, and so on. Declaring fields() replaces derivation entirely. See Fields.

Definition methods

These run once, at compile time, on an instance built without arguments, and return plain data:

Method

Declares

fields()

The fields, in order, with optional sections and tabs; see Forms.

actions()

Action classes; see Actions.

widgets()

Widget classes shown above the list; see Dashboards and widgets.

policy()

The policy class; see Authorization.

defaultSort()

The list’s order, such as ['-createdAt', 'name'].

perPage(), perPageOptions(), maxPerPage()

The default page size, the sizes offered, and the most one request may ask for.

softDeletes()

The nullable DateTimeImmutable property that marks a record trashed.

timestamps()

The properties Vector stamps on create and on every update; createdAt and updatedAt by default.

exportLimit(), exportQueueLimit(), bulkLimit()

Per-resource limits; see Limits and guarantees.

badge()

A count beside the resource in the menu.

replicable()

Whether records can be duplicated; true by default.

searchSubtitle(), searchImage()

What global search and a relation’s choice list show under the title and as a thumbnail.

Request-time hooks

These run on an instance the request container builds, so they may inject anything a controller may:

  • scope(Query $query, CurrentUserInterface $user) narrows the rows a user sees — everywhere: lists, counts, exports, badges, search.

  • create() builds a new entity from the validated values, and apply() writes them to a loaded one. Returning null or false leaves it to Vector’s default writer.

  • beforeCreate(), beforeUpdate() and beforeDelete() run after validation and before the flush; afterCreate(), afterUpdate() and afterDelete() run after the flush committed. Each receives the entity and a WriteContext.

Badges

A badge counts the records matching a filter, as the user’s own list would count them, and shows the count in the menu:

public function badge(): Badge
{
    return Badge::count(['status' => 'open'], 'info', 'open tickets');
}

The filter uses the list grammar and must name filterable fields, with operators they take. It is counted with the resource’s scope and soft deletion applied. The UI polls GET /vector/badges every minute and after each navigation. A user who may not list the resource, or see a filtered field, gets no badge.

Checked at build

Compilation refuses, naming the class and field: a field that maps no property, a #[BelongsTo] declared without BelongsTo::to(), a relation to an unregistered resource, a sortable field without a column, a searchable field that is not a string column, a filterable collection, a summary over a non-numeric column, a default sort over a field that is not sortable, an unadministered identifier, a softDeletes() property that is not a nullable DateTimeImmutable, duplicate field, action or widget names, a slug already taken, and an entity with several resources but not exactly one relationTarget. The pages on Fields and Forms list what each feature refuses besides.

Listing what compiled

vector:list compiles the registry afresh, as kinetis build would, and prints its resources, settings groups and dashboards; it reads and writes no cache. A compile error fails it with the same message. The labels are the declared ones, before translation, and every resource is listed whatever its policy: what a given user sees is GET /vector/schema or the MCP tool vector_describe. In the demo:

$ vendor/bin/kinetis vector:list
Resources:
Slug               Label              Group    Entity                          Resource                                Connection
-----------------  -----------------  -------  ------------------------------  --------------------------------------  ----------
agents             Agents             Support  App\Domain\Support\Agent        App\Vector\Support\AgentResource        support
categories         Categories         Catalog  App\Domain\Category             App\Vector\CategoryResource             default
customer-profiles  Customer profiles  Sales    App\Domain\CustomerProfile      App\Vector\CustomerProfileResource      default
customers          Customers          Sales    App\Domain\Customer             App\Vector\CustomerResource             default
labels             Labels             Support  App\Domain\Support\Label        App\Vector\Support\LabelResource        support
order-lines        Order lines        Sales    App\Domain\OrderLine            App\Vector\OrderLineResource            default
orders             Orders             Sales    App\Domain\Order                App\Vector\OrderResource                default
products           Products           Catalog  App\Domain\Product              App\Vector\ProductResource              default
profiles           Profiles           Access   App\Domain\User                 App\Vector\ProfileResource              default
support-teams      Teams              Support  App\Domain\Support\Team         App\Vector\Support\TeamResource         support
tags               Tags               Catalog  App\Domain\Tag                  App\Vector\TagResource                  default
ticket-replies     Ticket replies     Support  App\Domain\Support\TicketReply  App\Vector\Support\TicketReplyResource  support
tickets            Tickets            Support  App\Domain\Support\Ticket       App\Vector\Support\TicketResource       support
users              Users              Access   App\Domain\User                 App\Vector\UserResource                 default

Settings groups:
Name       Label            Class
---------  ---------------  ---------------------------------
branding   Branding         Kinetis\Vector\Settings\Branding
jobs       Background jobs  Kinetis\Vector\Settings\Jobs
languages  Languages        Kinetis\Vector\Settings\Languages
store      Store            App\Vector\Settings\StoreSettings

Dashboards:
Name  Label     Class
----  --------  -----------------------------------
main  Overview  App\Vector\Dashboards\MainDashboard

A resource in no navigation group shows — in the Group column.

See also

  • Fields — the field catalog.

  • Forms — conditions, dependent choices and repeaters.

  • Authorization — who may do what.