PHPackages                             webchemistry/stimulus - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. webchemistry/stimulus

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

webchemistry/stimulus
=====================

v3.0.5(1y ago)03.2k↓75%PHPPHP &gt;=8.0

Since Sep 16Pushed 1y ago1 watchersCompare

[ Source](https://github.com/WebChemistry/stimulus)[ Packagist](https://packagist.org/packages/webchemistry/stimulus)[ RSS](/packages/webchemistry-stimulus/feed)WikiDiscussions master Synced 1mo ago

READMEChangelog (10)Dependencies (10)Versions (11)Used By (0)

Installation
------------

[](#installation)

`composer require webchemistry/stimulus`

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

Set up extractor and generators
-------------------------------

[](#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):

```
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:

```
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
-----------------------

[](#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

```
/**
 * @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:

```
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
------------------------------

[](#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` =&gt; `int|float` narrowing is achieved by options (3rd section) `{ number: int }`
`Array` and `Object` =&gt; `mixed[]`
`Boolean` =&gt; `bool`
`String` =&gt; `string`
`other` =&gt; `mixed`

Arrays:
`String[]` =&gt; `string[]`
`Number[]` =&gt; `array` narrowing: `{ number: float }`
`Bool[]` =&gt; `bool[]`
`other` =&gt; `mixed[]`

Custom types
------------

[](#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
-----------------

[](#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.

```
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
------------

[](#usage-in-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
--------------

[](#usage-in-latte)

```

```

Controller as service
---------------------

[](#controller-as-service)

Sometimes we want to inject other services:

```
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('...'));
    }

}
```

###  Health Score

36

—

LowBetter than 82% of packages

Maintenance42

Moderate activity, may be stable

Popularity19

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity62

Established project with proven stability

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~137 days

Recently: every ~227 days

Total

10

Last Release

469d ago

Major Versions

v1.0.1 → v2.0.02022-07-08

v2.1.0 → v3.0.02022-07-16

### Community

Maintainers

![](https://www.gravatar.com/avatar/312e788a47a251e05734378921d4596a91819b7de416fa18e77aa69e08798ea8?d=identicon)[Antik](/maintainers/Antik)

---

Top Contributors

[![MartkCz](https://avatars.githubusercontent.com/u/10145362?v=4)](https://github.com/MartkCz "MartkCz (37 commits)")

###  Code Quality

Static AnalysisPHPStan

Type Coverage Yes

### Embed Badge

![Health badge](/badges/webchemistry-stimulus/health.svg)

```
[![Health](https://phpackages.com/badges/webchemistry-stimulus/health.svg)](https://phpackages.com/packages/webchemistry-stimulus)
```

###  Alternatives

[crescat-io/saloon-sdk-generator

Simplified SDK Scaffolding for Saloon

13130.9k7](/packages/crescat-io-saloon-sdk-generator)[craftcms/generator

Craft CMS component generator

88377.0k38](/packages/craftcms-generator)[beacon-hq/bag

A comprehensive immutable value objects implementation

1789.1k3](/packages/beacon-hq-bag)[okapi/aop

PHP AOP is a PHP library that provides a powerful Aspect Oriented Programming (AOP) implementation for PHP.

3812.0k](/packages/okapi-aop)[grifart/scaffolder

Class scaffolder. Write definition, generate simple value holders. Useful for trivial composite types used in event sourced applications - for commands, events and query definitions classes. This mostly supplements public readonly $properties

1017.7k2](/packages/grifart-scaffolder)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
