├── .gitignore ├── .scrutinizer.yml ├── .travis.yml ├── README.md ├── composer.json ├── phpunit.xml ├── src ├── CheckResult.php ├── Checks │ ├── Check.php │ ├── Laravel │ │ ├── Common │ │ │ └── AppKey.php │ │ ├── Dev │ │ │ ├── AppDebug.php │ │ │ ├── ConfigurationIsNotCached.php │ │ │ ├── OptimizedClassLoaderDoesNotExist.php │ │ │ └── RoutesAreNotCached.php │ │ └── Production │ │ │ ├── AppDebug.php │ │ │ ├── CacheDriver.php │ │ │ ├── ConfigurationIsCached.php │ │ │ ├── MailDriver.php │ │ │ ├── MailPretend.php │ │ │ ├── OptimizedClassLoaderExists.php │ │ │ ├── QueueDriver.php │ │ │ ├── RoutesAreCached.php │ │ │ └── SessionDriver.php │ └── Server │ │ ├── Common │ │ ├── PhpVersion.php │ │ └── RequiredPhpExtensions.php │ │ ├── Dev │ │ └── FunctionNestingLevel.php │ │ └── Production │ │ └── XdebugIsDisabled.php ├── ChecksCollection.php ├── Console │ └── SystemCheckCommand.php └── ServiceProvider.php └── tests ├── CheckResultTest.php └── ChecksCollectionTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | composer.phar 3 | composer.lock 4 | .DS_Store 5 | /.idea 6 | -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | filter: 2 | paths: 3 | - 'src/*' 4 | excluded_paths: 5 | - 'src/config/*' 6 | - 'vendor/*' 7 | - 'spec/*' 8 | tools: 9 | php_cs_fixer: 10 | config: { level: psr2 } 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.5 5 | - 5.6 6 | - 7.0 7 | - 7.1 8 | - 7.2 9 | - 7.3 10 | 11 | before_script: 12 | - composer self-update 13 | - composer install --prefer-source --no-interaction --dev 14 | 15 | script: 16 | - composer test 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Latest Stable Version](https://poser.pugx.org/arrilot/laravel-systemcheck/v/stable.svg)](https://packagist.org/packages/arrilot/laravel-systemcheck/) 2 | [![Total Downloads](https://img.shields.io/packagist/dt/arrilot/laravel-systemcheck.svg?style=flat)](https://packagist.org/packages/arrilot/laravel-systemcheck) 3 | [![Build Status](https://img.shields.io/travis/arrilot/laravel-systemcheck/master.svg?style=flat)](https://travis-ci.org/arrilot/laravel-systemcheck) 4 | [![Scrutinizer Quality Score](https://scrutinizer-ci.com/g/arrilot/laravel-systemcheck/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/arrilot/laravel-systemcheck/) 5 | 6 | # Laravel System Check 7 | 8 | *Check your server and application configuration according to APP_ENV* 9 | 10 | ## Installation 11 | 12 | 1) Run ```composer require arrilot/laravel-systemcheck``` 13 | 14 | 2) Register a service provider inside the `app.php` configuration file. 15 | 16 | ```php 17 | ... 18 | 'providers' => [ 19 | ... 20 | Arrilot\SystemCheck\ServiceProvider::class, 21 | ], 22 | ``` 23 | 24 | ## Usage 25 | 26 | This package provides a `php artisan system:check` command that performs a bunch of checks and prints results. 27 | 28 | ![screenshot](https://i.gyazo.com/1fb26f1eee971542accbc13eb3fdb49c.png) 29 | 30 | There are two modes. 31 | 32 | 1. production 33 | 2. dev 34 | 35 | Each mode has its own collection of checks. 36 | 37 | The mode is determined automatically according to APP_ENV. 38 | You can override current environment by passing --env to the command. `php artisan system:check --env=production` 39 | 40 | ## Configuration 41 | 42 | By default the package treats the following environments as "production": 43 | ```php 44 | ['production', 'prod'] 45 | ``` 46 | 47 | You can override them by calling 48 | ```php 49 | app()->make(ChecksCollection::class)->setProductionEnvironments(['prod1', 'prod2']); 50 | ``` 51 | in your `AppServiceProvider` 52 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "arrilot/laravel-systemcheck", 3 | "description": "", 4 | "license": "MIT", 5 | "keywords": ["laravel", "system", "health", "check"], 6 | "authors": [ 7 | { 8 | "name": "Nekrasov Ilya", 9 | "email": "nekrasov.ilya90@gmail.com" 10 | } 11 | ], 12 | "homepage": "https://github.com/arrilot/laravel-systemcheck", 13 | "require": { 14 | "php": ">=5.5.0", 15 | "illuminate/support": ">=5", 16 | "illuminate/contracts": ">=5", 17 | "illuminate/container": ">=5", 18 | "illuminate/console": ">=5" 19 | }, 20 | "require-dev": { 21 | "phpunit/phpunit": "~4.0" 22 | }, 23 | "autoload": { 24 | "psr-4": { 25 | "Arrilot\\SystemCheck\\": "src/" 26 | } 27 | }, 28 | "extra": { 29 | "laravel": { 30 | "providers":[ 31 | "Arrilot\\SystemCheck\\ServiceProvider" 32 | ] 33 | } 34 | }, 35 | "autoload-dev": { 36 | "psr-4": { 37 | "Arrilot\\SystemCheck\\Test\\": "tests" 38 | } 39 | }, 40 | "scripts": { 41 | "test": "phpunit" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | tests 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/CheckResult.php: -------------------------------------------------------------------------------- 1 | possibleStatuses)) { 44 | throw new InvalidArgumentException("Check result can not have status '{$status}'"); 45 | } 46 | 47 | $this->status = $status; 48 | $this->comment = $comment; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Checks/Check.php: -------------------------------------------------------------------------------- 1 | app = $app; 31 | } 32 | 33 | /** 34 | * Perform the check. 35 | * 36 | * @return CheckResult 37 | */ 38 | abstract public function perform(); 39 | 40 | /** 41 | * Stop the check because it passed. 42 | * 43 | * @param string $message 44 | * 45 | * @return CheckResult 46 | */ 47 | public function ok($message = '') 48 | { 49 | return new CheckResult('Ok', $message); 50 | } 51 | 52 | /** 53 | * Stop the check with a note. 54 | * 55 | * @param string $message 56 | * 57 | * @return CheckResult 58 | */ 59 | public function note($message = '') 60 | { 61 | return new CheckResult('Note', $message); 62 | } 63 | 64 | /** 65 | * Stop the check with an error. 66 | * 67 | * @param string $message 68 | * 69 | * @return CheckResult 70 | */ 71 | public function fail($message = '') 72 | { 73 | return new CheckResult('Fail', $message); 74 | } 75 | 76 | /** 77 | * Skip the check. 78 | * 79 | * @param string $message 80 | * 81 | * @return CheckResult 82 | */ 83 | public function skip($message = '') 84 | { 85 | return new CheckResult('Skip', $message); 86 | } 87 | 88 | /** 89 | * Getter for description. 90 | * 91 | * @return string 92 | */ 93 | public function getDescription() 94 | { 95 | return $this->description; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/Checks/Laravel/Common/AppKey.php: -------------------------------------------------------------------------------- 1 | app['config']['app.key']; 26 | 27 | if (!$key) { 28 | return $this->fail('app.key must be set'); 29 | } 30 | 31 | if (strlen($key) !== $this->keyLength) { 32 | return $this->fail("app.key must be a {$this->keyLength} character string"); 33 | } 34 | 35 | return $this->ok(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Checks/Laravel/Dev/AppDebug.php: -------------------------------------------------------------------------------- 1 | app['config']['app.debug']) { 25 | return $this->note("app.debug should not be set to 'false' in production"); 26 | } 27 | 28 | return $this->ok(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Checks/Laravel/Dev/ConfigurationIsNotCached.php: -------------------------------------------------------------------------------- 1 | app->configurationIsCached()) { 25 | return $this->fail('Configuration must not be cached in development'); 26 | } 27 | 28 | return $this->ok(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Checks/Laravel/Dev/OptimizedClassLoaderDoesNotExist.php: -------------------------------------------------------------------------------- 1 | app->getCachedPackagesPath()) || 26 | file_exists($this->app->getCachedServicesPath()) 27 | ) { 28 | return $this->fail("Run 'php artisan clear-compiled'"); 29 | } 30 | 31 | return $this->ok(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Checks/Laravel/Dev/RoutesAreNotCached.php: -------------------------------------------------------------------------------- 1 | app->routesAreCached()) { 25 | return $this->fail('Routes should not be cached in development'); 26 | } 27 | 28 | return $this->ok(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Checks/Laravel/Production/AppDebug.php: -------------------------------------------------------------------------------- 1 | app['config']['app.debug']) { 25 | return $this->fail("app.debug must not be set to 'true' in production"); 26 | } 27 | 28 | return $this->ok(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Checks/Laravel/Production/CacheDriver.php: -------------------------------------------------------------------------------- 1 | app['config']['cache.default']; 25 | 26 | if ($driver === 'array') { 27 | return $this->fail("Default cache driver must not be set to 'array' in production"); 28 | } 29 | 30 | if ($driver === 'file') { 31 | return $this->note('File cache driver is not recommended for production'); 32 | } 33 | 34 | return $this->ok(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Checks/Laravel/Production/ConfigurationIsCached.php: -------------------------------------------------------------------------------- 1 | app->configurationIsCached()) { 25 | return $this->fail('Configuration must be cached in production'); 26 | } 27 | 28 | return $this->ok(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Checks/Laravel/Production/MailDriver.php: -------------------------------------------------------------------------------- 1 | app['config']['mail.driver'] === 'array') { 25 | return $this->fail("Mail driver must not be set to 'log' in production"); 26 | } 27 | 28 | return $this->ok(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Checks/Laravel/Production/MailPretend.php: -------------------------------------------------------------------------------- 1 | app['config']['mail.pretend']) { 25 | return $this->fail("'mail.pretend' must not be set to 'true' in production"); 26 | } 27 | 28 | return $this->ok(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Checks/Laravel/Production/OptimizedClassLoaderExists.php: -------------------------------------------------------------------------------- 1 | app->getCachedConfigPath()) || 26 | ! file_exists($this->app->getCachedPackagesPath()) || 27 | ! file_exists($this->app->getCachedRoutesPath()) || 28 | ! file_exists($this->app->getCachedServicesPath()) 29 | ) { 30 | return $this->fail("Run 'php artisan optimize'"); 31 | } 32 | 33 | return $this->ok(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Checks/Laravel/Production/QueueDriver.php: -------------------------------------------------------------------------------- 1 | app['config']['queue.default']; 25 | 26 | if ($driver === 'null') { 27 | return $this->fail("Default queue driver must not be set to 'null' in production"); 28 | } 29 | 30 | if ($driver === 'sync') { 31 | return $this->note('Sync queue driver is not recommended for production'); 32 | } 33 | 34 | return $this->ok(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Checks/Laravel/Production/RoutesAreCached.php: -------------------------------------------------------------------------------- 1 | app->routesAreCached()) { 25 | return $this->fail('Routes should be cached in production'); 26 | } 27 | 28 | return $this->ok(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Checks/Laravel/Production/SessionDriver.php: -------------------------------------------------------------------------------- 1 | app['config']['session.driver']; 25 | 26 | if ($driver === 'array') { 27 | return $this->fail("'session.driver' must not be set to 'array' in production"); 28 | } 29 | 30 | if ($driver === 'file') { 31 | return $this->note('File session driver is not recommended for production'); 32 | } 33 | 34 | return $this->ok(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Checks/Server/Common/PhpVersion.php: -------------------------------------------------------------------------------- 1 | fail("Laravel requires PHP >= {$minimal}. Current version: '{$current}'"); 30 | } 31 | 32 | if (version_compare($current, '7.0.0', '<')) { 33 | return $this->note("PHP7 is highly recommended. Current version: '{$current}'"); 34 | } 35 | 36 | return $this->ok(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Checks/Server/Common/RequiredPhpExtensions.php: -------------------------------------------------------------------------------- 1 | extensions as $extension) { 37 | if (!extension_loaded($extension)) { 38 | return $this->fail("PHP extension '{$extension}' is missing from your system."); 39 | } 40 | } 41 | 42 | return $this->ok(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Checks/Server/Dev/FunctionNestingLevel.php: -------------------------------------------------------------------------------- 1 | skip(); 26 | } 27 | 28 | if (ini_get('xdebug.max_nesting_level') < 256) { 29 | return $this->fail('xdebug.max_nesting_level should be >= 256'); 30 | } 31 | 32 | return $this->ok(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Checks/Server/Production/XdebugIsDisabled.php: -------------------------------------------------------------------------------- 1 | fail('Xdebug extension should not be loaded in production'); 26 | } 27 | 28 | return $this->ok(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/ChecksCollection.php: -------------------------------------------------------------------------------- 1 | [ 31 | Checks\Server\Common\PhpVersion::class, 32 | Checks\Server\Common\RequiredPhpExtensions::class, 33 | Checks\Server\Production\XdebugIsDisabled::class, 34 | ], 35 | 'Dev' => [ 36 | Checks\Server\Common\PhpVersion::class, 37 | Checks\Server\Common\RequiredPhpExtensions::class, 38 | Checks\Server\Dev\FunctionNestingLevel::class, 39 | ], 40 | ]; 41 | 42 | /** 43 | * The array of available Laravel configuration checks. 44 | * 45 | * @var array 46 | */ 47 | protected $laravelChecks = [ 48 | 'Production' => [ 49 | Checks\Laravel\Production\OptimizedClassLoaderExists::class, 50 | Checks\Laravel\Production\ConfigurationIsCached::class, 51 | Checks\Laravel\Production\RoutesAreCached::class, 52 | Checks\Laravel\Common\AppKey::class, 53 | Checks\Laravel\Production\AppDebug::class, 54 | Checks\Laravel\Production\CacheDriver::class, 55 | Checks\Laravel\Production\MailDriver::class, 56 | Checks\Laravel\Production\MailPretend::class, 57 | Checks\Laravel\Production\QueueDriver::class, 58 | Checks\Laravel\Production\SessionDriver::class, 59 | ], 60 | 'Dev' => [ 61 | Checks\Laravel\Common\AppKey::class, 62 | Checks\Laravel\Dev\AppDebug::class, 63 | ], 64 | ]; 65 | 66 | /** 67 | * ChecksCollection constructor. 68 | * 69 | * @param \Illuminate\Contracts\Foundation\Application $app 70 | */ 71 | public function __construct($app) 72 | { 73 | $this->app = $app; 74 | } 75 | 76 | /** 77 | * Get all server checks for a given environment. 78 | * 79 | * @param string|null $env 80 | * 81 | * @return array 82 | */ 83 | public function getServerChecks($env = null) 84 | { 85 | return $this->serverChecks[$this->getModeByEnv($env)]; 86 | } 87 | 88 | /** 89 | * Get all Laravel checks for a given environment. 90 | * 91 | * @param string|null $env 92 | * 93 | * @return array 94 | */ 95 | public function getLaravelChecks($env = null) 96 | { 97 | return $this->laravelChecks[$this->getModeByEnv($env)]; 98 | } 99 | 100 | /** 101 | * Get checking mode by laravel environment. 102 | * 103 | * @param string|null $env 104 | * 105 | * @return string 106 | */ 107 | protected function getModeByEnv($env = null) 108 | { 109 | if (is_null($env)) { 110 | $env = $this->app->environment(); 111 | } 112 | 113 | return in_array($env, $this->productionEnvironments) ? 'Production' : 'Dev'; 114 | } 115 | 116 | /** 117 | * Setter for production environments. 118 | * 119 | * @param array|string $env 120 | * 121 | * @return $this 122 | */ 123 | public function setProductionEnvironments($env) 124 | { 125 | $this->productionEnvironments = (array) $env; 126 | 127 | return $this; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/Console/SystemCheckCommand.php: -------------------------------------------------------------------------------- 1 | checks = $checks; 49 | } 50 | 51 | /** 52 | * Execute the console command. 53 | * 54 | * @return void 55 | */ 56 | public function fire() 57 | { 58 | $env = is_null($this->option('env')) 59 | ? $this->laravel->environment() 60 | : $this->option('env'); 61 | 62 | $this->output->writeln("Performing checks for environment: {$env}"); 63 | $this->output->newLine(); 64 | 65 | $this->performServerChecks($env); 66 | $this->performLaravelChecks($env); 67 | } 68 | 69 | /** 70 | * laravel 5.* support 71 | */ 72 | public function handle() { 73 | $this->fire(); 74 | } 75 | 76 | /** 77 | * Perform server configuration checks. 78 | * 79 | * @param string $env 80 | * 81 | * @return void 82 | */ 83 | protected function performServerChecks($env) 84 | { 85 | $this->info('Server configuration checks:'); 86 | 87 | $this->performChecks($this->checks->getServerChecks($env)); 88 | } 89 | 90 | /** 91 | * Perform laravel configuration checks. 92 | * 93 | * @param string $env 94 | * 95 | * @return void 96 | */ 97 | protected function performLaravelChecks($env) 98 | { 99 | $this->info('Laravel configuration checks:'); 100 | 101 | $this->performChecks($this->checks->getLaravelChecks($env)); 102 | } 103 | 104 | /** 105 | * Perform configuration checks. 106 | * 107 | * @param array $checks 108 | * 109 | * @return void 110 | */ 111 | protected function performChecks($checks) 112 | { 113 | $rows = []; 114 | 115 | foreach ($checks as $check) { 116 | if ($row = $this->performCheck($check)) { 117 | $rows[] = $row; 118 | } 119 | } 120 | 121 | $this->displayRows($rows); 122 | } 123 | 124 | /** 125 | * Build a check object and perform a check. 126 | * 127 | * @param string $class 128 | * 129 | * @return array 130 | */ 131 | protected function performCheck($class) 132 | { 133 | $check = new $class($this->laravel); 134 | $result = $check->perform(); 135 | 136 | if ($result->status == 'Skip') { 137 | return []; 138 | } 139 | 140 | return [ 141 | 'Check' => $check->getDescription(), 142 | 'Result' => $this->styleStatus($result->status), 143 | 'Comment' => $result->comment, 144 | ]; 145 | } 146 | 147 | /** 148 | * Display all given rows as a table. 149 | * 150 | * @param array $rows 151 | */ 152 | protected function displayRows(array $rows) 153 | { 154 | $this->table($this->headers, $rows); 155 | 156 | $this->output->newLine(); 157 | } 158 | 159 | /** 160 | * Add some styling to status. 161 | * 162 | * @param string $status 163 | * 164 | * @return string 165 | */ 166 | protected function styleStatus($status) 167 | { 168 | if ($status === 'Ok') { 169 | return 'Ok'; 170 | } 171 | 172 | if ($status === 'Note') { 173 | return 'Note'; 174 | } 175 | 176 | if ($status === 'Fail') { 177 | return 'Fail'; 178 | } 179 | 180 | throw new InvalidArgumentException("Check can not return status '{$status}'"); 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/ServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->singleton(ChecksCollection::class, function ($app) { 19 | return new ChecksCollection($app); 20 | }); 21 | 22 | $this->app->singleton('command.system.check', function ($app) { 23 | return new SystemCheckCommand($app->make(ChecksCollection::class)); 24 | }); 25 | 26 | $this->commands('command.system.check'); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/CheckResultTest.php: -------------------------------------------------------------------------------- 1 | assertSame('Ok', $result->status); 15 | $this->assertSame('Ok comment', $result->comment); 16 | } 17 | 18 | public function test_it_can_be_instantiated_with_note_status() 19 | { 20 | $result = new CheckResult('Note', 'Note comment'); 21 | 22 | $this->assertSame('Note', $result->status); 23 | $this->assertSame('Note comment', $result->comment); 24 | } 25 | 26 | public function test_it_can_be_instantiated_with_fail_status() 27 | { 28 | $result = new CheckResult('Fail', 'Fail comment'); 29 | 30 | $this->assertSame('Fail', $result->status); 31 | $this->assertSame('Fail comment', $result->comment); 32 | } 33 | 34 | public function test_it_can_be_instantiated_with_skip_status() 35 | { 36 | $result = new CheckResult('Skip', 'Skip comment'); 37 | 38 | $this->assertSame('Skip', $result->status); 39 | $this->assertSame('Skip comment', $result->comment); 40 | } 41 | 42 | public function test_it_can_not_be_instantiated_with_random_status() 43 | { 44 | $this->setExpectedException('InvalidArgumentException'); 45 | 46 | $result = new CheckResult('Foo', 'Random comment'); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/ChecksCollectionTest.php: -------------------------------------------------------------------------------- 1 | collection(); 22 | 23 | $checks = $collection->getServerChecks('production'); 24 | 25 | $this->assertInternalType('array', $checks); 26 | $this->assertTrue(is_subclass_of($checks[0], Check::class)); 27 | } 28 | 29 | public function test_it_returns_some_laravel_checks() 30 | { 31 | $collection = $this->collection(); 32 | 33 | $checks = $collection->getLaravelChecks('production'); 34 | 35 | $this->assertInternalType('array', $checks); 36 | $this->assertTrue(is_subclass_of($checks[0], Check::class)); 37 | } 38 | } 39 | --------------------------------------------------------------------------------