> >

Payment is more than just payment
Laravel-workflow

In this article, we'll learn how Laravel Workflow can simplify and manage our workflows, making them easier to understand. We'll also look at the main idea behind the package.

Khalil Bouzidi
Payment is more than just payment  <br> Laravel-workflow

Intro:

First, let's clarify that "payment" is just an example here. The idea is that some features are built from multiple actions. As the project grows, understanding the inputs and outputs of a feature becomes increasingly challenging.

Understanding the code often requires more than just reading it. You need a good grasp of the framework, including components like events, listeners, and observers. Additionally, navigating through the code from file to file can be challenging, especially when there’s no documentation available, which is quite common, and this can leave you feeling lost and unsupported.

pablo after reading code

A ~week ago, Povilas Korop made a LinkedIn post about the same subject.

Povilas here discusses how respecting SOLID principles, especially the separation of concerns, and clean code rules can complicate even a simple task, which is why he prefers 'longer' or 'fat' controller methods.

Working with legacy code full of hidden logic has made me think how we can follow SOLID principles, keep our code clean, and stick to framework best practices without getting lost!

I need one place where I can encapsulate the entire feature so that a junior or newly hired developer can see this class and understand everything about it, at the same time without violating these principles.

That’s the idea behind Laravel Workflow to bundle all the details of a workflow into one class, where you can find everything related to that feature.

- The actions chain with their state, knowing which actions failed or succeeded.

- The hidden logic, like observers, where you can register them from inside the workflow class itself.

- Standard responses.

- Event tracking to understand what happened during the workflow execution.

- I will add more 🚀

Demo Time:

Now, let's see a simple demo:

Let's start by installing the package via Composer:

composer require safemood/laravel-workflow

First, we need to generate a workflow class using this artisan command:

php artisan make:workflow PaymentWorkflow

We will also need some action classes. Additionally, we can use Artisan to execute:

php artisan make:workflow-action ValidateCartItems
php artisan make:workflow-action CalculateTotal
php artisan make:workflow-action MakePayment
php artisan make:workflow-job SendEmails 

Our workflow class will be structured as follows:

<?php

namespace App\Workflows;

use App\Actions\CalculateTotal;
use App\Actions\MakePayment;
use App\Actions\ValidateCartItems;
use App\Events\PaymentProcessed;
use App\Jobs\SendEmails;
use App\Observers\UserObserver;
use App\Models\Order;
use Safemood\Workflow\WorkflowManager;

class PaymentWorkflow extends WorkflowManager
{
    public function handle()
    {
        // Actions to be executed before the main action
        $this->addBeforeActions([
            new ValidateCartItems(),
            new CalculateTotal()
        ]);
	  
	    // Conditional Action Execution
	  	$this->when(auth()->user()->isPremium(), function () {
            $this->addBeforeActions([
                new DiscountAction(),
            ]);
        });

        // The main action of the workflow
        $this->addMainAction(new MakePayment());

        // Actions to be executed after the main action
        $this->addAfterAction(new SendEmails()); // Normal laravel Job in this example

        // Observers to register for specific entities
        $this->registerObservers([
            Order::class => OrderObserver::class,
        ]);

        // Good Debugging or if you want to understand what is happining during the workflow execution: 
	  
	   $this->trackEvents([
            PaymentProcessed::class
        ]);
	  
       // $this->trackAllEvents(); // or

       // $this->trackEventsIn('App\Events\\'); 

       
    }
}

They will be executed before our main action and are useful for tasks like validation, data preparation, and so on..

<?php

namespace App\Actions;

use Safemood\Workflow\Action;

class ValidateCartItems extends Action
{
    public function handle(array &$context)
    {
        // Simulate extra validation logic
        if (empty($context['cart'])) {
            throw new \Exception('Cart is empty');
        }

       // you can pass data to the next action if you want
	  $context['validated'] = true; 
        
    }
}

We will simulate the payment logic in the MakePayment class, which serves as the main action in the chain.

<?php

namespace App\Actions;

use App\Events\PaymentProcessed;
use Safemood\Workflow\Action;
use App\Models\User;
use Exception;

class MakePayment extends Action
{
    public function handle(array &$context)
    {
        // Simulate payment logic
        try {
            // Load a user from the database
            $user = User::find($context["user_id"]);

            if (!$user) {
                throw new Exception("User not found");
            }

            // For example, update user's payment status or perform external API calls

            // Dispatch an event to notify payment processed
            PaymentProcessed::dispatch([
                "user" => $user,
                "amount" => $context["amount"],
            ]); // This will create a new order
        } catch (Exception $e) {
            throw $e;
        }
    }
}

SendEmails is a typical Laravel job that will be dispatched after all preceding actions have successfully completed. It handles tasks such as sending email notifications related to the completed payment process.

Now our checkout can be something like this:

<?php

namespace App\Http\Controllers;

use App\Workflows\PaymentWorkflow;
use Illuminate\Http\Request;

class PaymentController extends Controller
{
    public function payment(Request $request)
    {
        // Example context data representing a user's cart and user information
        $context = [
            'cart' => [
                ['id' => 1, 'name' => 'Product A', 'price' => 100, 'quantity' => 2],
                ['id' => 2, 'name' => 'Product B', 'price' => 50, 'quantity' => 1]
            ],
            'user_id' => 123
        ];

        // Execute the PaymentWorkflow with the provided context
        $paymentWorkflow = (new PaymentWorkflow)->run($context);

        // Check if the workflow execution was successful
        $success = $paymentWorkflow->passes();

        // Check if the workflow execution failed
        $failure = $paymentWorkflow->failed();

        // Handle the response based on the workflow outcome
        if ($success) {
            return $paymentWorkflow->successResponse();
        }  

        return $paymentWorkflow->failureResponse();
    }
}

Here, we execute our PaymentWorkflow, which processes all actions and determines whether the workflow completed successfully or encountered any issues.

In closing, I think this demo has reached its end, and I want to thank you for reading. I hope you learned something from it!

✨ I also want to point out that the package is fairly new, and I’m excited to hear your feedback! Your thoughts will not only inspire me but also help shape future versions of the package.

If you want to contact me, you can reach me on LinkedIn || Twitter || Github!.

I welcome you to reach out and say hello. Meeting new people and expanding my network is something I always look forward to! 😊

You like it,it will be nice if you share it

🍪 Cookie Notice

We use cookies to ensure you have a delightful time on our website, by continuing to browse this website, you consent to our delicious cookies. Read our privacy policy.