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
.
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]
}
We will need a Route, Controller, and an action function to initiate the process of saving the company and its related data.
First, define a route in your routes/web.php
file:
Route::post('/companies', [CompanyController::class, 'store']);
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);
}
}
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);
}
}
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 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;
}
}
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;
}
}
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.');
}
}
}
saving the data logic is handled in the parent class .
$company = parent::store($params);
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
]);
}
}
}
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();
}
}
}
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.