Skip to main content

Attribute

This module allows for user-made attributes to be appended to (resource) as fields.

Every attribute acts like a normal field; they are added to forms, detail page and the index page.

Installation

To install this module:

composer require qore/attribute
php artisan vendor:publish --tag=qore.attribute.db
php artisan vendor:publish --tag=qore.attribute.frontend
php artisan vendor:publish --tag=qore.attribute.config

Usage

Make sure to migrate (php artisan tenants:migrate)

Getting started

In the published config file attribute.php, you need to configure which resources should allow for attributes:

/**
* The resourced allowed to have attributes
*/
'resources' => [
// MyResource::class
],
success

After enabling the module, you should be ready to use attributes now.

Creating a new attribute

In the interface, you can start creating attributes by going to /resources/attributes. You can also add a menu item:

$tab->addResourceMenuItem(resource(AttributeResource::class));

When you create an attribute, you can select a type. These types are defined in the config attribute.php file. You can add your own types here:

'types' => fn () => [
[
'label' => __('attribute::attribute.Text'),
'value' => AttributeType::TEXT->value
],
[
'label' => __('attribute::attribute.Checkbox'),
'value' => AttributeType::CHECKBOX->value
],
[
'label' => __('attribute::attribute.Dropdown'),
'value' => AttributeType::SELECT->value
],
[
'label' => __('attribute::attribute.Multiselect'),
'value' => AttributeType::MULTISELECT->value
]
],
info

Note that if you add a type to the config file, you should also add a custom factory and add it to the factories array

Automatically add attributes to your resource

By default, if you create an attribute, select a resource, and mark them as active (with the checkbox), they should automatically be added to your resource.

To prevent auto-adding attributes to your resource, set this to false in the config file:

'add_fields_automatically' => false,
info

Note that if you use a fieldLayout in a resource, you will need to add your attribute names manually to the fieldLayout array.

Manually append attributes to your fieldLayout

You can add attributes to your fieldLayout like this:

public function fieldLayout(bool $forDetail, ?Model $model = null): ?FieldLayout
{
$cards = [
'Detail' => [
'gender',
['first_name', 'last_name']
],
'Attributes' => [
// Attributes will be appended here
]
];

foreach ($this->getAttributes() as $attribute) {
$cards['Attributes'][] = ($attribute->field_name);
}

return (new FieldLayout())->cards($cards);
}

private function getAttributes(): Collection
{
$resource = $this;

// It's good practise to cache the results of this query (at least for the current request)
return request_cache("attributes_{$resource->name()}", function() use ($resource) {
return Attribute::isActive()->forResource($resource)->get();
});
}

Manually append attributes to your fields

If you need to add attributes fields manually to your resource, you can do the following:

public function fields(): FieldCollection 
{
$fields = new FieldCollection( /* your fields */ );

foreach ($this->getAttributes() as $attribute) {
$fields->add(attribute_to_field($attribute, $this->model()));
}

return $fields;
}

private function getAttributes(): Collection
{
$resource = $this;

// It's good practise to cache the results of this query (at least for the current request)
return request_cache("attributes_{$resource->name()}", function() use ($resource) {
return Attribute::isActive()->forResource($resource)->get();
});
}

The attribute_to_field function returns an instance of the field which extends Field, so if necessary, you can chain any methods on it, for example:

foreach ($this->getAttributes() as $attribute) {
$fields->add(
attribute_to_field($attribute, $this->model())
->setColumnWidth(100)
);
}

Attribute type value resolving

Simple attributes like Text and Select will just store a string value in the database. However, a Checkbox for example will cast the value to a boolean, and a MultiSelect will store options as json.

You can define how values will be stored in the database by overriding the following in the attribute.php config file:

/**
* When the value of an attribute value is accessed ($attributeValue->value),
* resolve the value based on the attribute type
*/
'attribute_value_resolver' => function(AttributeValue $attributeValue) {

$attributeType = AttributeType::tryFrom($attributeValue->attribute->type);

if ($attributeType) {
return $attributeType->resolveValue($attributeValue);
}

return $attributeValue->raw_value;
}

Using attributes outside of resources

Sometimes you can have attributes that are not linked to any resource, but still need to append them to fields.

For example, let's say we have a Test action, and we want to append attributes here:

class Test extends Action
{
public function title(): string
{
return 'I am some action';
}

public function fields(): FieldCollection
{
return new FieldCollection(
attribute_to_field(
Attribute::where('name', 'My custom attribute')
->whereNull('resource_names')
->first()
)
);
}

public function run(Model $model, array $arguments): void
{
dd($arguments);
}
}

Because this attribute is not linked to a resource, it will throw an exception because it doesn't know which model is related to it. In order to make it work, we just need to provide a model class:

attribute_to_field(
Attribute::where('name', 'My custom attribute')
->whereNull('resource_names')
->first(),
Employee::class
)

Now, when run is called, you should see the value of the custom attribute.

Upgrade Guide

To upgrade this module:

composer update qore/attribute

If you need to upgrade migrations, config or Vue components:

php artisan vendor:publish --tag=qore.attribute.db --force
php artisan vendor:publish --tag=qore.attribute.frontend --force
php artisan vendor:publish --tag=qore.attribute.config --force