JejjE's avatar

Bloated controller - where to extract my logic to?

Hello everyone!

I've been working on and off with Laravel, and I'm working on a project right now where I've reached a state of head scraching. My code is working but I know the code is out of place and I'm unsure of what to actually google to figure it out.

I have a Controller that is resposible for uploads of images, wether it's file upload or downloading image from URL. It's also responsible to take a video URL of a youtube video if it's set.

To clean up my controller I made regular functions at the end of the file which I call - I know this is the wrong approach and that's why I need your help.

For example if I'd wish to add video from Facebook, Instagram, Vine and so on - the controller would be so bloated a coroner wouldn't be able to identify the corpse.

Could you point me into some reading material and helping me figure out where to put all this logic?

0 likes
9 replies
HRcc's avatar

That depends on what you prefer. As a simple solution you can use a service layer with suitable naming convention (Image/Picture/Photo/...) like:

$image = $this->imageService->upload($request);

which you can then reuse for downloading as well you like it that way:

$image = $this->imageService->download($videoId);

Or you can split those two into separate classes (ImageUploadService, ImageDownloadService).

Anyway, you can do your magic inside that class which will keep your controller slim. Feel free to fire various events and listen for them where necessary (e.g. ImageWasUploaded => email notifier , ImageWasConverted => user can download new file, ...).

PS: please remember, that I am talking about service classes ( = classes with specialized functionality based on your needs) and not about service providers ( = classes you mostly use to bind or prepare various stuff)

1 like
JejjE's avatar

This way might be the way I need to go, so to see if I understand you correctly I would want to put my class into app/Services, where I'd do something like:

// File: Acme/app/Services/Media.php
namespace App/Services;

class Media {
    public function create($request)
    {
        // All checks are successful!
        $myChecks = true;


        if($myChecks == true) {
            // Do the stuff

            if($request->media == 'img')
            {
                // Image Processing
               $media = process_image($request);
            } elseif ($request->media == 'video') {
                $media = process_video($request);
            } else {
                $media = 'error';
            }

            return $media;
        }
    }

    public function process_image($request) {
        // Media proccessing
    }

    public function process_video($request) {
        // Media proccessing
        if ($request['video_type'] == 'youtube'){
            // youtube stuff, and repeat
        }
    }
}
}

This is a crude example and wouldn't work in an real application, but it's mostly the theory of it that I'm curios about - to make it simpler to add vendors like Facebook video's and so on.

And I would then be able to call it from my Media Controller like...

$media = $this->media->create($request);

What is the context of the $this variable? I'm sorry for my ignorance but I'm not used to calling it like that, does $this refer to the App's folder?

You wouldn't happen to have some links I could dig in to, I'd love me some reading material! When I try to google all I find is IoC and Service Providers.

1 like
Kryptonit3's avatar

It means you would include the service class at the top with use App\Services\Media; then your constructor would look like this

public function __construct(Media $media)
{
    $this->media = $media;
}

Then when you call $this->media you have access to all the methods of that class. It's called dependency injection.

2 likes
JejjE's avatar

I'm ashamed to say I haven't made much use of the constructors in Laravel - but most of my applications has been simple CRUD ones that never see production, mostly for personal use. I'm still learning, I'm always learning - "Never stop learning", hehe. That's the beauty of programming in my opinion.

I'll look into this first thing in the morning, and clean up my super bloated controller - and keep on with my project. I haven't been able to let this go and my programming has come to a complete stop - and I need to put on my runner shoes and get this project to the finish line!

Any reading material och free courses on Laracast would be much appriciated, just as all the help you've supplied. :)

3 likes
jekinney's avatar

There are many ways some as stated. I personally try to code in a way that if I need to update something like add a input to a form I only need to open one file, views excluded. Sometimes it's not readable, but effort is there.

If I need to add functionality I utilize traits, for example most features utilize a users table, so I code my feature separately and only need to open the user model and add the trait. Not only then I can reuse the feature on any project, I believe it keeps things linear and clean.

As far as controllers, as mentioned I try to keep them to one line of code when feasible. Sometimes it makes sense to perform the requirements in the controller though. No sense using another class to log out a user, or a simple log in especially with form requests and other features.

1 like
jekinney's avatar

Feasible not readable.. Can't edit on mobile.

1 like
JejjE's avatar
JejjE
OP
Best Answer
Level 1

Thanks to all of your help I managed to solve it using Services, and I'm going to share a basic concept of how I managed to get it to work to move my logic from the controller. At first I encountered an simple error due to a naming mistake, since my model was named Media so all I had to do was to rename my service to MediaService.

// Acme/app/Services/MediaService.php
<?php
namespace App\Services;

class MediaService {

   public function create($request) {
        $request = $request . " create";
        return $request;
    }

    public function youtube($request) {
        $request = $request . " youtube";
        return $request;
    }

And to make it work in my MediaController which was bloated to damnation, I made it look something like this:

// Acme/app/Http/Controllers/MediaController.php
<?php
namespace App\Http\Controllers;

// All other stuff that is needed
use AllMyStuff;
// My newly made Media Service
use App\Services\MediaService;

class MediaController extends Controller {

    public function index()
    {
        //
        $request = "my request for";

        $mediaService = $this->mediaService->youtube($request);
        dd($mediaService);
    }

}

Which returned my request for youtube as expected! Now I'll have to move all my logic to clean everything up, many thanks for all your help - much appriciated!

2 likes
thepsion5's avatar

You should probably specify the request argument as an array instead of just anything, and use $request->all(). Your services shouldn't depend (explicitly or otherwise) on an HTTP request.

For example, this is one of my controller methods:

    public function update($profileId, StoreProfileRequest $request)
    {
        $this->profiles->updateProfileById($profileId, $request->except(['_token', '_method']) );
        $this->notifySuccess('Profile updated successfully!');
        return redirect()->route('profiles.show', $profileId);
    }

Please or to participate in this conversation.