Tempest is still a work in progress. Visit our GitHub or Discord

Package Development

Tempest comes with a handful of tools to help third-party package developers.

#[DoNotDiscover]

Tempest has an attribute called #[DoNotDiscover], which you can add on classes. Any class within your package that has this attribute won't be discovered by Tempest. You can still use that class internally, or allow you package to publish it (see installers).

use Tempest\Core\DoNotDiscover;

#[DoNotDiscover]
final readonly class UserMigration implements Migration
{
    // …
}

Installers

Packages can have one or more installers, which can be used to set up your package within a project. For example: a package can optionally publish migration files that will only be discovered when they are published. Take, for example, a look at the AuthInstaller, which will install the User model, as well as other related models and their migrations:

use Tempest\Core\DoNotDiscover;
use Tempest\Core\Installer;
use Tempest\Core\PublishesFiles;
use Tempest\Generation\ClassManipulator;
use function Tempest\src_namespace;
use function Tempest\src_path;

final readonly class AuthInstaller implements Installer
{
    use PublishesFiles;

    public function getName(): string
    {
        return 'auth';
    }

    public function install(): void
    {
        $publishFiles = [
            __DIR__ . '/User.php' => src_path('User.php'),
            __DIR__ . '/UserMigration.php' => src_path('UserMigration.php'),
            __DIR__ . '/Permission.php' => src_path('Permission.php'),
            __DIR__ . '/PermissionMigration.php' => src_path('PermissionMigration.php'),
            __DIR__ . '/UserPermission.php' => src_path('UserPermission.php'),
            __DIR__ . '/UserPermissionMigration.php' => src_path('UserPermissionMigration.php'),
        ];

        foreach ($publishFiles as $source => $destination) {
            $this->publish(
                source: $source,
                destination: $destination,
            );
        }
    
        $this->publishImports();
    }
}

Running the installer looks like this:

./tempest install auth

Running the `auth` installer, continue? [yes/no] 

app/User.php already exists. Do you want to overwrite it? [yes/no] 
app/User.php created 

app/UserMigration.php already exists. Do you want to overwrite it? [yes/no] 

app/Permission.php already exists. Do you want to overwrite it? [yes/no]
 
app/PermissionMigration.php already exists. Do you want to overwrite it? [yes/no] 
app/PermissionMigration.php created 

app/UserPermission.php already exists Do you want to overwrite it? [yes/no]
Done 

Note that you can use src_path() to generate paths to the project's source folder. This folder be either src/ or app/, depending on the project's preferences. If you're using the PublishesFiles trait, then Tempest will also automatically adjust class namespaces and remove #[DoNotDiscover] attributes when publishing files.

On top of that, you can pass a callback to the publish() method, which gives you even more control over the published files:

public function install(): void
{
    // …
    
    $this->publish(
        source: $source,
        destination: $destination,
        callback: function (string $source, string $destination): void {
            // …
        },
    );
    
    $this->publishImports();
}

Installers are resolved via the container, which means you can inject any dependency you need. You can also interact with the console from within the install() method if you want to (if you use the PublishesFiles trait, you have the console available automatically):

final readonly class MyPackageInstaller implements Installer
{
    use PublishesFiles;

    public function getName(): string
    {
        return 'my-package';
    }

    public function install(): void
    {
        $base = $this->ask('Which subfolder do you want to store the files in?');
        
        $this->publish(
            source: __DIR__ . '/file.php',
            destination: src_path($base, 'file.php'),
            callback: function (string $source, string $destination): void {
                // …
            },
        );
        
        $this->publishImports();
    }
}

Finally, Installers will be discovered by Tempest, so you only need to implement the \Tempest\Core\Installer interface.

Publishing imports

As you can see in the previous examples, $this->publishImports() is always called within the install() method. Calling this method will loop over all published files, and adjust any imports that reference to published files.

Testing helpers

Tempest provides a class called \Tempest\Framework\Testing\IntegrationTest. Your PHPUnit tests can extend from it. By doing so, your tests will automatically boot the framework, and have a range of helper methods available.