Saving Data

In modern application development, it's essential to structure your data handling processes to be clean, modular, and maintainable. This article will guide you through a comprehensive approach to managing data in a service layer by focusing on five key steps: Prepare, Validate, Save, Save Related Data, and After Save. We'll demonstrate this using a CompanyService that manages Company entities and their related Accounts.

Input

Consider the data below is the expected data


{
  "name": "Tech Innovations LLC",
  "email": "info@techinnovations.com",
  "phone": "+1-555-1234",
  "address": "1234 Innovation Drive, Suite 100, Tech City",
  "logo": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/wcAAwAB/et8lAAA",
  "password": "securePassword123",
  "parent_company_id": 1,
  "accounts": [
    {
      "name": "John Doe",
      "email": "john.doe@techinnovations.com",
      "role": "Admin",
      "password": "johnPassword123"
    },
    {
      "name": "Jane Smith",
      "email": "jane.smith@techinnovations.com",
      "role": "Manager",
      "password": "janePassword123"
    }
  ],
  "company_industries": [2, 3]
}
    

Basics

We will need a Route, Controller, and an action function to initiate the process of saving the company and its related data.

-Route

First, define a route in your routes/web.php file:


Route::post('/companies', [CompanyController::class, 'store']);
-Controller

Next, create a controller with an action method that will handle the request. In this case, we'll use a CompanyController:

In this step we should handle basic validation in CompanyRequest class


<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Services\CompanyService;

class CompanyController extends Controller
{
    protected $companyService;

    public function __construct(CompanyService $companyService)
    {
        $this->companyService = $companyService;
    }

    public function store(CompanyRequest $request)
    {
        $company = $this->companyService->storeAndCommit($params);

        return response()->json($company);
    }
}

Base Service Class

We should have a base service class which contain functions that are needed in all services that save data .


<?php

namespace App\Services;
class DefaultService
{
    public $repository;

    public function storeAndCommit($params)
    {
        DB::beginTransaction();
        try {
            $result = $this->store($params);
            DB::commit();
            $result->refresh();
            return $result;
        } catch (Exception $e) {
            DB::rollBack();
            throw $e;
        }
    }

    public function updateAndCommit($params)
    {
        DB::beginTransaction();
        try {
            $result = $this->update($params);
            DB::commit();
            $result->refresh();
            return $result;
        } catch (Exception $e) {
            DB::rollBack();
            throw $e;
        }
    }

    public function store($params)
    {
        return $this->repository->store($params);
    }

    public function update($params)
    {
        return $this->repository->update($params);
    }


    public function saveMany($params, $extraParams = []): void
    {
        foreach ($params as $param) {
            $param = array_merge($param, $extraParams);
            $this->saveOne($param);
        }
    }

    public function saveOne($param)
    {
        if (isset($param['id'])) {
            return $this->update($param);
        } else {
            return $this->store($param);
        }
    }

    public function saveManyToMany($arr): void
    {
        $field1 = array_keys($arr)[0];
        $field1data = array_values($arr)[0];
        $field2 = array_keys($arr)[1];
        $field2data = array_values($arr)[1];
        $result = [];
        $this->deleteMany([$field2 => $field2data]);
        $field1data = array_unique($field1data);
        foreach ($field1data as $item) {
            $resultItem[$field1] = $item;
            $resultItem[$field2] = $field2data;
            $result[] = $resultItem;
        }
        $this->saveMany($result);
    }

    public function deleteMany($filters): void
    {
        $this->repository->deleteMany($filters);
    }
}

Base Repository Class

Also we should have default repository that contain basic save functions .


<?php

namespace App\Repositories;

use Illuminate\Support\Facades\Log;

class DefaultRepository
{
    public $modelName;
    public function store($params)
    {
        $modelName = $this->modelName;
        $modelObject = new $modelName;
        $modelObject->fill($params);
        $modelObject->save();
        return $modelObject;
    }

    public function update($params)
    {
        $modelName = $this->modelName;
        $modelObject = $modelName::findOrFail($params['id']);
        $modelObject->fill($params);
        $modelObject->save();
        return $modelObject;
    }
}

The Service class

The service class will have two main functions save() and update() that will contain the 5 main steps.


<?php

namespace App\Services;

class CompanyService extends DefaultService
{
    public function __construct(CompanyRepository $companyRepository)
    {
        $this->repository = $companyRepository;
    }
    public function store($params)
    {
        $params = $this->prepareArray($params);

        $this->validate($params);

        $company = parent::store($params);

        $this->saveRelatedData($company, $params);

        $this->afterSave($company, $params);

        return $company;
    }

    public function update($params)
    {
        $params = $this->prepareArray($params);

        $this->validate($params);

        $company = parent::update($params);

        $this->saveRelatedData($company, $params);

        $this->afterSave($company, $params);

        return $company;
    }
}

Prepare

The first step in the process is to prepare the data. This involves transforming and structuring the incoming data so that it can be correctly processed and stored.


<?php
class CompanyService extends DefaultService
{
   //...

   public function prepareArray($params)
    {
        if (isset($params['logo'])) {
            $params = $this->handleUploadLogo($params);
        }
        if (isset($params['password'])) {
            $params = $this->handlePassword( $params );
        }
        return $params;
    }
    public function handlePassword($params){
        $params['password']=Hash::make($params['password']);
        return $params;
    }
    public function handleUploadLogo($params){
        $mediaUpload=new MediaUpload();
        $mediaUpload->upload($params['logo']);
        $params['logo']=$mediaUpload->getUrl();
        return $params;
    }
}

Validate

After preparing the data, the next step is to validate it. Validation ensures that the data meets all required criteria before proceeding to the save operation.


<?php
class CompanyService extends DefaultService
{
   //...

    public function validate($params)
    {
        if (isset($params['parent_company_id']) && isset($params['id']) && $params['parent_company_id'] == $params['id']) {
            throw new \Exception('A company cannot be its own parent.');
        }
    }

}

Save

saving the data logic is handled in the parent class .



$company = parent::store($params);

Save Related Data

After saving the primary data, you may need to handle related data. This involves saving records that are associated with the main record, such as accounts related to a company. it should be something clear and simple just like writing the relations in the model


class CompanyService extends DefaultService
{
   //...
    public function saveRelatedData($company, $params)
    {

        if (isset($params['company_documents'])) {
            (new MediaUploadService())->saveMany($params['account_documents'], [
                'model_id' => $company->id,
                'model_type' => ModelTypes::company,
                'flag' => 'account_documents',
            ]);
        }

        if (isset($params['accounts'])) {
            app(AccountService::class)->saveMany($params['accounts'], [
                'company_id' => $company->id,
            ]);
        }

        if (isset($params['company_industries'])) {
            app(CompanyIndustries::class)->saveManyToMany([
                'commodity_id' => $params['company_industries'],
                'company_id' => $company->id
            ]);
        }
    }
}

After Save

Now we might need to do something after saving .


class CompanyService extends DefaultService
{
   //...
    public function afterSave($company, $params)
    {
        if (isset($params['accounts'])) {
            $company->number_of_accounts=$company->accounts->count() ;
            $company->save();
        }
    }
}

Conclusion

By structuring your data-saving process into discrete steps—Prepare, Validate, Save, Save Related Data and After Save — you create a well-organized and maintainable codebase. This approach allows each step to build upon the previous one, ensuring that your data handling is robust and scalable.

The CompanyService class in this example illustrate how to implement these steps in Laravel using a service layer while leveraging a base DefaultService class for common operations. This modular approach enhances code clarity and facilitates easier management of complex data interactions.