Skip to content

Commit 6a51f37

Browse files
committed
introduce RedirectChecker bundle
1 parent a418862 commit 6a51f37

File tree

8 files changed

+305
-1
lines changed

8 files changed

+305
-1
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/vendor/
2+
composer.lock

README.md

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,34 @@
1-
# redirect-checker
1+
# Redirect Checker
2+
Simple tool for checking if your **http redirects** work as you want.
3+
4+
Installation
5+
-
6+
```
7+
$ composer require antstudiocz/redirect-checker
8+
```
9+
10+
Register DI extension:
11+
```neon
12+
extensions:
13+
- Ant\RedirectChecker\DI\RedirectCheckerExtension
14+
```
15+
16+
Add configuration:
17+
```neon
18+
parameters:
19+
redirect-checker:
20+
file: app/config/redirects.neon
21+
```
22+
23+
Create file for example:
24+
```neon
25+
redirects:
26+
"https://www.antstudio.cz": http://www.antstudio.cz
27+
```
28+
29+
Usage
30+
-
31+
```
32+
$ php index.php app:redirect-checker:run
33+
```
34+

composer.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"name": "antstudiocz/redirect-checker",
3+
"license": "MIT",
4+
"require": {
5+
"nette/neon": "^2.4",
6+
"nette/utils": "^2.4",
7+
"ptcong/php-http-class": "^3.0",
8+
"symfony/console": "^3.1",
9+
"kdyby/console": "^2.6",
10+
"adeira/compiler-extension": "^1.0"
11+
},
12+
"require-dev": {
13+
"consistence/coding-standard": "^0.12.0",
14+
"slevomat/coding-standard": "^1.0",
15+
"nette/tester": "2.0.x-dev as v1.7"
16+
},
17+
"autoload": {
18+
"psr-4": {
19+
"Ant\\RedirectChecker\\": "src/"
20+
}
21+
}
22+
}

ruleset.xml

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?xml version="1.0"?>
2+
<ruleset name="ANT studio">
3+
4+
<!--
5+
SEE:
6+
- https://github.com/juzna/nette-coding-standard/blob/master/NetteStandard/ruleset.xml
7+
- https://gist.github.com/jakubkulhan/b054b89cb207822fcb71
8+
- https://github.com/consistence/coding-standard
9+
- https://github.com/slevomat/coding-standard
10+
-->
11+
12+
<description>The ANT studio coding standard.</description>
13+
<exclude-pattern>*/vendor/*</exclude-pattern>
14+
15+
<!-- Composer vendor: -->
16+
<rule ref="./vendor/consistence/coding-standard/Consistence/ruleset.xml">
17+
<exclude name="Generic.PHP.LowerCaseConstant"/>
18+
<exclude name="Generic.Formatting.SpaceAfterCast"/>
19+
<exclude name="Generic.Functions.OpeningFunctionBraceBsdAllman.BraceOnSameLine"/> <!-- better implementation already in: Squiz.Functions.MultiLineFunctionDeclaration.BraceOnSameLine -->
20+
<exclude name="Squiz.Commenting.FunctionComment.MissingParamTag"/>
21+
<exclude name="Squiz.Commenting.FunctionComment.WrongStyle"/>
22+
<exclude name="Squiz.PHP.Heredoc.NotAllowed"/>
23+
<exclude name="Squiz.Strings.DoubleQuoteUsage.ContainsVar"/>
24+
</rule>
25+
26+
<rule ref="./vendor/slevomat/coding-standard/SlevomatCodingStandard/ruleset.xml">
27+
<exclude name="SlevomatCodingStandard.Files.TypeNameMatchesFileName"/>
28+
<exclude name="SlevomatCodingStandard.Namespaces.FullyQualifiedClassNameAfterKeyword"/>
29+
<exclude name="SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly"/>
30+
<exclude name="SlevomatCodingStandard.Namespaces.UseOnlyWhitelistedNamespaces"/>
31+
</rule>
32+
33+
<rule ref="PSR1.Files.SideEffects.FoundWithSymbols">
34+
<exclude-pattern>*.phpt</exclude-pattern>
35+
</rule>
36+
37+
<rule ref="SlevomatCodingStandard.Classes.UnusedPrivateElements">
38+
<properties>
39+
<property name="alwaysUsedPropertiesAnnotations" type="array" value="@ORM\Column,@ORM\Embedded,@ORM\JoinColumn,@ORM\ManyToMany"/>
40+
</properties>
41+
</rule>
42+
43+
<rule ref="SlevomatCodingStandard.Namespaces.UnusedUses">
44+
<properties>
45+
<property name="searchAnnotations" value="true"/>
46+
</properties>
47+
</rule>
48+
49+
<rule ref="Generic.PHP.UpperCaseConstant"/>
50+
51+
</ruleset>

src/Checker.php

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
<?php
2+
3+
namespace Ant\RedirectChecker;
4+
5+
use Nette\Neon\Neon;
6+
7+
class Checker
8+
{
9+
10+
const PASSED = 'ok';
11+
const WARNING = 'warning';
12+
const FAILED = 'failed';
13+
14+
private $configFile;
15+
16+
public function __construct($configFile = '')
17+
{
18+
$this->configFile = __DIR__ . '/../../' . $configFile;
19+
}
20+
21+
public function run()
22+
{
23+
return $this->checkListFromNeon($this->configFile);
24+
}
25+
26+
/**
27+
* Will check list from associative array of rules.
28+
* Index => origin url, Value => needed url
29+
*
30+
* @param [] $list
31+
*
32+
* @return array [OVERALL STATUS, [[SINGLE STATUS, [additional single info]]]]
33+
*/
34+
public function checkList($list)
35+
{
36+
$out = self::PASSED;
37+
$responses = [];
38+
foreach ($list as $originUrl => $finalUrl) {
39+
list($result, $response) = $this->check($originUrl, $finalUrl);
40+
switch ($result) {
41+
case self::WARNING:
42+
if ($out == self::PASSED) {
43+
$out = $result;
44+
}
45+
break;
46+
case self::FAILED:
47+
if ($out !== self::FAILED) {
48+
$out = $result;
49+
}
50+
break;
51+
}
52+
$responses[] = [$result, $response];
53+
}
54+
return [$out, $responses];
55+
}
56+
57+
/**
58+
* Checks single url
59+
*
60+
* @param string $originUrl Full url with http://
61+
* @param string $finalUrl Full url with http://
62+
* @param integer $maxFollowRedirects
63+
*
64+
* @return array [STATUS, [additional info for output]]
65+
*/
66+
public function check($originUrl, $finalUrl, $maxFollowRedirects = 10)
67+
{
68+
$out = self::FAILED;
69+
$client = \EasyRequest::create($originUrl, 'GET', ['follow_redirects' => $maxFollowRedirects]);
70+
$client->send();
71+
$outList = $list = $client->getRedirectedUrls();
72+
73+
if (empty($list)) {
74+
if ($originUrl == $finalUrl) {
75+
$out = self::PASSED;
76+
}
77+
} elseif (in_array($finalUrl, $list)) {
78+
if (array_pop($list) == $finalUrl) {
79+
$out = self::PASSED;
80+
} else {
81+
$out = self::WARNING;
82+
}
83+
}
84+
85+
array_shift($outList);
86+
return [$out, ['from' => $originUrl, 'to' => $finalUrl, 'hops' => $outList, 'hopsCount' => count($outList)]];
87+
}
88+
89+
/**
90+
* Will check list of rules from a neon file.
91+
*
92+
* @param string $filefilename
93+
*
94+
* @return array
95+
* @throws \Exception
96+
*/
97+
public function checkListFromNeon($filefilename)
98+
{
99+
if (!file_exists($filefilename)) {
100+
throw new \Exception("File '$filefilename' not found.");
101+
}
102+
$configuration = Neon::decode(file_get_contents($filefilename));
103+
if (!isset($configuration['redirects']) || empty($configuration['redirects'])) {
104+
throw new \Exception("Section 'redirects' not found or empty in configuration file: '{$filefilename}'");
105+
}
106+
return $this->checkList($configuration['redirects']);
107+
}
108+
109+
}

