# Smart Actions

{% hint style="success" %}
This is the official documentation of the `forestadmin/laravel-forestadmin` v2+ and `forestadmin/symfony-forestadmin` PHP agents.
{% endhint %}

In legacy agents declaring a Smart Action was a two-step process:

* First, you had to declare by changing the parameters of the `collection` function in the appropriate `models/*.php` file.
* Then, you had to implement the action by creating a route handler in the appropriate `routes/web.php` file.

  In the new agent, the process is simplified to a single step.

{% hint style="info" %}
You can find the full documentation of action customization [here](https://docs.forestadmin.com/developer-guide-agents-php/agent-customization/actions).
{% endhint %}

## Code cheatsheet

| Legacy agent                                            | New agent                                                                          |
| ------------------------------------------------------- | ---------------------------------------------------------------------------------- |
| <p>type: 'single'<br>type: 'bulk'<br>type: 'global'</p> | <p>scope: 'Single'<br>scope: 'Bulk'<br>scope: 'Global'</p>                         |
| download: true                                          | generateFile: true                                                                 |
| reference: 'otherCollection.id'                         | { type: 'Collection', collectionName: 'otherCollection' }                          |
| enums: \['foo', 'bar']                                  | { type: 'Enum', enumValues: \['foo', 'bar'] }                                      |
| Request object                                          | context.getRecordIds()                                                             |
| Response object                                         | <p>return resultBuilder.success(...)<br>return resultBuilder.error(...)<br>...</p> |

## Steps

### Step 1: Calling `addAction` for the appropriate collection

Start by calling the `addAction` function on the appropriate collection and passing the appropriate parameters.

Most notably, you will need to pass:

* `type` should become `scope`
  * Note that the values are now capitalized (e.g. `single` becomes `Single`)
  * Legacy agents defaulted to `'bulk'` if no type was specified. The new agent requires you to specify the scope.
* `download` should become `generateFile`. This is still a boolean and the same value can be passed.
* `endpoint` and `httpMethod` should be removed. The agent will now automatically handle the routing.

{% tabs %}
{% tab title="Before" %}

```php
public function markAsLive(): SmartAction
{
    return $this->smartAction('single', 'Mark as Live');
}
```

{% endtab %}

{% tab title="After" %}

```php
$forestAgent->customizeCollection(
    'Company',
    function (CollectionCustomizer $builder) {
        $builder->addAction(
            'Mark as live',
            new BaseAction(
                scope: ActionScope::SINGLE,
                execute: function(ActionContextSingle $context) {}
            )
        );
   }
);
```

{% endtab %}
{% endtabs %}

### Step 2: Porting the form definition

Forms are now defined in the `form` property of the action.

You can simply copy the field's definition from the legacy agent to the new agent with the following differences:

* `fields` should become `form`.
* `widget` choice is no longer supported. A default widget will be used depending on the field type.
* `hook` can be removed, those will be handled by the new agent automatically.
* `reference` no longer exists. Use `{ type: 'Collection', collectionName: '...' }` instead.
* `enums` no longer exist. Use `{ type: 'Enum', enumValues: ['...'] }` instead.

{% tabs %}
{% tab title="Before" %}

```php
public function chargeCreditCard(): SmartAction
{
    return $this->smartAction('single', 'Charge credit card')
        ->addField(
            [
                'field' => 'amount',
                'type' => 'Number',
                'is_required' => true,
                'description' => 'The amount (USD) to charge the credit card. Example: 42.50'
            ]
        )
        ...
}
```

{% endtab %}

{% tab title="After" %}

```php
$forestAgent->customizeCollection(
    'Customer',
    function (CollectionCustomizer $builder) {
        $builder->addAction('Charge credit card', new BaseAction(
            scope: ActionScope::SINGLE,
            form: [
                new DynamicField(
                    label: 'amount',
                    type: FieldType::NUMBER,
                    description: 'The amount (USD) to charge the credit card. Example: 42.50',
                    isRequired: true,
                ),
                ...
            ],
            execute: fn ($context) => ...,
        ));
  }
);
```

{% endtab %}
{% endtabs %}

### Step 3: Porting the route to the new agent `execute` function

In the legacy agent, users had to implement the action by creating a route handler in the appropriate `routes/web.php` file.

This is no longer needed as the new agent provides a `context` object that contains all the information that is needed to implement the action.

When porting the route handler to the new agent, you will need to:

* Move the body of the route handler to the `execute` function of the action.
* Replace `RecordsGetter.getIdsFromRequest()` call with `context.getRecordIds()`.
* Replace `return response();` calls with `return resultBuilder->success()` or `return resultBuilder->error()`, or the [appropriate `resultBuilder` method](https://docs.forestadmin.com/developer-guide-agents-php/agent-customization/actions/result-builder).

{% tabs %}
{% tab title="Before" %}

```php
public function markAsLive(): JsonResponse
{
    $id = request()->input('data.attributes.ids')[0];
    $company = Company::findOrFail($id);
    $company->status = 'live';
    $company->save();

    return response()->noContent();
}
```

{% endtab %}

{% tab title="After" %}

```php
$forestAgent->customizeCollection(
    'Company',
    function (CollectionCustomizer $builder) {
        $builder->addAction(
            'Mark as live',
            new BaseAction(
                scope: ActionScope::SINGLE,
                execute: function(ActionContextSingle $context, $resultBuilder) {
                    $companyId = $context->getRecordId();
                    $context->getCollection()->update(['status' => 'live'], new Filter('id', Operator::EQUAL, $companyId));

                    return $resultBuilder->success('Company is now live!');
                }
            )
        );
   }
);
```

{% endtab %}
{% endtabs %}

### Step 4: Porting Smart Action hooks

Load hooks and change hooks have been replaced on the new agent by the possibility to use callbacks in the form definition.

Here is an example of a load hook where the default value of a field is set to 50 euros converted into dollars:

{% tabs %}
{% tab title="Before" %}

```php
public function sendInvoice(): SmartAction
{
    return $this->smartAction('single', 'Charge credit card')
        ->addField(
            [
                'field' => 'country',
                'type' => 'Enum',
                'enums' => [],
            ]
        )
        ->load(
            function () {
                $fields = $this->getFields();
                $fields['country']['enums'] = Company::getEnumsFromDatabaseForThisRecord();

                return $fields;
            }
        )
    ...
```

{% endtab %}

{% tab title="After" %}

```php
$forestAgent->customizeCollection(
    'Company',
    function (CollectionCustomizer $builder) {
        $builder->addAction(
            'Charge credit card',
            new BaseAction(
                scope: ActionScope::SINGLE,
                form: [
                    new DynamicField(
                        label: 'Country',
                        type: FieldType::ENUM,
                        enumValues: fn ($context) => Company::getEnumsFromDatabaseForThisRecord(),
                    ),
                ],
            )
        );
    }
);
```

{% endtab %}
{% endtabs %}

And another for a change hook which makes a field required if the value of another field is greater than 100:

{% tabs %}
{% tab title="Before" %}

```php
return $this->smartAction('single', 'Charge credit card')
    ->addField(
        [
            'field' => 'amount',
            'type' => 'Number',
            'hook' => 'onAmountChange',
        ]
    )
    ->addField(
        [
            'field' => 'motivation',
            'type' => 'String',
            'isRequired' => false,
        ]
    )
    ->change(
        [
            'onAmountChange' => function () {
                $fields = $this->getFields();
                $fields['motivation']['isRequired'] = $fields['amount']['value'] > 100;

                return $fields;
            },
        ]
    );
```

{% endtab %}

{% tab title="After" %}

```php
$forestAgent->customizeCollection(
    'Company',
    function (CollectionCustomizer $builder) {
        $builder->addAction(
            'Send invoice',
            new BaseAction(
                scope: ActionScope::SINGLE,
                form: [
                    new DynamicField(
                        label: 'city',
                        type: FieldType::STRING,
                    ),
                    new DynamicField(
                        label: 'zipCode',
                        type: FieldType::STRING,
                        value: function ($context) {
                          if ($context->getFormValues()['city']) {
                            return Company::getZipCodeFromCity(context->getFormValues()['city']['value']);
                          }
                        }
                    ),
                ],
            )
        );
    }
);

```

{% endtab %}
{% endtabs %}
