silverleague /
silverstripe-console
This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
| 1 | <?php |
||
| 2 | |||
| 3 | namespace SilverLeague\Console\Command\Object; |
||
| 4 | |||
| 5 | use SilverLeague\Console\Command\SilverStripeCommand; |
||
| 6 | use SilverStripe\ORM\DataObject; |
||
| 7 | use Symfony\Component\Console\Helper\Table; |
||
| 8 | use Symfony\Component\Console\Input\InputArgument; |
||
| 9 | use Symfony\Component\Console\Input\InputInterface; |
||
| 10 | use Symfony\Component\Console\Input\InputOption; |
||
| 11 | use Symfony\Component\Console\Output\OutputInterface; |
||
| 12 | use Symfony\Component\Console\Question\ChoiceQuestion; |
||
| 13 | use Symfony\Component\Console\Question\Question; |
||
| 14 | |||
| 15 | /** |
||
| 16 | * Outputs a formatted data representation of a DataObject's values in either JSON or a table. |
||
| 17 | * |
||
| 18 | * The class name is required, but the ID is optional. If left blank an interactive search-by-column will be given |
||
| 19 | * for all of the object's columns. |
||
| 20 | * |
||
| 21 | * @package silverstripe-console |
||
| 22 | * @author Robbie Averill <[email protected]> |
||
| 23 | */ |
||
| 24 | class DebugCommand extends SilverStripeCommand |
||
| 25 | { |
||
| 26 | /** |
||
| 27 | * {@inheritDoc} |
||
| 28 | */ |
||
| 29 | protected function configure() |
||
| 30 | { |
||
| 31 | $this |
||
| 32 | ->setName('object:debug') |
||
| 33 | ->setDescription('Outputs a visual representation of a DataObject') |
||
| 34 | ->addArgument('object', InputArgument::REQUIRED, 'DataObject class name') |
||
| 35 | ->addArgument('id', InputArgument::OPTIONAL, 'The ID, or field to search') |
||
| 36 | ->addOption('no-sort', null, InputOption::VALUE_NONE, 'Do not sort the output') |
||
| 37 | ->addOption('output-table', null, InputOption::VALUE_NONE, 'Output in a table'); |
||
| 38 | |||
| 39 | $this->setHelp( |
||
| 40 | <<<TEXT |
||
| 41 | Look up a DataObject by class name and either its ID or an interactive search-by-column. |
||
| 42 | |||
| 43 | If no ID is provided then an interactive prompt will ask for the column to search by, then autocomplete all available |
||
| 44 | values for that class's columns to choose from. |
||
| 45 | |||
| 46 | The default output format is JSON. You can add the --output-table option to output the results in a table instead. |
||
| 47 | |||
| 48 | By default the output will also be sorted by key. To prevent this, add the --no-sort option. |
||
| 49 | TEXT |
||
| 50 | ); |
||
| 51 | } |
||
| 52 | |||
| 53 | protected function execute(InputInterface $input, OutputInterface $output) |
||
| 54 | { |
||
| 55 | $objectClass = $input->getArgument('object'); |
||
| 56 | if (!class_exists($objectClass) || !is_subclass_of($objectClass, DataObject::class)) { |
||
|
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||
| 57 | $output->writeln('<error>' . $objectClass . ' does not exist, or is not a DataObject.</error>'); |
||
| 58 | return; |
||
| 59 | } |
||
| 60 | |||
| 61 | $id = $this->getId($input, $output, $objectClass); |
||
| 62 | $data = $this->getData($input, $objectClass, $id); |
||
|
0 ignored issues
–
show
It seems like
$objectClass defined by $input->getArgument('object') on line 55 can also be of type array<integer,string> or null; however, SilverLeague\Console\Com...DebugCommand::getData() does only seem to accept string, maybe add an additional type check?
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check: /**
* @return array|string
*/
function returnsDifferentValues($x) {
if ($x) {
return 'foo';
}
return array();
}
$x = returnsDifferentValues($y);
if (is_array($x)) {
// $x is an array.
}
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue. Loading history...
|
|||
| 63 | // Remove passwords or salts |
||
| 64 | $data = array_diff_key($data, array_flip(['Password', 'Salt'])); |
||
| 65 | |||
| 66 | if (!$data) { |
||
|
0 ignored issues
–
show
The expression
$data of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent. Consider making the comparison explicit by using Loading history...
|
|||
| 67 | $output->writeln('<error>' . $objectClass . ' with ID ' . $id . ' was not found.</error>'); |
||
| 68 | return; |
||
| 69 | } |
||
| 70 | |||
| 71 | $output->writeln( |
||
| 72 | [ |
||
| 73 | '<info>Object: ' . $objectClass . '</info>', |
||
| 74 | '<info>ID: ' . $id . '</info>', |
||
| 75 | '' |
||
| 76 | ] |
||
| 77 | ); |
||
| 78 | |||
| 79 | $asTable = $input->getOption('output-table'); |
||
| 80 | $this->output($output, $data, $asTable); |
||
| 81 | } |
||
| 82 | |||
| 83 | /** |
||
| 84 | * Get the "id" argument either from that provided, or trigger the interactive lookup |
||
| 85 | * |
||
| 86 | * @param InputInterface $input |
||
| 87 | * @param OutputInterface $output |
||
| 88 | * @return int |
||
| 89 | */ |
||
| 90 | protected function getId(InputInterface $input, OutputInterface $output, $objectClass) |
||
| 91 | { |
||
| 92 | $id = $input->getArgument('id'); |
||
| 93 | if (!$id || !is_numeric($id)) { |
||
| 94 | $id = $this->askInteractively($input, $output, $objectClass); |
||
| 95 | } |
||
| 96 | return $id; |
||
| 97 | } |
||
| 98 | |||
| 99 | /** |
||
| 100 | * Load the object by the given ID and return an optionally sorted array of its data |
||
| 101 | * |
||
| 102 | * @param InputInterface $input |
||
| 103 | * @param string $objectClass |
||
| 104 | * @param int $id |
||
| 105 | * @return array |
||
| 106 | */ |
||
| 107 | protected function getData(InputInterface $input, $objectClass, $id) |
||
| 108 | { |
||
| 109 | $data = $objectClass::get()->byId($id); |
||
| 110 | if (!$data || !($data instanceof DataObject)) { |
||
|
0 ignored issues
–
show
The class
SilverStripe\ORM\DataObject does not exist. Did you forget a USE statement, or did you not list all dependencies?
This error could be the result of: 1. Missing dependenciesPHP Analyzer uses your Are you sure this class is defined by one of your dependencies, or did you maybe
not list a dependency in either the 2. Missing use statementPHP does not complain about undefined classes in if ($x instanceof DoesNotExist) {
// Do something.
}
If you have not tested against this specific condition, such errors might go unnoticed. Loading history...
|
|||
| 111 | return []; |
||
| 112 | } |
||
| 113 | $data = $data->toMap(); |
||
| 114 | |||
| 115 | $this->sanitizeResults($data); |
||
| 116 | if (!$input->getOption('no-sort')) { |
||
| 117 | ksort($data); |
||
| 118 | } |
||
| 119 | return $data; |
||
| 120 | } |
||
| 121 | |||
| 122 | /** |
||
| 123 | * Find the DataObject entity to retrieve and return its ID. This method works by first asking to select |
||
| 124 | * one of the object's data columns to filter with, then asking again with an autocompletion on that column |
||
| 125 | * to assist with finding the entity you want to return. |
||
| 126 | * |
||
| 127 | * @param InputInterface $input |
||
| 128 | * @param OutputInterface $output |
||
| 129 | * @param string $objectClass |
||
| 130 | * @return int |
||
| 131 | */ |
||
| 132 | protected function askInteractively(InputInterface $input, OutputInterface $output, $objectClass) |
||
| 133 | { |
||
| 134 | $choices = $objectClass::get()->toArray(); |
||
|
0 ignored issues
–
show
$choices is not used, you could remove the assignment.
This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently. $myVar = 'Value';
$higher = false;
if (rand(1, 6) > 3) {
$higher = true;
} else {
$higher = false;
}
Both the Loading history...
|
|||
| 135 | $class = singleton($objectClass); |
||
| 136 | $columns = array_keys($objectClass::getSchema()->databaseFields($objectClass)); |
||
| 137 | $this->sanitizeResults($columns); |
||
| 138 | |||
| 139 | $question = new ChoiceQuestion('Choose a column to search by:', $columns); |
||
| 140 | $column = $this->getHelper('question')->ask($input, $output, $question); |
||
|
0 ignored issues
–
show
It seems like you code against a concrete implementation and not the interface
Symfony\Component\Console\Helper\HelperInterface as the method ask() does only exist in the following implementations of said interface: Symfony\Component\Console\Helper\QuestionHelper, Symfony\Component\Consol...r\SymfonyQuestionHelper.
Let’s take a look at an example: interface User
{
/** @return string */
public function getPassword();
}
class MyUser implements User
{
public function getPassword()
{
// return something
}
public function getDisplayName()
{
// return some name.
}
}
class AuthSystem
{
public function authenticate(User $user)
{
$this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
// do something.
}
}
In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break. Available Fixes
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types
inside the if block in such a case.
Loading history...
|
|||
| 141 | |||
| 142 | $entities = $objectClass::get()->map('ID', $column)->toArray(); |
||
| 143 | $this->sanitizeResults($entities, true); |
||
| 144 | |||
| 145 | $question = new Question('Look up ' . $class->i18n_singular_name() . ' by ' . $column . ': '); |
||
| 146 | $question->setAutocompleterValues($entities); |
||
| 147 | $entity = $this->getHelper('question')->ask($input, $output, $question); |
||
|
0 ignored issues
–
show
It seems like you code against a concrete implementation and not the interface
Symfony\Component\Console\Helper\HelperInterface as the method ask() does only exist in the following implementations of said interface: Symfony\Component\Console\Helper\QuestionHelper, Symfony\Component\Consol...r\SymfonyQuestionHelper.
Let’s take a look at an example: interface User
{
/** @return string */
public function getPassword();
}
class MyUser implements User
{
public function getPassword()
{
// return something
}
public function getDisplayName()
{
// return some name.
}
}
class AuthSystem
{
public function authenticate(User $user)
{
$this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
// do something.
}
}
In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break. Available Fixes
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types
inside the if block in such a case.
Loading history...
|
|||
| 148 | |||
| 149 | preg_match('/\[#(?<id>\d+)\]$/', $entity, $matches); |
||
| 150 | if (!empty($matches['id'])) { |
||
| 151 | return $matches['id']; |
||
| 152 | } |
||
| 153 | |||
| 154 | // Try and look up the column and value instead |
||
| 155 | $object = $objectClass::get()->filter($column, $entity)->first(); |
||
| 156 | if ($object) { |
||
| 157 | return $object->ID; |
||
| 158 | } |
||
| 159 | |||
| 160 | return 0; |
||
| 161 | } |
||
| 162 | |||
| 163 | /** |
||
| 164 | * Remove array entries that might be passwords, and add the ID to the end of the value for when autocompleting |
||
| 165 | * |
||
| 166 | * @param array &$results The data array, or array of column names |
||
| 167 | * @param bool $addId Whether to add the ID to the value for display purposes |
||
| 168 | * @return $this |
||
| 169 | */ |
||
| 170 | protected function sanitizeResults(&$results, $addId = false) |
||
| 171 | { |
||
| 172 | foreach ($results as $key => $value) { |
||
| 173 | if (stripos($value, 'Password') !== false || stripos($key, 'Password') !== false) { |
||
| 174 | unset($results[$key]); |
||
| 175 | } |
||
| 176 | if ($addId) { |
||
| 177 | $results[$key] .= ' [#' . $key . ']'; |
||
| 178 | } |
||
| 179 | } |
||
| 180 | |||
| 181 | return $this; |
||
| 182 | } |
||
| 183 | |||
| 184 | /** |
||
| 185 | * Output the results to the console in either JSON format or in a table |
||
| 186 | * |
||
| 187 | * @param OutputInterface $output |
||
| 188 | * @param array $data |
||
| 189 | * @param bool $asTable |
||
| 190 | * @return $this |
||
| 191 | */ |
||
| 192 | protected function output(OutputInterface $output, $data, $asTable = false) |
||
| 193 | { |
||
| 194 | if (!$asTable) { |
||
| 195 | return $output->writeln(json_encode($data, JSON_PRETTY_PRINT)); |
||
| 196 | } |
||
| 197 | |||
| 198 | array_walk($data, function (&$value, $key) { |
||
| 199 | $value = [$key, $value]; |
||
| 200 | }); |
||
| 201 | |||
| 202 | $table = new Table($output); |
||
| 203 | $table |
||
| 204 | ->setHeaders(['Key', 'Value']) |
||
| 205 | ->setRows($data) |
||
| 206 | ->render(); |
||
| 207 | |||
| 208 | return $this; |
||
| 209 | } |
||
| 210 | } |
||
| 211 |