6 min readMar 9, 2025

--

Part 2: Fine-Tuning Application Settings & Tooling for a Modular Laravel API

In this second installment, we dive into the behind-the-scenes configuration that powers our modular API. We’ll discuss our approach to bootstrapping settings — from middleware to scheduled commands — and explore several indispensable development tools. This post covers:

  • Tooling Packages: Their roles, installation, and configuration.
  • App Service Provider: A detailed walkthrough of its methods, benefits, and design decisions.
  • Bootstrap Configuration: How we organize middleware, exceptions, and schedules in a modular manner.

Tooling Packages: Enhancing Quality, Consistency, and Developer Experience

To ensure a robust and maintainable codebase, we integrated a collection of packages that cover refactoring, static analysis, code formatting, API documentation, and more. Here’s a breakdown of each package, its importance, and how to set it up:

1. PHP Rector

Role & Importance:

Rector automates code refactoring and migrations, helping you modernize your codebase. It’s especially useful when moving to PHP 8.3 features, as it converts older constructs to new syntax.

Installation & Setup:

composer require --dev rector/rector

Create a rector.php configuration file at the root of your project to define your rules and target version. For example:

<?php

declare(strict_types=1);

use Rector\Config\RectorConfig;
use Rector\Set\ValueObject\SetList;
use Rector\Set\ValueObject\LevelSetList;
// Code Quality rules
use Rector\CodeQuality\Rector\Assign\CombinedAssignRector;
use Rector\CodeQuality\Rector\BooleanAnd\SimplifyEmptyArrayCheckRector;
use Rector\CodeQuality\Rector\Class_\CompleteDynamicPropertiesRector;
use Rector\CodeQuality\Rector\ClassMethod\InlineArrayReturnAssignRector;
use Rector\CodeQuality\Rector\Equal\UseIdenticalOverEqualWithSameTypeRector;
use Rector\CodeQuality\Rector\Expression\InlineIfToExplicitIfRector;
use Rector\CodeQuality\Rector\Foreach_\SimplifyForeachToCoalescingRector;
use Rector\CodeQuality\Rector\FuncCall\SimplifyStrposLowerRector;
use Rector\CodeQuality\Rector\FunctionLike\SimplifyUselessVariableRector;
use Rector\CodeQuality\Rector\If_\CombineIfRector;
use Rector\CodeQuality\Rector\If_\ExplicitBoolCompareRector;
use Rector\CodeQuality\Rector\If_\SimplifyIfElseToTernaryRector;
use Rector\CodeQuality\Rector\If_\SimplifyIfReturnBoolRector;
// Dead Code rules
use Rector\DeadCode\Rector\Array_\RemoveDuplicatedArrayKeyRector;
use Rector\DeadCode\Rector\Assign\RemoveDoubleAssignRector;
use Rector\DeadCode\Rector\Assign\RemoveUnusedVariableAssignRector;
use Rector\DeadCode\Rector\ClassMethod\RemoveEmptyClassMethodRector;
use Rector\DeadCode\Rector\ClassMethod\RemoveUnusedConstructorParamRector;
use Rector\DeadCode\Rector\ClassMethod\RemoveUnusedPrivateMethodRector;
use Rector\DeadCode\Rector\Expression\RemoveDeadStmtRector;
use Rector\DeadCode\Rector\For_\RemoveDeadLoopRector;
use Rector\DeadCode\Rector\FunctionLike\RemoveDeadReturnRector;
use Rector\DeadCode\Rector\Property\RemoveUnusedPrivatePropertyRector;
use Rector\DeadCode\Rector\PropertyProperty\RemoveNullPropertyInitializationRector;
// Early Return rules
use Rector\EarlyReturn\Rector\Foreach_\ChangeNestedForeachIfsToEarlyContinueRector;
use Rector\EarlyReturn\Rector\If_\ChangeIfElseValueAssignToEarlyReturnRector;
use Rector\EarlyReturn\Rector\If_\RemoveAlwaysElseRector;
use Rector\EarlyReturn\Rector\Return_\PreparedValueToEarlyReturnRector;
// Naming rules
use Rector\Naming\Rector\Assign\RenameVariableToMatchMethodCallReturnTypeRector;
use Rector\Naming\Rector\ClassMethod\RenameParamToMatchTypeRector;
use Rector\Naming\Rector\ClassMethod\RenameVariableToMatchNewTypeRector;
use Rector\Naming\Rector\Foreach_\RenameForeachValueVariableToMatchMethodCallReturnTypeRector;
// PHP version and modern features
use Rector\Php73\Rector\FuncCall\JsonThrowOnErrorRector;
use Rector\Php74\Rector\Closure\ClosureToArrowFunctionRector;
// Privatization rules
use Rector\Privatization\Rector\ClassMethod\PrivatizeFinalClassMethodRector;
use Rector\Privatization\Rector\MethodCall\PrivatizeLocalGetterToPropertyRector;
use Rector\Privatization\Rector\Property\PrivatizeFinalClassPropertyRector;
// Type Declaration rules
use Rector\TypeDeclaration\Rector\ClassMethod\AddParamTypeDeclarationRector;
use Rector\TypeDeclaration\Rector\ClassMethod\AddReturnTypeDeclarationRector;
use Rector\TypeDeclaration\Rector\ClassMethod\ReturnTypeFromReturnNewRector;
use Rector\TypeDeclaration\Rector\ClassMethod\ReturnTypeFromStrictTypedCallRector;
use Rector\TypeDeclaration\Rector\ClassMethod\ReturnTypeFromStrictTypedPropertyRector;
use Rector\TypeDeclaration\Rector\Property\TypedPropertyFromStrictConstructorRector;

return static function (RectorConfig $rectorConfig): void {
// Specify paths to analyze: include both legacy paths.
$rectorConfig->paths([
__DIR__ . '/app',
__DIR__ . '/config',
__DIR__ . '/database',
__DIR__ . '/resources',
__DIR__ . '/routes',
__DIR__ . '/Modules',
]);

// Specify paths and patterns to skip.
$rectorConfig->skip([ __DIR__ . '/bootstrap/*.php', __DIR__ . '/config/*.php', __DIR__ . '/app/Providers/*.php', __DIR__ . '/database/migrations', __DIR__ . '/database/migrations/*.php', __DIR__ . '/Modules/**/database/migrations', __DIR__ . '/app/Http/Requests/Api/BaseApiFormRequest.php', __DIR__ . '/app/Http/Controllers/Api/BaseApiController.php', // Skip a specific rule for certain paths. RemoveUnusedPrivateMethodRector::class => [ __DIR__ . '/app/Jobs/*.php', __DIR__ . '/app/Listeners/*.php', __DIR__ . '/Modules/**/Jobs/*.php', __DIR__ . '/Modules/**/Listeners/*.php', ],
]);

// Set the target PHP version (82000 corresponds to PHP 8.2.0).
$rectorConfig->phpVersion(82000);

// Register sets from both configurations.
$rectorConfig->sets([ SetList::CODE_QUALITY, SetList::DEAD_CODE, SetList::PHP_82, SetList::TYPE_DECLARATION, SetList::CODING_STYLE, SetList::NAMING, SetList::EARLY_RETURN, LevelSetList::UP_TO_PHP_84, ]);

// Register individual rules.
$rectorConfig->rules([ // Type Declaration rules AddParamTypeDeclarationRector::class, AddReturnTypeDeclarationRector::class, ReturnTypeFromReturnNewRector::class, ReturnTypeFromStrictTypedCallRector::class, ReturnTypeFromStrictTypedPropertyRector::class, TypedPropertyFromStrictConstructorRector::class, // Code Quality rules CombinedAssignRector::class, SimplifyEmptyArrayCheckRector::class, CompleteDynamicPropertiesRector::class, InlineArrayReturnAssignRector::class, UseIdenticalOverEqualWithSameTypeRector::class, InlineIfToExplicitIfRector::class, SimplifyForeachToCoalescingRector::class, SimplifyStrposLowerRector::class, SimplifyUselessVariableRector::class, CombineIfRector::class, SimplifyIfElseToTernaryRector::class, SimplifyIfReturnBoolRector::class, ExplicitBoolCompareRector::class, // Dead Code rules RemoveDuplicatedArrayKeyRector::class, RemoveDoubleAssignRector::class, RemoveUnusedVariableAssignRector::class, RemoveEmptyClassMethodRector::class, // RemoveUnusedConstructorParamRector::class, RemoveUnusedPrivateMethodRector::class, RemoveDeadStmtRector::class, RemoveDeadLoopRector::class, RemoveDeadReturnRector::class, RemoveUnusedPrivatePropertyRector::class, RemoveNullPropertyInitializationRector::class, // Early Return rules ChangeNestedForeachIfsToEarlyContinueRector::class, ChangeIfElseValueAssignToEarlyReturnRector::class, RemoveAlwaysElseRector::class, PreparedValueToEarlyReturnRector::class, // Naming rules RenameVariableToMatchMethodCallReturnTypeRector::class, RenameParamToMatchTypeRector::class, RenameVariableToMatchNewTypeRector::class, RenameForeachValueVariableToMatchMethodCallReturnTypeRector::class, // Privatization rules PrivatizeFinalClassMethodRector::class, PrivatizeLocalGetterToPropertyRector::class, PrivatizeFinalClassPropertyRector::class, // Modern PHP Features ClosureToArrowFunctionRector::class, JsonThrowOnErrorRector::class, ]);

// Additional Rector parameters.
$rectorConfig->parallel();
$rectorConfig->importNames();
$rectorConfig->importShortClasses();

// Ensure vendor autoloading.
$rectorConfig->autoloadPaths([ __DIR__ . '/vendor/autoload.php', ]);
};

2. Laravel IDE Helper

Role & Importance:

This package improves IDE auto-completion for Laravel projects by generating helper files. It ensures that classes, facades, and methods are correctly documented, enhancing developer productivity.

Installation & Setup:

composer require --dev barryvdh/laravel-ide-helper

After installation, publish the configuration and generate helper files:

php artisan vendor:publish --provider="Barryvdh\LaravelIdeHelper\IdeHelperServiceProvider" --tag=config
php artisan ide-helper:generate

You may also generate model PHPDocs with:

php artisan ide-helper:models

but we must update the config /config/ide-helper.php

 /*
|--------------------------------------------------------------------------
| Model locations to include
|--------------------------------------------------------------------------
|
| Define in which directories the ide-helper:models command should look
| for models.
|
| glob patterns are supported to easier reach models in sub-directories,
| e.g. `app/Services/* /Models` (without the space).
|
*/

'model_locations' => [
'app',
'Modules/*/Domain/Models',
],

3. Laravel Pint

Role & Importance:

Laravel Pint is an opinionated code style fixer. It enforces coding standards and ensures your code remains consistent across the team.

Installation & Setup:

composer require laravel/pint --dev

Create a pint.json file at the project root to customize rules if needed, then run:

{
"preset": "laravel",
"paths": [
"app",
"routes",
"tests",
"Modules"
],
"exclude": [
"database/migrations",
"Modules/**/database/migrations"
],
"rules": {
"@PSR12": true,
"align_multiline_comment": true,
"array_indentation": true,
"array_syntax": {
"syntax": "short"
},
"array_push": true,
"assign_null_coalescing_to_coalesce_equal": true,
"binary_operator_spaces": {
"default": "align_single_space_minimal",
"operators": {
"=>": "single_space",
"=": "single_space"
}
},
"blank_line_after_namespace": true,
"blank_line_after_opening_tag": true,
"blank_line_before_statement": {
"statements": [
"break",
"case",
"continue",
"declare",
"default",
"do",
"exit",
"for",
"foreach",
"goto",
"if",
"include",
"include_once",
"phpdoc",
"require",
"require_once",
"return",
"switch",
"throw",
"try",
"while",
"yield",
"yield_from"
]
},
"braces": {
"allow_single_line_closure": true
},
"cast_spaces": true,
"class_attributes_separation": {
"elements": {
"method": "one"
}
},
"class_definition": {
"single_line": true
},
"concat_space": {
"spacing": "one"
},
"declare_equal_normalize": true,
"declare_parentheses": true,
"declare_strict_types": true,
"explicit_string_variable": true,
"explicit_indirect_variable": false,
"final_class": true,
"final_internal_class": {
"include": [
"internal"
]
},
"final_public_method_for_abstract_class": true,
"fully_qualified_strict_types": true,
"global_namespace_import": {
"import_classes": true,
"import_constants": true,
"import_functions": true
},
"function_typehint_space": true,
"include": true,
"increment_style": {
"style": "post"
},
"is_null": true,
"lambda_not_used_import": true,
"lowercase_cast": true,
"lowercase_keywords": true,
"lowercase_static_reference": true,
"logical_operators": true,
"magic_constant_casing": true,
"magic_method_casing": true,
"mb_str_functions": false,
"method_argument_space": {
"on_multiline": "ensure_fully_multiline",
"keep_multiple_spaces_after_comma": false
},
"method_chaining_indentation": true,
"modernize_strpos": true,
"modernize_types_casting": true,
"new_with_braces": true,
"no_blank_lines_after_class_opening": true,
"no_blank_lines_after_phpdoc": true,
"no_empty_comment": true,
"no_empty_statement": true,
"no_extra_blank_lines": true,
"no_leading_import_slash": true,
"no_multiline_whitespace_around_double_arrow": true,
"no_short_bool_cast": true,
"no_singleline_whitespace_before_semicolons": true,
"no_spaces_after_function_name": true,
"no_spaces_around_offset": true,
"no_trailing_comma_in_list_call": true,
"no_trailing_comma_in_singleline_array": true,
"no_unneeded_curly_braces": true,
"no_unreachable_default_argument_value": true,
"no_unused_imports": true,
"no_whitespace_before_comma_in_array": true,
"no_whitespace_in_blank_line": true,
"not_operator_with_successor_space": true,
"object_operator_without_whitespace": true,
"ordered_class_elements": {
"order": [
"use_trait",
"case",
"constant",
"constant_public",
"constant_protected",
"constant_private",
"property_public",
"property_protected",
"property_private",
"construct",
"destruct",
"magic",
"phpunit",
"method_abstract",
"method_public_static",
"method_public",
"method_protected_static",
"method_protected",
"method_private_static",
"method_private"
],
"sort_algorithm": "none"
},
"ordered_imports": {
"sort_algorithm": "alpha"
},
"phpdoc_align": {
"align": "vertical"
},
"phpdoc_indent": true,
"phpdoc_no_access": true,
"phpdoc_no_alias_tag": true,
"phpdoc_no_empty_return": true,
"phpdoc_no_useless_inheritdoc": true,
"phpdoc_scalar": {
"types": [
"boolean",
"callback",
"double",
"integer",
"real",
"str"
]
},
"phpdoc_single_line_var_spacing": true,
"phpdoc_summary": true,
"phpdoc_tag_type": true,
"phpdoc_to_comment": false,
"phpdoc_trim": true,
"phpdoc_types": true,
"phpdoc_var_without_name": true,
"return_type_declaration": {
"space_before": "none"
},
"semicolon_after_instruction": true,
"short_scalar_cast": true,
"single_blank_line_at_eof": true,
"blank_lines_before_namespace": true,
"single_class_element_per_statement": true,
"single_import_per_statement": true,
"single_line_after_imports": true,
"single_line_comment_style": {
"comment_types": [
"hash"
]
},
"single_quote": true,
"single_trait_insert_per_statement": true,
"space_after_semicolon": true,
"standardize_not_equals": true,
"ternary_operator_spaces": true,
"trailing_comma_in_multiline": {
"elements": [
"arguments",
"arrays",
"match",
"parameters"
]
},
"trim_array_spaces": true,
"unary_operator_spaces": true,
"whitespace_after_comma_in_array": true,
"combine_consecutive_issets": true,
"combine_consecutive_unsets": true
}
}

4. Laravel Telescope

Role & Importance:

Telescope is a debugging assistant that monitors requests, exceptions, database queries, and more. It is invaluable during development and troubleshooting.

Installation & Setup:

composer require laravel/telescope
php artisan telescope:install
php artisan migrate

Note: Telescope is registered only in local environments via our App Service Provider.

5. Laravel Log Viewer

Role & Importance:

This package provides a user-friendly interface for viewing and managing your application logs. It simplifies troubleshooting and monitoring in production.

Installation & Setup:

Depending on your chosen package (log viewer — Search):

composer require opcodesio/log-viewer
php artisan log-viewer:publish

Publish configuration files if needed and then access the log viewer through the designated route.

6. Scribe for API Documentation

Role & Importance:

Scribe automatically generates API documentation from your routes and annotations. This ensures that your API consumers have up-to-date, comprehensive docs.

Installation & Setup:

composer require --dev knuckleswtf/scribe
php artisan scribe:install

Customize the generated configuration to suit your API’s needs, then generate docs with:

php artisan scribe:generate

7. PHPStan

Role & Importance:

PHPStan is a static analysis tool that helps catch bugs and enforce type safety. It integrates smoothly with Laravel and ensures code quality through early detection of issues.

Installation & Setup:

composer require --dev phpstan/phpstan

Add a phpstan.neon file to configure rules and analysis levels, then run:

./vendor/bin/phpstan analyse

where the phpstan.neon file is

parameters:
level: 5
paths:
- Modules
- app
excludePaths:
- vendor
- node_modules
- storage

8. Hiskey for Git Commit Hooks

Role & Importance:

Hiskey provides Git commit hooks that enforce commit standards, run tests, or execute static analysis before code is committed. This helps maintain a high-quality commit history and avoids pushing broken code.

Install the package via Composer (or as per your chosen package’s instructions):

composer require --dev hiskey/commit-hooks

Configure your Git hooks (usually by linking or copying provided hook scripts to the .git/hooks folder) and adjust the settings as needed in your project’s configuration files.

Conclusion

By integrating these specialized tooling packages and centralizing our configuration in the App Service Provider and bootstrap files, we achieve a highly modular, maintainable, and scalable Laravel API. This structure not only streamlines development and debugging but also ensures that our application remains robust as it grows.

In the next installment, we’ll explore

App Service Provider: Centralizing Bootstrapping Logic

Our AppServiceProvider is the central location for initializing essential configurations. Below is an overview of its key methods and their benefits

and

Bootstrap Files: Modularizing Middleware, Exceptions, and Scheduling

Our bootstrapping files demonstrate our commitment to separation of concerns. Each configuration aspect is extracted into dedicated files

--

--

Abdullah AlHabal
Abdullah AlHabal

Written by Abdullah AlHabal

Software Engineer | Laravel, PHP, NestJS | API Design

No responses yet