MVC服务层内的限制

I have recently dived into OOP & PHP MVC Application Design. At the moment I am learning a lot but I have one thing that is currently bugging me.

I read and now understand why it isn't wise to place http redirects within a service layer. We do not know what the controller will need to do once the service is complete, etc. etc. I also read that the service should not do anything outside of its purpose. Example: User Registration should only create a new user, using input passed by controller, but I am wondering if it is also fine to set flash messages within the service layer.

My application displays a lot of flash messages session based notifications for users. All of them are based on service related input validation checks, and produce alerts similar to the following

The username xxxxxx is already in use

Usernames Should be > 5 Characters

Should/can this be defined/set within the service class or is there something wrong with that? I have a Alert Helper function that handles setting the alerts. I can easily use my dependency injector to make it available I am just wondering if there is an issue with doing that.

I made the mistake of implementing all redirects within the services and I just finished removing all of them and placing them in the controllers, I don't want to make the same time consuming mistake so I am looking for advice here.

Thank you in advance for the help.

EDIT - CODE EXAMPLE

<?php

/**
 *-----------------------------------------------------------------
 * 
 *  LOGIN CONTROLLER
 * 
 */
namespace Controller\www;
use \Helper\Controller;

class Login extends Controller {
    public $dependencies = ['arena', 'login', 'site'];

    /**
     *  Login
     * 
     *  Login Runs Through Various Checks Including If User is Banned, Account is Locked, 
     *  or Forgot Password Request Is Active. Then the Entered Password is Matched & if Valid
     *  User is Logged In
     */
    public function index() {

        // Define Default
        $username = '';                                                                     


        /** 
         *  User Login
         * 
         *  If      Successful, Login User, Redirect Home
         *  Else    Set Error Alerts 
         */
        if ($this->form->post('login')) { 

            // Define and Sanitize Post Data
            $username = $this->input->get('username');
            $password = $this->input->get('password');


            // Login Service Layer
            $login = $this->factory->make('user/login');

            // If Successful Redirect Home - Else Set Errors
            if ($login->user($username, $password) === true) {
                $this->redirect->home();
            }
            $this->alert->error($login->get('errors'));
        }


        /**
         *  Define Site Title & Display Page
         */
        $this->view->sitetitle('login');
        $this->view->display('www/login', [
            'video'     => $this->arena->video(),
            'username'  => $this->input->set($username)
        ], ['notifications' => 'user/forgotpassword']);
    }
}

Service Layer

/**
 *-----------------------------------------------------------------
 * 
 *  USER LOGIN SERVICE LAYER
 * 
 */
namespace Service\User;
use \Helper\Service;

class Login extends Service {
    public $dependencies = ['login', 'mail', 'time', 'user', 'vars'];

    /**
     *  Handles Entire Login Process For Site Users
     *  
     *  @params all         User Submitted Form Data
     */
    public function user($username = '', $password = '') {

        // Validate $_POST Form Data
        $this->validateInput($username, $password);


        /**
         *  No Errors Produced - Complete Form Submission
         * 
         *  We Are Not Using `elseif` Between Forgot Password & Normal Login
         *  After a Forgot Password Code is Generated User May Remember Old Passwords
         *  We Need to Ensure Users Can Still Login Using Account Password As Well
         */
        if (!$this->errors()) {


            /** 
             *  User Input Password Matches Account Password
             */
            if ($this->input->verifyhash($password, $this->user->get('info.password'))) {
                $this->login->user();
                return true;                                                                    
            }


            /** 
             *  If We Have Not Been Redirected Login Was Unsuccessful
             */
            $message = $forgotPW ? 'Forgot Password Code Invalid - Login Lost Incorrect' : 'Login Unsuccessful - Incorrect Username or Password';
            $this->log->error($message, ['Username' => $username, 'Password' => $password]);

            $this->error('Incorrect Username or Password');
        }

        /** 
         *  If We Have Made It This Far Login Was Unsuccessful - Log Unsuccessful Attempt
         */
        $this->login->logAttempt();


        return false;
    }


    /**
     *  Validate $_POST Data
     * 
     *  @params all         User Submitted Form Data
     */
    private function validateInput($username = '', $password = '') {

        // Display Error if Username is Empty 
        if (!$username) {                                                                                           
            $this->error('Please enter a username');                                        
        }
        // Display Error if Password is Empty
        elseif (!$password) {                                                                                       
            $this->error('Please enter a password');                                        
        } 
        // Search DB For User With Matching Username - If User Not Found Display/Log Error, Else Set User
        else {
            $user = $this->user->info($username, 'username', '', '`userid`');
            if (!$user) {
                $this->error('The username ' . $username . ' does not exist'); 
                $this->log->error('User Not Found When Attempting to Login', ['username' => $username]);   
            } else {
                $this->user->set('user', $user['userid']);
            } 
        }
    }
}

In order to answer your question, I think it's best to break down the concept of MVC into a very basic form, and its individual parts. I apologise in advance if this comes across as being somewhat condescending.

View
The view of the application displays anything and everything. If something is going to be displayed, it should be done in this layer

Controller
The controller is a mediator between the view and the model. It takes input from the view, applies logic/rules to it (where required), and interacts with the model to then get data to pass back to the view.

Model
This is where the loading and saving of data are done. The majority of the validation should have been done as part of the rules in the controller, and this should only pass details of any errors during loading or saving back to the controller should the arise. If there are no errors, it should return the relevant data, or a success status back to the controller.

With those points in mind, the model should not set flash messages to the session, that should be done within the controller depending on the result from the model.

Look at redirects and alerts as specific to one particular form of UI, and it should be obvious that there's no place for them in the Model. Simply always try to picture an alternative interface for your application; e.g. a command line interface for administrative tasks or a REST API. Redirects obviously have no place in either of these alternatives. Alerts are debatable... at the very least the form of the alert will be very different. Your Model will need to be able to pass back some status code to your Controller or View, and then it's the job of the Controller to react to "negative" events and the job of the View to visualise any alerts if necessary.

For example, your model may do something like this:

public function registerUser(User $user) {
    ...

    if (!$successful) {
        throw new EmailAlreadyRegisteredException;
    }
    return true;
}

The controller may then look like this:

public function userRegistration(Request $request) {
    try {
        $user = User::fromRequest($request);
        $this->services->get('Users')->registerUser($user);
        $this->view->render('registration_successful', $user);
    } catch (InvalidUserData $e) {
        $this->view->render('registration_form', $request, $e);
    } catch (EmailAlreadyRegisteredException $e) {
        $this->view->render('registration_failed', $user, $e);
    }
}

The "alert" is passed around as an exception. It's just a method for the Model to signal to its callers what happened. It's up to the callers then to react to and visualise those events. You should certainly not expect any particular type of visualisation in the Model. So you don't want to hardcode specific HTML encoded messages or such. You don't even want to touch human languages at all, that's all the job of the View.