src/Console/TestRedirects.php

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
<?php
2+
3+
namespace Ant\RedirectChecker\Console;
4+
5+
use Ant\RedirectChecker\Checker;
6+
use Nette\Utils\Strings;
7+
use Symfony\Component\Console\Input\InputInterface;
8+
use Symfony\Component\Console\Output\OutputInterface;
9+
10+
class TestRedirects extends \Symfony\Component\Console\Command\Command
11+
{
12+
13+
/** @var Checker @inject */
14+
public $checker;
15+
16+
protected function configure()
17+
{
18+
$this->setName('app:redirect-checker:run');
19+
$this->setDescription('Checks list of redirects');
20+
}
21+
22+
protected function execute(InputInterface $input, OutputInterface $output)
23+
{
24+
try {
25+
list($result, $data) = $this->checker->run();
26+
foreach ($data as $resultRow) {
27+
$row = $this->style(Strings::firstUpper($resultRow[0]), $resultRow[0]);
28+
$output->writeln($row);
29+
$output->writeln("\tFrom: " . $resultRow[1]['from']);
30+
$output->writeln("\tTo: " . $resultRow[1]['to']);
31+
$output->writeln("\tHops count: " . $resultRow[1]['hopsCount']);
32+
if ($resultRow[0] !== Checker::PASSED || $resultRow[1]['hopsCount'] > 1) {
33+
$output->writeln("\tHops: " . implode(' -> ', $resultRow[1]['hops']));
34+
}
35+
$output->writeln('');
36+
}
37+
38+
$output->writeln('============================');
39+
$output->writeln('Final result: ' . $this->style(Strings::firstUpper($result), $result));
40+
41+
return 0; // zero return code means everything is ok
42+
} catch (\Exception $exc) {
43+
$output->writeln('<error>' . $exc->getMessage() . '</error>');
44+
return 1; // non-zero return code means error
45+
}
46+
}
47+
48+
protected function style($text, $state)
49+
{
50+
switch ($state) {
51+
case Checker::FAILED:
52+
$out = "<error>{$text}</error>";
53+
break;
54+
case Checker::WARNING:
55+
$out = "<comment>{$text}</comment>";
56+
break;
57+
default:
58+
$out = "<info>{$text}</info>";
59+
}
60+
return $out;
61+
62+
}
63+
64+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace Ant\RedirectChecker\DI;
4+
5+
use Adeira\CompilerExtension;
6+
use Ant\RedirectChecker\Console\TestRedirects;
7+
8+
class RedirectCheckerExtension extends CompilerExtension
9+
{
10+
11+
public function loadConfiguration()
12+
{
13+
$this->addConfig(__DIR__ . '/services.neon');
14+
15+
$builder = $this->getContainerBuilder();
16+
$builder->addDefinition($this->prefix('command.testRedirects'))
17+
->setClass(TestRedirects::class)
18+
->addTag(\Kdyby\Console\DI\ConsoleExtension::TAG_COMMAND);
19+
}
20+
21+
}

src/DI/services.neon

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
services:
2+
- Ant\RedirectChecker\Checker(%redirect-checker.file%)

0 commit comments

Comments
 (0)