PHP - How can I refactor this store function (which handles different database operation for user types) in order to reduce duplication? I'm simply using Slim PHP MVC framework in my API project. PDO::FETCH_ASSOC is used in the database calls. So no data objects. The router handles requests and pass to the controller. controller calls the necessary model to do database operations and response is delivered.

This is the scenario that I want to handle different user types. Each user_type has user_type based specific db transaction to be done. I want to make this more reusable and remove code duplication.

Sorry if there's any wrong thing here, and for my English.

My Route

$this-&gt;post('/users/preferences', \UserPreferenceController::class . ':store')


UserPreferenceController => Controller

public function store($request, $response, $args){
    $parsedBody = $request-&gt;getParsedBody();
    $user_id = $parsedBody['user_id'];
    $preferences = $parsedBody['preferences'];

    $user = $this-&gt;User-&gt;getUserInfo($user_id);

    if ($user["type_id"] == 'Admin') {
        $preferenceAdded = $this-&gt;User-&gt;AddAdminUserPreferences($user_id, $preferences); // DB transaction operation
    } else if ($user["type_id"] == 'Customer') {
        $preferenceAdded = $this-&gt;User-&gt;AddCustomerUserPreferences($user_id, $preferences); // DB transaction Operation
    } else if ($user["type_id"] == 'Sales') {
        $preferenceAdded = $this-&gt;User-&gt;AddSalesUserPreferences($user_id, $preferences); // DB transaction Operation
    } 

    // return if the transaction is rollbacked
    if(!$preferenceAdded){
        return $response-&gt;withJSON([
            "error" =&gt; true, 
            "message" =&gt; "cannot add preferences for user"
            "data" =&gt; $user_id
        ]);
    }

    if ($user["type_id"] == 'Admin') {
        $staisticsAdded = $this-&gt;User-&gt;AddAdminUserPreferencesStatistics($user_id, $preferences); // DB transaction operation
    } else {
        $staisticsAdded = $this-&gt;User-&gt;AddOtherUserPreferencesStatistics($user_id, $preferences); // DB transaction operation
    }

    // return if the transaction is rollbacked
    if(!$staisticsAdded ){
        return $response-&gt;withJSON([
            "error" =&gt; true, 
            "message" =&gt; "cannot add statistics for user"
            "data" =&gt; $user_id
        ]);
    }

    return $response-&gt;withJSON([
        "error" =&gt; false, 
        "message" =&gt; "operation completed successfully"
        "data" =&gt; null
    ]);
}


User.php => User model

public function getUserInfo($user_id)
{
    $sql = "SELECT id, name, email, type_id FROM users WHERE id= :id";
    $stmt = $this-&gt;db-&gt;prepare($sql);
    $result = $stmt-&gt;execute(['id' =&gt; $user_id]);
    return $stmt-&gt;fetch()[0];
}

public function AddAdminUserPreferences($user_id, $preferences)
{
    $this-&gt;db-&gt;beginTransaction();

    try {
        // $sql1 execute (common function for every user_type)
        // rollback if $sql1 fails

        // $sql2 execute (a user_type specific function)
        // rollback if $sql2 fails

        $this-&gt;db-&gt;commit();
        return true;
    } catch(\PDOException $e){
        $this-&gt;db-&gt;rollBack();
        return false;
    }
}


My Container

$container = $app-&gt;getContainer();

$container['UserPreferenceController'] = function ($c) {
    return new App\Controllers\UserPreferenceController($c);
};

$container['User'] = function ($c) {
    return new App\Models\User($c);
};
I think it's a very good case to employ OOP, namely polymorphism and inheritance.

First, we've got to create a dedicated class to handle the user preferences.

A common ancestor to hold all the common code

abstract class UserPreferences
{
    protected $userId;
    protected $db;

    public function __construct($userId, $db) {
        $this-&gt;userId = $userId;
        $this-&gt;db = $db;
    }
    protected function addGeneric($preferences) {
        $sql = "INSERT INTO preferences (...) VALUES (?,?,?)";
        $this-&gt;db-&gt;prepare($sql)-&gt;execute($preferences);
    } 

    abstract protected function addSpecific($preferences);

    public function add($preferences)
    {
        $this-&gt;db-&gt;beginTransaction();

        try {
            $this-&gt;addGeneric($preferences);
            $this-&gt;addSpecific($preferences);
            $this-&gt;db-&gt;commit();
        } catch(Throwable $e){
            $this-&gt;db-&gt;rollBack();
            throw $e;
        }
    }
}


and then implementations for different types

class AdminPreferences extends UserPreferences 
{
    protected function addSpecific($preferences) {
        $sql = "INSERT INTO admin_preferences (...) VALUES (?,?,?)";
        $this-&gt;db-&gt;prepare($sql)-&gt;execute($preferences);
    }
} 

class SalesPreferences extends UserPreferences 
{
    protected function addSpecific($preferences) {
        $sql = "INSERT INTO sales_preferences (...) VALUES (?,?,?)";
        $this-&gt;db-&gt;prepare($sql)-&gt;execute($preferences);
    }
} 


...and so on.

Then in the User class create an instance of the Preferences class

class User 
{
    public $preferences;
    public function __construct($db, $type) {
        $this-&gt;userId = $userId;
        $this-&gt;db = $db;
        $this-&gt;preferences = $this-&gt;setPreferences($db, $type);
    }
    protected function setPreferences($db, $type) {
        switch($type) {
            case 'Admin':
                $this-&gt;preferences = new AdminPreferences($db);
                break;
            case 'Sales':
                $this-&gt;preferences = new SalesPreferences($db);
                break;
        }
    }
}


the same goes for the statistics

and finally in your controller simply 

$preferences = $parsedBody['preferences'];
$this-&gt;User-&gt;preferences-&gt;add($preferences);
$this-&gt;User-&gt;preferencesStatistics-&gt;add($preferences);

return $response-&gt;withJSON([
    "error" =&gt; false, 
    "message" =&gt; "operation completed successfully"
    "data" =&gt; null
]);


Note that checking $preferenceAdded manually is not the way to go. There should be a dedicated error handler to do the job.