Multiple apps, shared common lib, reduce code duplication


#1

Hi,

I have an api with two logical parts, myapi.com/managers and myapi.com/staff. Much of the model and middleware code is the same, particularly the small forgettable stuff like string validation functions, etc. I want an app for both managers and staff, but also want to avoid duplicating code where possible.

In my mind I want to do something like:

manager\src\app\
manager\src\app\Controllers\
manager\src\app\Datastore\
manager\src\app\Common\  (git submodule, contains the CommonDatastore class)

// file: manager\src\app\Datastore\
class Datastore extends CommonDatastore {
}

staff\src\app\
staff\src\app\Controllers\
staff\src\app\Datastore\
staff\src\app\Common\  (git submodule, contains the CommonDatastore class)

// file: staff\src\app\Datastore\
class Datastore extends CommonDatastore {
}

I have different settings files for each app, eg different log locations, database credentials, etc.

Is this, in general, the right way to go, or at least not a bad way to go with Slim? I’m using submodules in git because as I develop I’ll need to change/add code that’s common to both apps.

thanks,


#2

I think this a not a very general way of doing this. I would “simplify” your setup a little bit. Here just are some ideas:

  • Create just one project (git repository)
  • Create a “flat” and more “standard” directory structure.
  • Use only src/ for the “App” namespace
  • Share the same controllers / actions for both parts of the application
  • Optional: Split the controllers into subfolders e.g. src/Controllers/Staff/* and src/Controllers/Manager/*
  • Create Slim routing groups for /staff and /manager.

I think it’s more important to organize the “business logic” in a good way according to your use cases. For example: src/Service/Staff and src/Service/Manager and so on.


#3

Hi,

yes, that’s pretty much what I have now, and find it’s quickly becoming complicated. In lots of places I must check if the request is a manager or a staff, and branch accordingly. I also end up prefixing functions with manager or staff, or adding in function parameters to indicate that. So that means classes/functions get longer.

The business logic is split between manager and staff, it’s just they share quite a lot of underlying helper functions. The middleware authentication, for example, is broadly the same but uses a slightly different sql query.


#4

In lots of places I must check if the request is a manager or a staff,

Why don’t use Routes to delegate each request to the right service (business logic) class?
Example: request -> controller / action -> service

I also end up prefixing functions with manager or staff

Just use classes and namespaces. Please don’t use functions for your business logic :stuck_out_tongue_winking_eye:

So that means classes/functions get longer.

No, if you organize your namespaces and classes correctly (SRP) then this should scale very good.

The middleware authentication, for example, is broadly the same but uses a slightly different sql query.

In Slim you can also attach middleware to any route or route group.

Example

$app->group('/manager', function ()  {
    // ....
})->add(new ManagerAuthMiddleware());

$app->group('/staff', function ()  {
    // ....
})->add(new StaffAuthMiddleware());

#5

Hi,

I think I’m not explaining it well. Everything you’ve suggested, I am already doing. I don’t use functions for business logic, but if I have:

private function getValueFromHeader($request, $headerLabel, $headerValueLength)
{
    if ($request->hasHeader($headerLabel)) {

        // get string from header
        $headerValues = $request->getHeader($headerLabel);
        $value = array_shift($headerValues);

        // match on first non valid char, then ! the if statement
        $match_pattern = "/^[A-Za-z0-9]{" . $headerValueLength . "}$/u";
        if (preg_match($match_pattern, $value))
        {
          return $value;
        }
    }
    return false;
}

I don’t want to maintain/duplicate that function in multiple classes, so I don’t want a ‘manager data class’ and a ‘staff data class’. I would like to implemented it in a single employee data class, and then subclass to get my manager and staff classes where I can override some functions. Currently, I’m doing it exactly as you’ve suggested, I have to duplicate this and similar functions because there’s no code sharing between manager and staff classes.

If this were straight PHP then I’d just follow normal inheritance (employee->manager, employee->staff, employee->staff->temp). I think what I’m asking is, how is this best done with Slim ?

My goal is to avoid maintaining duplicate code.


#6

OK, after some playing around I think I’ve got options. I might use a combination of class inheritance and traits, or one or the other. This, for me, removes most or all duplication of code that I’m running into. I won’t bother with git submodules, but I will use separate index and settings files, allowing easier de-coupling of the two api endpoint groups (possibly even manager.myapi.com, staff.myapi.com).

src\app\manager\controllers
src\app\manager\datastore
src\app\manager\middleware
src\app\staff\controllers
src\app\staff\datastore
src\app\staff\middleware
src\app\common\controllers
src\app\common\datastore
src\app\common\middleware
src\app\common\traits
src\public\manager_index.php
src\public\staff_index.php
src\manager_settings.php
src\staff_settings.php

so, not real code, but it’s the gist of it. For anyone who might be trying a similar thing.

namespace Manager\Controllers;

class Manager extends \Common\Controllers\Employee {

    use \Common\Traits\stringValidation;  // trait

    public function __invoke($request, $response, array $args) {

        $id = $request->getAttribute('identity');

        if ($this->isValidEmployeeIdentifierString($id)) {  // use method from trait

            $msg = $this->getGeneralEmployeeMessage($id); // use method from parent class
            $msg .= " you are a manager";
            return $response->withStatus(200)
                            ->getBody()->write($msg);
        }

        return $response->withStatus(404);
    }
}

namespace Staff\Controllers;

class Staff extends \Common\Controllers\Employee {

    use \Common\Traits\stringValidation;

    public function __invoke($request, $response, array $args) {

        $id = $request->getAttribute('identity');

        if ($this->isValidEmployeeIdentifierString($id)) {  // use method from trait

            $msg = $this->getGeneralEmployeeMessage($id); // use method from parent class
            return $response->withStatus(200)
                            ->getBody()->write($msg);
        }

        return $response->withStatus(404);
    }
}