Provisional classes in Tinkerwell cover image

Provisional classes in Tinkerwell

Sam Ciaramilaro • May 19, 2022

code

Tinkerwell is an amazing tool by Beyond Code. Like many who use it, I find it an invaluable tool in my development toolbox. But, soon after I began using Tinkerwell, I discovered a little hidden gem.

Provisional classes

Tinkerwell allows you to create "provisional" on-the-fly classes directly in the editor.

For example:

// Within the Tinkerwell Editor
class StringFormatter
{
    public function format(string $string)
    {
        return strtolower($string);
    }
}

$formatter = new StringFormatter();
$formatter->format('TACOS');

// Tinkerwell Output
=> "tacos"

Pretty slick. But, not only can you create these classes in Tinkerwell, you can use them directly with your Laravel applications. For example, you can declare a class in Tinkerwell, instantiate it, and use that instance object as parameter or dependency in your Laravel application.

To illustrate this, let's say you have a service class that performs string formatting, which takes an instance of a FormatterInterface in its constructor. Here's how the interface, the service class, and a concrete implementation of the interface might look like.

// Service class
class StringFormattingService
{
    private \App\Formatting\Formatter $formatter;

    public function __construct(Formatter $formatter)
    {
        $this->formatter = $formatter;
    }

    public function format(string $string): string
    {
        return $this->formatter->format($string);
    }
}

// Formatter interface.
interface Formatter
{
    public function format(string $string): string;
}

// Concrete implementation of the Formatter interface.
class LowercaseFormatter implements Formatter
{
    public function format(string $string): string
    {
        return strtolower($string);
    }
}

// AppServiceProvider: Bind the interface to the implementation.
public function register()
{
    $this->app->bind(Formatter::class, App\Formatters\LowercaseFormatter::class);
}

To see how this functionality works within our application, we can test it Tinkerwell.

// Within the Tinkerwell Editor
$formatterService = app()->make(App\Formatters\StringFormattingService::class);
$formatterService->format('Tacos');

// Tinkerwell Output
=>'tacos'

Use the Tinkerwell provisional class together with your Laravel Service class

Now, let's see how we can test this new implementation of the Formatter interface within Tinkerkwell. We'll create a provisional class that implements the Formatter interface. Then, we'll instantiate the StringFormattingService class, and pass in the new formatter into its constructor.

// Tinkerwell Editor
class UppercaseFormatter implements App\Formatters\Formatter
{
    public function format(string $string): string
    {
        return strtoupper($string);
    }
}

$formatter = new UppercaseFormatter();
$formatterService = new App\Formatters\StringFormattingService($formatter);
$formatterService->format('Tacos');

// Tinkerwell Output
=>'TACOS'

Cool, right? We've just created a class outside of our application, and were then able to use it together with code from our Laravel application.

Binding to the IOC from outside the application

Now for the really interesting stuff. Not only can we manually inject our new class, but we can also override the existing interface binding in the Service Container from within Tinkerwell, replacing it with the new implementation.

// Tinkerwell Editor
class UppercaseFormatter implements App\Formatters\Formatter
{
    public function format(string $string): string
    {
        return strtoupper($string);
    }
}

// Bind the Formatter interface to the new class.
app()->bind(App\Formatters\Formatter::class, UppercaseFormatter::class);
$formatterService = app(App\Formatters\StringFormattingService::class);
$formatterService->format('Tacos');

// Tinkerwell Output
=>'TACOS'

This gives us the ability to interact with code and data on a remote server, without having to alter the codebase running on it. Imagine what possibilities this affords us?

Since we could interact with a remote server, we have the possibility of accessing and transforming that data via code, without ever having to make a change to the codebase!

Sometimes, the dataset we're testing with locally just doesn't match all the variety that exists in a production system. Let's say we have an Artisan command that generates a report, and this command has a dependency on our StringFormatterService. If we needed to change how the report was formatted, we could easily create an different implementation of the Formatter implementation class in Tinkerwell that contains the desired changes, connect to the remote server, and bind this new class to the Service Container. We could then run the Artisan command from within Tinkerwell, and the report would now be using the new service. All without ever having to touch the existing codebase. Now, that's pretty powerful!

I'd like to say that I'm not advocating for or against running rogue code on a production server as a best practice, but I'm just pointing out that the possibility exists. As always, let best judgment prevail.

If you'd like to find out more about Tinkerwell, visit the official Tinkerwell page.

I hope you will find this useful. Feel free to DM me with any feedback or comments. 😊