├── .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 | [](https://packagist.org/packages/arrilot/laravel-systemcheck/)
2 | [](https://packagist.org/packages/arrilot/laravel-systemcheck)
3 | [](https://travis-ci.org/arrilot/laravel-systemcheck)
4 | [](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 | 
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 |
--------------------------------------------------------------------------------