Last active
November 25, 2017 16:39
-
-
Save FWidm/f09aaa852a03e857eabf33990b2fb0af to your computer and use it in GitHub Desktop.
A small CLI tool that finds all `use`d containers for a container. In addition it also detects active `Apiato::call` (only spaces or tabs in front of a call)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$ php artisan apiato:container-dependencies app/Containers/GeoLocation/ | |
Searching for dependencies in container: app/Containers/GeoLocation/ | |
Remove own container from listings? (y/n): | |
> n | |
Found dependencies: | |
[imports]: | |
[Authentication]: | |
[0]: app/Containers/GeoLocation/Actions/CreateGeoRequestAction.php | |
[1]: app/Containers/GeoLocation/Actions/GetGeoLocationByIdAction.php | |
[2]: app/Containers/GeoLocation/Actions/GetGeoLocationsAction.php | |
[GeoLocation]: | |
[0]: app/Containers/GeoLocation/Actions/CreateGeoRequestAction.php | |
[1]: app/Containers/GeoLocation/Actions/DeleteGeoLocationByIDAction.php | |
[2]: app/Containers/GeoLocation/Actions/DemoAction.php | |
[3]: app/Containers/GeoLocation/Actions/GetGeoLocationByIdAction.php | |
[4]: app/Containers/GeoLocation/Actions/GetGeoLocationsAction.php | |
[5]: app/Containers/GeoLocation/Tasks/CreateGeoLocationTask.php | |
[6]: app/Containers/GeoLocation/Tasks/DeleteGeoLocationTask.php | |
[7]: app/Containers/GeoLocation/Tasks/GetGeoLocationByIdTask.php | |
[8]: app/Containers/GeoLocation/Tasks/GetGeoLocationsTask.php | |
[9]: app/Containers/GeoLocation/Tasks/UpdateGeoLocationTask.php | |
[10]: app/Containers/GeoLocation/UI/API/Controllers/Controller.php | |
[11]: app/Containers/GeoLocation/UI/API/Transformers/GeoLocationTransformer.php | |
[User]: | |
[0]: app/Containers/GeoLocation/Actions/DemoAction.php | |
[1]: app/Containers/GeoLocation/Data/Seeders/GeoLocationCreateResearcherSeeder_9.php | |
[2]: app/Containers/GeoLocation/Models/GeoLocation.php | |
[3]: app/Containers/GeoLocation/UI/API/Transformers/GeoLocationTransformer.php | |
[WeatherData]: | |
[0]: app/Containers/GeoLocation/Actions/DemoAction.php | |
[1]: app/Containers/GeoLocation/UI/API/Transformers/GeoLocationTransformer.php | |
[Authorization]: | |
[0]: app/Containers/GeoLocation/Data/Seeders/GeoLocationCreateResearcherSeeder_9.php | |
[1]: app/Containers/GeoLocation/Data/Seeders/GeoLocationPermissionsSeeder_1.php | |
[2]: app/Containers/GeoLocation/Data/Seeders/GeoLocationRolesSeeder_2.php | |
[apiatoCalls]: | |
[GeoLocation]: | |
[0]: app/Containers/GeoLocation/Actions/DemoAction.php | |
Display Container author and description from the composer.json?(y/n): | |
> y | |
[name]: apiato/authentication | |
[description]: apiato/authentication | |
[name]: fwidm/geolocation | |
[description]: fwidm/geolocation | |
[type]: apiato-container | |
[support]: | |
[source]: link/to/container/repository/ | |
[name]: apiato/user | |
[description]: apiato/user | |
[name]: fwidm/weatherdata | |
[description]: fwidm/weatherdata | |
[type]: apiato-container | |
[name]: apiato/authorization | |
[description]: apiato/authorization |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Containers\Application\UI\CLI\Transformers; | |
use App\Ship\Parents\Transformers\Transformer; | |
use stdClass; | |
class ComposerTransformer extends Transformer | |
{ | |
/** | |
* @param stdClass $decodedJson | |
* @return array | |
*/ | |
public function transform(stdClass $decodedJson) | |
{ | |
$ret = [ | |
'name' => $decodedJson->name, | |
'description' => $decodedJson->name, | |
]; | |
if (isset($decodedJson->type)) | |
$ret['type'] = $decodedJson->type; | |
if (isset($decodedJson->support)) | |
$ret['support'] = (array)$decodedJson->support; | |
return $ret; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Containers\Application\UI\CLI\Commands; | |
use App\Containers\Application\UI\CLI\Transformers\ComposerTransformer; | |
use App\Ship\Parents\Commands\ConsoleCommand; | |
use Dotenv\Exception\InvalidPathException; | |
use Exception; | |
use RecursiveDirectoryIterator; | |
use RecursiveIteratorIterator; | |
use Spatie\Fractal\Fractal; | |
use Spatie\Fractalistic\ArraySerializer; | |
/** | |
* Class FindContainerDependenciesCommand | |
* Parses all files in the Container. This is needed due to the implemented apiato calls. | |
* It supports both $this->call(PATH/TO/FILE,... (by parsing imports) | |
* as well as $Apiato::call('CONTAINER@FUNC',[args]... | |
* | |
* @author Fabian Widmann <[email protected]> | |
*/ | |
class FindContainerDependenciesCommand extends ConsoleCommand | |
{ | |
protected $signature = 'apiato:list:dependencies {containerPath}'; | |
protected $description = 'Lists all dependencies from the given container to other containers.'; | |
public function __construct() | |
{ | |
parent::__construct(); | |
} | |
public function handle() | |
{ | |
$containerPath = $this->argument('containerPath'); | |
$this->info('Searching for dependencies in container: ' . $containerPath); | |
$input = $this->ask('Remove own container from listings? (y/n)'); | |
$filterOwnContainer = false; | |
if (isset($input) && $input == 'y') { | |
$filterOwnContainer = true; | |
} | |
$fileContainerMatch = $this->getDependencies($containerPath, $filterOwnContainer); | |
if (count($fileContainerMatch) > 0) { | |
$this->info('Found dependencies:'); | |
$this->info($this->prettyPrintArray($fileContainerMatch)); | |
$input = $this->ask('Display Container author and description from the composer.json?(y/n)'); | |
if (isset($input) && $input == 'y') { | |
// $fileContainerMatch structure: | |
// imports | |
// containerName(s) | |
// File(s) | |
$matches = array_unique(array_keys(array_merge(...array_values($fileContainerMatch)))); | |
foreach ($matches as $match) { | |
$this->info($this->prettyPrintArray($this->getComposerInformation($match))); | |
} | |
} | |
} else | |
$this->info('No dependencies found.'); | |
} | |
/**Utility print function that takes an array and outputs it by applying the given indent. Each array found will be printed recursively with indent+indentmodifier. | |
* @param $arr | |
* @param int $indent | |
* @param int $indentModifier | |
* @return string | |
*/ | |
private function prettyPrintArray($arr, $indent = 0, $indentModifier = 4) | |
{ | |
if (!is_array($arr)) { | |
return $arr; | |
} | |
$string = ""; | |
foreach ($arr as $key => $value) { | |
$string .= str_repeat(" ", $indent) . "[" . $key . "]" . ": "; | |
if (is_array($value)) { | |
$string .= PHP_EOL . $this->prettyPrintArray($value, $indent + $indentModifier) . PHP_EOL; | |
} else if (is_string($value) || settype($item, 'string') !== false || (is_object($value) && method_exists($value, '__toString'))){ | |
$string .= $value . PHP_EOL; | |
} | |
else | |
throw new \InvalidArgumentException('Current value cannot be converted to string: value=' . $value); | |
} | |
return $string; | |
} | |
/** | |
* Get composer information by decoding the json and applying the ComposerTransformer. | |
* @param $containerName | |
* @return array|string | |
*/ | |
private function getComposerInformation($containerName) | |
{ | |
$composerFile = 'app/Containers/' . $containerName . '/composer.json'; | |
try { | |
$content = file_get_contents($composerFile); | |
if (isset($content)) { | |
$json = \GuzzleHttp\json_decode($content); | |
return Fractal::create($json, ComposerTransformer::class, ArraySerializer::class)->toArray(); | |
} | |
} catch (Exception $e) { | |
return 'No composer.json found in path: ' . $composerFile; | |
} | |
} | |
/** | |
* Extracts the content of a file and find all containers by finding all containers in App\Containers\$containerName\* | |
* @param $filePath string - path to the file | |
* @return null | array of containers | |
*/ | |
private function getContainerFromUseStatement($filePath) | |
{ | |
$content = file_get_contents($filePath); | |
//alphanumeric? | |
preg_match_all('/use App\\\\Containers\\\\(?P<containers>[a-zA-Z\d]*)\\\\/', $content, $matches); | |
$ret = []; | |
if (isset($matches['containers'])) { | |
$ret['containers'] = array_unique($matches['containers']); | |
} | |
return $ret; | |
} | |
/** | |
* Extracts the content of a file and find all containers by finding all containers in App\Containers\$containerName\* | |
* @param $filePath string - path to the file | |
* @return null | array of containers | |
*/ | |
private function getContainerFromApiatoCall($filePath) | |
{ | |
$content = file_get_contents($filePath); | |
//ignores everything that doesnt begin with spaces or tabs. | |
$pattern = "/^[ \t]*Apiato::call\('(?P<containers>.*)@([?P<functions>a-zA-Z][a-zA-Z\d]*)',\[(?P<args>.*)]/m"; | |
preg_match_all($pattern, $content, $matches); | |
$ret = []; | |
if (isset($matches['containers'])) { | |
$ret['containers'] = array_unique($matches['containers']); | |
} | |
//todo: add functions and arguments if needed currently unsupported. They are stored in the group 'functions' and 'args'. | |
return $ret; | |
} | |
/** | |
* Iterates through the given path recursively to obtain | |
* 1) all used containers of the given container | |
* 2) an array that contains the containers as keys and all files using it as value. | |
* @param $path - to the container | |
* @return array - [$usedContainers, $filesInContainers] | |
*/ | |
private function getDependencies($path, $filterOwnContainer = false) | |
{ | |
$ownContainerName = explode('/', explode('app/Containers/', $path)[1])[0]; | |
if (!file_exists($path)){ | |
throw new InvalidPathException('Given path does not exist: path=' . $path); | |
} | |
$recursiveIteratorIterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path)); | |
$useStatements = []; | |
$filesInContainers = []; | |
foreach ($recursiveIteratorIterator as $file) | |
if (!$file->isDir()) { | |
$apiatoCalls = $this->getContainerFromApiatoCall($file->getPathName()); | |
$imports = $this->getContainerFromUseStatement($file->getPathName()); | |
if (isset($apiatoCalls['containers'])) { | |
if ($filterOwnContainer){ | |
$apiatoCalls['containers'] = array_diff($apiatoCalls['containers'], [$ownContainerName]); | |
} | |
foreach ($apiatoCalls['containers'] as $container) { | |
$filesInContainers['apiatoCalls'][$container][] = $file->getPathName(); | |
} | |
} | |
if (isset($imports['containers'])) { | |
if ($filterOwnContainer){ | |
$imports['containers'] = array_diff($imports['containers'], [$ownContainerName]); | |
} | |
foreach ($imports['containers'] as $container) { | |
$filesInContainers['imports'][$container][] = $file->getPathName(); | |
} | |
} | |
} | |
return $filesInContainers; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment