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