Search code examples
phpmodel-view-controllerservice-layer

Designing service layer classes in PHP


I was recently introduced to service layers by Jani Hartikainen in a discussion about how to best handle form data in a MVC app. After doing some reading I can really see the benefits of this approach. My question is this:

How should services classes be structured?

  • First, is user_service() an appropriate class name for my user() model or is there another standard?
  • Since the methods in my service will only be doing one task, is it correct to think that these can always be a static function? A service class isn't representing data, but rather is a series a actions, so this seems appropriate.
  • Should a service method only accept one argument, which would be an array?

Consider a form has posted data to a controller to save user data:

<?php

    class form_controller extends controller
    {

        public function process_submit()
        {
            if(user_service::update_preferences($_POST))
            {

                echo json_encode(array('success' => true));
            }
            else
            {
                echo json_encode(array('success' => false));
            }
        }

    }

    class user_service
    {

        // Accepts array()
        public static function update_preferences($fields)
        {

            // Check for required fields
            if((
                isset($fields['firstname']) and
                isset($fields['lastname']) and
                isset($fields['email'])
                ) == false
            {
                return false;
            }

            // Update user
            try
            {
                $s = new user();
                $s->set_firstname($fields['firstname']);
                $s->set_lastname($fields['lastname']);
                $s->set_email($fields['email']);
                $s->update();

                return true;
            }
            catch(Exception $e)
            {
                return false;
            }
        }
    }

I feel this is a good approach because:

  • I can add another field to my form and I won't have to update the controller, just the service. It seems right that the controller shouldn't be concerned about what data is being passed, just that it is passed. This keeps my controller small, and the logic in my models.
  • If I didn't pass an array, I could setup functions with multiple arguments. For example my function could be update_preferences($firstname, $lastname, $email). This however could make for functions with upwards of 20 arguments (for large forms), and the order would just become terrible to work with.
  • I could pass an object, but does that make sense? If I'm creating an object, it should the be the object it represents (the user in this case) right? But does it make sense that the controller instantiates the user object? Isn't that the whole point of the service layer in the first place?
  • Maybe there is an argument for having some methods with multiple arguments (when there are just one to three) and some methods that accept an array (when there are lots of fields). This just seems like it could be a nightmare, as you would always have to reference the class to know what that particular method was asking for.

Does anyone have an opinion on what the right thing to do here is? Am I on the right track? What have you done in the past? Thanks a lot!


Solution

  • First, is user_service() an appropriate class name for my user() model or is there another standard?

    That's acceptable. However, you should rather use one of the established PHP coding conventions, such as the PEAR or ZF conventions. In both cases, class names are UpperCamelCase and method names lowerCamelCase. Using this, the classes would be User and UserService

    Since the methods in my service will only be doing one task, is it correct to think that these can always be a static function? A service class isn't representing data, but rather is a series a actions, so this seems appropriate.

    No. It's a poor design choice to make methods static - and this applies to most code, not just services. One of the main reasons in case of a service would be that generally your service needs to interact with a data store or another class which represents the data layer (repository, data access object, whatever).

    When your service has static methods, this means you would need to instanciate your dependencies in your methods. This in turn means that, amongst other things, the code becomes hard to test, as you can't easily replace the dependencies.

    There's some good reading on this for example here (In fact nearly everything on that blog is good reading for software devs)

    Should a service method only accept one argument, which would be an array?

    This is dependent on what the method does. Assuming your example of processing a form's resultset, then yes this will probably work. In some other case it might be a poor choice.

    I can add another field to my form and I won't have to update the controller, just the service. [ ... ]

    If I didn't pass an array, I could setup functions with multiple arguments. [ ... ]

    Yep, your argumentation for these two cases is pretty much spot on for this use-case in my opinion.

    I could pass an object, but does that make sense? If I'm creating an object, it should the be the object it represents (the user in this case) right? But does it make sense that the controller instantiates the user object? Isn't that the whole point of the service layer in the first place?

    This depends. For example, if you were using a framework which lets you represent forms as objects (such as the Zend Framework and Zend_Form), you could consider passing the form object straight to the service.

    Maybe there is an argument for having some methods with multiple arguments (when there are just one to three) and some methods that accept an array (when there are lots of fields). This just seems like it could be a nightmare, as you would always have to reference the class to know what that particular method was asking for.

    You should usually aim to make the parameters at least half-guessable based on the method's name. In something I work on, we have a model which has for example businesses and products, where a business can sponsor a product. In a ProductService, we have a method called sponsorProduct which takes a business and a product as parameters. You can pretty much guess it would take these two (if you were familiar with the codebase anyway)

    IDE's generally help you with this too - they provide code-assist which displays what params functions take. This is one of the main reasons I think IDE's are very useful in larger projects where you can't always remember what exactly a certain function needs as parameters.

    As for parameter count, I think usually you should try to have separate parameters. This allows anyone to easily see what parameters are required by just looking at the function's signature, and allows you to define typehints and default values quite easily.

    However there is a point when you get so many parameters it's too much. This is maybe at +5 or so, depending a bit on what sort of method it is. In this case you can consider using an array, or something called a Parameter Object, which is essentially an object that contains all the parameters for the call. More on parameter objects here