Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/webchemistry/stimulus


https://github.com/webchemistry/stimulus

Last synced: about 1 month ago
JSON representation

Awesome Lists containing this project

README

        

## Installation

`composer require webchemistry/stimulus`

```neon
extensions:
- WebChemistry\Stimulus\DI\StimulusExtension
```

## Set up extractor and generators

First, set up extractor and generator. [Namespaced](https://stimulus.hotwired.dev/handbook/installing#controller-filenames-map-to-identifiers)
identifiers also supported.

Code with comments (copy-paste code is below):
```php

require __DIR__ . '/vendor/autoload.php';

// directory with controllers, only *_controller.js and *-controller.js are extracted
$extractor = new JavascriptSourceExtractor(__DIR__ . '/js/stimulus/controllers');

// optional, we want UI namespace instead of Ui
$keywords = ['ui' => 'UI'];

// generate classes as Stimulus\*\_*Controller, these classes we don't edit
$originalClassNameConverter = new PrependClassNameConverter(
'Stimulus\\',
new AppendClassNameConverter(
new BaseClassNameConverter($keywords, fn (string $className) => '_' . $className),
'Controller',
),
);

// We want edit these classes
$emptyClassNameConverter = new PrependClassNameConverter(
'App\\Stimulus\\Controller\\',
new AppendClassNameConverter(
new BaseClassNameConverter($keywords),
'Controller',
),
);

// for autocomplete_controller.js these controllers are generated
// class Stimulus\_AutocompleteController
// class App\Stimulus\Controller\AutocompleteController extends Stimulus\_AutocompleteController

// for namespace/autocomplete_controller.js these controllers are generated
// class Stimulus\Namespace\_AutocompleteController
// class App\Stimulus\Controller\Namespace\AutocompleteController extends Stimulus\_AutocompleteController

// Generator generates static methods
$generator = new StaticClassStimulusControllerGenerator($extractor, $originalClassNameConverter);

// Generator generates class with empty body and extends original class (Stimulus\*\_*Controller)
$emptyGenerator = new EmptyClassStimulusControllerGenerator(
$extractor,
$emptyClassNameConverter,
$originalClassNameConverter,
);

// Files are written in app/generated/stimulus/*, Stimulus\ namespace have to be removed from path
$writer = new FilesystemWriter(__DIR__ . '/app/generated/stimulus', 'Stimulus\\');
// Files are written in app/src/Stimulus/Controller/*, App\Stimulus\Controller\ namespace have to be removed from path, if file exists don't rewrite it
$emptyWriter = new FilesystemWriter(__DIR__ . '/app/src/Stimulus/Controller', 'App\\Stimulus\\Controller\\', false);

foreach ($generator->generate() as $generated) {
$writer->write($generated);
}

foreach ($emptyGenerator->generate() as $generated) {
$emptyWriter->write($generated);
}

```

copy-paste code:
```php
require __DIR__ . '/vendor/autoload.php';

$extractor = new JavascriptSourceExtractor(__DIR__ . '/js/stimulus/controllers');

$keywords = ['ui' => 'UI'];

$originalClassNameConverter = new PrependClassNameConverter(
'Stimulus\\',
new AppendClassNameConverter(
new BaseClassNameConverter($keywords, fn (string $className) => '_' . $className),
'Controller',
),
);
$emptyClassNameConverter = new PrependClassNameConverter(
'App\\Stimulus\\Controller\\',
new AppendClassNameConverter(
new BaseClassNameConverter($keywords),
'Controller',
),
);

$generator = new StaticClassStimulusControllerGenerator($extractor, $originalClassNameConverter);

$emptyGenerator = new EmptyClassStimulusControllerGenerator(
$extractor,
$emptyClassNameConverter,
$originalClassNameConverter,
);

$writer = new FilesystemWriter(__DIR__ . '/app/generated/stimulus', 'Stimulus\\');
$emptyWriter = new FilesystemWriter(__DIR__ . '/app/src/Stimulus/Controller', 'App\\Stimulus\\Controller\\', false);

foreach ($generator->generate() as $generated) {
$writer->write($generated);
}

foreach ($emptyGenerator->generate() as $generated) {
$emptyWriter->write($generated);
}

```

## How files are generated

Class `JavascriptSourceExtractor` uses javascript comments for generating.
Each controller must be annotated with `@controller`, actions with `@action` and their parameters with `@param`, values, classes and targets with `@property`.

my_controller.js
```js
/**
* @controller
*
* @property {String} stringValue
*
* @property {HTMLElement[]} itemTargets
* @property {HTMLElement} resultsTarget
*
* @property {String} activeClass
*/
export default class extends Controller {

static targets = ['results', 'item'];

static values = {
string: String,
};

static classes = ['active'];

/**
* @action
*/
switch() {

}

}

```

This PHP class is generated:

```php
declare(strict_types = 1);

/**
* NOTE: This class is auto generated by file: my_controller.js
* Do not edit the class manually
*/

namespace Stimulus;

use WebChemistry\Stimulus\Type\StimulusAction;
use WebChemistry\Stimulus\Type\StimulusController;
use WebChemistry\Stimulus\Type\StimulusTarget;

abstract class _MyController
{

final public const identifier = 'my';

public static function construct(string $stringValue, string $activeClass): StimulusController
{
return new StimulusController(self::identifier, [
'stringValue' => $stringValue,
'activeClass' => $activeClass,
], []);
}

public static function itemTarget(): StimulusTarget
{
return new StimulusTarget(self::identifier, 'itemTarget');
}

public static function switchAction(): StimulusAction
{
return new StimulusAction(self::identifier, 'switch', []);
}

}

```

By default, each property is required, if we want to make optional we have
several ways:

1. add `?` to the end of property name: `@property {String} stringValue?`
2. add `{ optional }` to the 3rd section (options) of property: `@property {String} stringValue {optional}`
3. add hasser `@property {Boolean} hasStringValue`

## Javascript types and PHP types

webchemisty/stimulus introduces stricter environment for writting application.
Nowadays, everyone use static analysis (at least they should) so correct types are crucial.

Library converts javascript types in the following way:

`Number` => `int|float` narrowing is achieved by options (3rd section) ` { number: int } ` \
`Array` and `Object` => `mixed[]` \
`Boolean` => `bool` \
`String` => `string` \
`other` => `mixed`

Arrays: \
`String[]` => `string[]` \
`Number[]` => `array` narrowing: `{ number: float }` \
`Bool[]` => `bool[]` \
`other` => `mixed[]`

## Custom types

Sometimes we need overriding comment types `@param ...` and types `method(... $type)`
fot this there is options `type` and `commentType` e.g. ` { type: mixed, commentType: "array" } `

## Action parameters

Stimulus 3 introduced [parameters](https://stimulus.hotwired.dev/reference/actions#action-parameters) for actions.
For generating just use intersection type or just object type.

```js
export default class extends Controller {

/**
* @action
* @param { { params: { value: String } } & PointerEvent} event
*/
switch(event) {
const { value } = event.params;
}

/**
* @action
* @param { { params: { value: String } } } event
*/
switchTwo(event) {
const { value } = event.params;
}

}
```

## Usage in PHP

```php
use WebChemistry\Stimulus\Renderer\HtmlRenderer;

$htmlAttributes = HtmlRenderer::render(
App\Stimulus\Controller\MyController::construct('string', 'activeClass'),
App\Stimulus\Controller\MyController::switchAction()->event('click'),
); // data-controller="my" data-my-string-value="string" data-my-active-class="active" data-action="click->my#switch"

// or as array [attribute => value]

HtmlRenderer::toArray(...);
```

## Usage in Latte

```html


```

## Controller as service

Sometimes we want to inject other services:

```php
declare(strict_types = 1);

namespace App\Stimulus\Controller;

use Stimulus\_MyController as ParentController;
use WebChemistry\Stimulus\Type\StimulusController;

final class MyController extends ParentController
{

public function __construct(
private LinkGenerator $linkGenerator,
) {}

public function doConstruct(): StimulusController
{
return self::construct($this->linkGenerator->link('...'));
}

}

```