Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

uniqueginun's avatar

Help Refactoring Monstrous Code

Seniors:

I have a controller method that has some complexity and it's doing several tasks at once:

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class RegisterationController extends Controller
{
    public function store(Request $request)
    {
        // 1. check request for user unique identifier. then firstOrCreate this user then pass output to next step
        // 2. take the user and create new (requests) relationship record and pass that to the next step
        // 3. take the relationship record and create 2 (details records) relationships for that and pass the original record to next step
        // 4. run database procedure and take result to update the relationship record 
        // 4. if all of above went well, upload some attachment and add record to attachment table 
    }
}

the current code is working fine. I wrote everything on the store method with help of private functions but I don't like it and I think it's not maintainable and not really OOP.

I'm thinking about using Laravel *** Pipeline *** class and create didecated class for each step but I'm not sure if it's the right thing.

Also I can use Repository pattern but also I don't think it's very useful here.

any tips???

0 likes
14 replies
uniqueginun's avatar

isn't form request for validation/authorization only? I'm talking about dividing multi-step/task control action into separate class for each step/task.

MichalOravec's avatar

@uniqueginun Post your full store method of your controller. To the form request class you can add your full logic.

1 like
MichalOravec's avatar

Yes, that series is the best on Laracasts in my opinion.

But how I said before, form request is enough for your case.

martinbean's avatar

@uniqueginun I’ve done a lot of refactoring projects in the past! It’s always a good feeling to take a large chunk of code and refactor it into “nice” code.

Without being able to see what you’re working with, my first steps would be to extract all of that logic into another method in the controller, so your controllers looks something like this:

class RegisterationController extends Controller
{
    public function store(Request $request)
    {
        $this->handleRegistration($request);

        // Return response
    }

    protected function handleRegistration(Request $request)
    {
        // All of the logic that used to be in the store method, here instead
    }
}

From here, you can then start splitting the handleRegistration method body into other, smaller methods in the controller as well. So eventually, you should end up with a controller with lots of small, protected methods.

From here, you can look to see if there are any classes you can extrapolate, and move those methods to the objects you’ve identified, which will start to clean up your controller.

The key to refactoring is to do it in small, discreet steps. Try to stick to doing one thing at a time. If you try to refactor everything in one go, you’ll just find yourself going down a rabbit hole, and find yourself running git reset far too often. So move slow, write tests, and then move on to another step when you’ve successfully refactored something with your tests still passing.

uniqueginun's avatar

I get it. but at the end if I use form request the form-request controller will end up almost as same as this.

what I really did is I split the controller into many protected methods as @martinbean described. and then created service controller for each related-steps and then moved logic there.

uniqueginun's avatar

@michaloravec

<?php

namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

class UserRegisterationOperation extends Controller
{

    public function store(Request $request)
    {
        DB::transaction();

        try {
            $user = $this->createUserFromRequest($request);

            $task = $user->tasks()->create([
                'name' => $request->name,
                'desc' => $request->task_desc
            ]);

            $files = $this->getFilesData($request);

            $user->uploads()->insert($files);

            DB::commit();

            return response([
                'user' => $user,
                'task' => $task,
                'files' => $files
            ], 201);

        } catch (\Exception $exception) {
            DB::rollBack();
            Log::error($exception->getMessage());
            return response([
                'error' => $exception->getMessage()
            ], 500);
        }

    }

    protected function createUserFromRequest(Request $request): User
    {
        $user = User::firstOrCreate(['email' =>  $request->email], [
                    'name' => $request->name,
                    'password' => bcrypt($request->password)
                ]);

        if (!$user) {
            throw new \Exception('couldn\'t create or fetch user');
        }

        return $user;
    }

    /**
     * @param Request $request
     */
    protected function getFilesData(Request $request)
    {
        $files = collect($request->allFiles())->map(function ($file, $key) {
            return [
                'name' => $key,
                'path' => $file->store('photos')
            ];
        })->values()->toArray();

        if (!$files) {
            throw new \Exception('files not uploaded');
        }

        return $files;
    }
}
MichalOravec's avatar
Level 75

@uniqueginun Your code could look likes this

UserRegisterationOperationController.php

<?php

namespace App\Http\Controllers;

use App\Http\Requests\UserRegisterationOperation\StoreForm;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;

class UserRegisterationOperationController extends Controller
{
    public function store(StoreForm $form)
    {
        try {
            [$user, $task, $files] = $form->persist();

            return response(['user' => $user, 'task' => $task, 'files' => $files], 201);
        } catch (Exception $e) {
            Log::error($e->getMessage());

            return response(['error' => $e->getMessage()], 500);
        }
    }
}

And here is StoreForm what is a form request

<?php

namespace App\Http\Requests\UserRegisterationOperation;

use App\Models\User;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Support\Facades\DB;

class StoreForm extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     *
     * @return bool
     */
    public function authorize()
    {
        return true;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    public function rules()
    {
        return [
            'name' => 'required|string',
            'desc' => 'required|string',

            // etc
        ];
    }

    /**
     * Get custom attributes for validator errors.
     *
     * @return array
     */
    public function attributes()
    {
        return [
            'name' => 'Name',
            'desc' => 'Desciption',

            // etc
        ];
    }

    public function persist()
    {
        return DB::transaction(function () {
            $user = $this->createUser();

            $task = $this->createTask($user);

            $files = $this->createFiles($user);

            return [$user, $task, $files];
        });
    }

    protected function createUser()
    {
        return User::firstOrCreate(
            ['email' => $this->email],
            ['name' => $this->name, 'password' => bcrypt($this->password)]
        );
    }

    protected function createTask($user)
    {
        return $user->tasks()->create([
            'name' => $this->name,
            'desc' => $this->task_desc
        ]);
    }

    protected function createFiles($user)
    {
        $files = collect($this->allFiles())->map(function ($file, $key) {
            return [
                'name' => $key,
                'path' => $file->store('photos')
            ];
        })->values()->toArray();

        $user->uploads()->insert($files);

        return $files;
    }
}

So you have there a validation, which is very important.

martinbean's avatar

@uniqueginun Think it’s a bad form to award the answer to someone, and then re-award it to someone else later.

uniqueginun's avatar

sorry @martinbean I thought I could set more than one best answer. I kinda used a little bit of both of your answers guys.

Please or to participate in this conversation.