PhpStorm v2017.3.4 & Codeception v2.4.0 incompatibility - phpstorm

I'm struggling to make codeception work with PhpStorm.
I have setup CLI Interpreter:
I have also setup Codeception:
And here is the configuration of Codeception:
And when I run the tests from PhpStorm I receive the following:
On the left:
and the text on the right:
Testing started at 00:06 ...
/Applications/MAMP/bin/php/php7.2.1/bin/php /private/var/folders/3g/xng_tnzj6797frqqk20r69mw0000gn/T/ide-codeception.php run --report -o "reporters: report: PhpStorm_Codeception_ReportPrinter" --no-ansi --no-interaction unit
Process finished with exit code 255
but when I run concept run inside my project folder on the terminal, it works fine and I receive this:
What am I doing wrong? I really read the manual on PhpStorm here and videos on youtube, but I can't make it work :/
Thanks!
UPDATE:
On further investigation, I found out the following:
$ /usr/bin/env php --version
PHP 7.2.1 (cli) (built: Jan 15 2018 12:20:50) ( NTS )
Copyright (c) 1997-2017 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2017 Zend Technologies
$ php --version
PHP 7.2.1 (cli) (built: Jan 15 2018 12:20:50) ( NTS )
Copyright (c) 1997-2017 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2017 Zend Technologies
with Zend OPcache v7.2.1, Copyright (c) 1999-2017, by Zend Technologies
with Xdebug v2.6.0beta1, Copyright (c) 2002-2017, by Derick Rethans
$ /usr/bin/env php -c "/Library/Application Support/appsolute/MAMP PRO/conf/php7.2.1.ini" --version
PHP 7.2.1 (cli) (built: Jan 15 2018 12:20:50) ( NTS )
Copyright (c) 1997-2017 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2017 Zend Technologies
with Zend OPcache v7.2.1, Copyright (c) 1999-2017, by Zend Technologies
with Xdebug v2.6.0beta1, Copyright (c) 2002-2017, by Derick Rethans
$ cat .zprofile | grep "alias php="
alias php='/Applications/MAMP/bin/php/php7.2.1/bin/php -c "/Library/Application Support/appsolute/MAMP PRO/conf/php7.2.1.ini"'
UPDATE 2:
While this solved the issue I had with codeception when I was running with code coverage flag --coverage on terminal:
$ which php
php: aliased to /Applications/MAMP/bin/php/php7.2.1/bin/php -c '/Library/Application Support/appsolute/MAMP PRO/conf/php7.2.1.ini'
$ /usr/bin/env php -i | grep "Loaded Configuration File"
Loaded Configuration File => /Applications/MAMP/bin/php/php7.2.1/conf/php.ini
$ mv /Applications/MAMP/bin/php/php7.2.1/conf/php.ini /Applications/MAMP/bin/php/php7.2.1/conf/php.bk.ini
$ ln -s "/Library/Application Support/appsolute/MAMP PRO/conf/php7.2.1.ini" /Applications/MAMP/bin/php/php7.2.1/conf/php.ini
$ /usr/bin/env php --version
PHP 7.2.1 (cli) (built: Jan 15 2018 12:20:50) ( NTS )
Copyright (c) 1997-2017 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2017 Zend Technologies
with Zend OPcache v7.2.1, Copyright (c) 1999-2017, by Zend Technologies
with Xdebug v2.6.0beta1, Copyright (c) 2002-2017, by Derick Rethans
Now I receive the following message on PhpStorm:
Testing started at 15:55 ...
/Applications/MAMP/bin/php/php7.2.1/bin/php /private/var/folders/3g/xng_tnzj6797frqqk20r69mw0000gn/T/ide-codeception.php run --report -o "reporters: report: PhpStorm_Codeception_ReportPrinter" --no-ansi --no-interaction
Fatal error: Class 'PHPUnit_TextUI_ResultPrinter' not found in /private/var/folders/3g/xng_tnzj6797frqqk20r69mw0000gn/T/ide-codeception.php on line 22
Call Stack:
0.0222 528280 1. {main}() /private/var/folders/3g/xng_tnzj6797frqqk20r69mw0000gn/T/ide-codeception.php:0
Process finished with exit code 255
and this is the part of "require-dev" inside my composer.json file
"require-dev": {
"codeception/codeception": "^2.4"
},
based on Fatal error: Class 'PHPUnit_TextUI_ResultPrinter' not found and by seeing the classname PHPUnit_TextUI_ResultPrinter instead of PHPUnit\TextUI\ResultPrinter at that point, I suspect that the phpunit version that codeception 2.4 uses is newer than the one required by PhpStorm? Maybe phpunit v6 is required?
Thanks!
UPDATE 3:
I can now confirm that PhpStorm v2017.3.4 is not compatible with Codeception 2.4 because the later, after v2.4.0 moved to PHPUnit v7.x which PhpStorm v2017.3.4 seems to not be compatible yet. After running:
$ composer remove codeception/codeception
$ composer require codeception/codeception:2.3.9 --dev
I got the following on PhpStorm:

So after more research, I fixed my issue.
simple steps:
$ cd /Applications/PhpStorm.app/Contents/plugins/codeception/lib
$ ls
codeception.jar resources_en.jar
$ mv codeception.jar codeception.zip
$ unzip codeception.zip -d codeception
$ rm codeception.zip
$ cd codeception
$ vim scripts/codeception.php
$ zip -r ../codeception.zip *
$ cd ..
$ mv codeception.zip codeception.jar
$ rm -r codeception/
instead of vim scripts/codeception.php you can use any other text editor you like and replace the contents with the following:
<?php
if (!isset($_SERVER['IDE_CODECEPTION_EXE'])) {
fwrite(STDERR, "The value of Codeception executable is not specified" . PHP_EOL);
exit(1);
}
$exe = realpath($_SERVER['IDE_CODECEPTION_EXE']);
if (!file_exists($exe)) {
$originalPath = $_SERVER['IDE_CODECEPTION_EXE'];
fwrite(STDERR, "The value of Codeception executable is specified, but file doesn't exist '$originalPath'" . PHP_EOL);
exit(1);
}
if (Phar::isValidPharFilename(basename($exe), true)) {
require_once 'phar://' . $exe . '/autoload.php';
}
else {
require_once dirname($exe) .'/autoload.php';
}
class PhpStorm_Codeception_ReportPrinter extends \PHPUnit\TextUI\ResultPrinter
{
protected $testStatus = \PHPUnit\Runner\BaseTestRunner::STATUS_PASSED;
protected $failures = [];
/**
* #var bool
*/
private $isSummaryTestCountPrinted = false;
/**
* #var string
*/
private $startedTestName;
/**
* #var string
*/
private $flowId;
/**
* #param string $progress
*/
protected function writeProgress($progress): void
{
}
/**
* #param \PHPUnit\Framework\TestResult $result
*/
public function printResult(\PHPUnit\Framework\TestResult $result) : void
{
$this->printHeader();
$this->printFooter($result);
}
/**
* An error occurred.
*
* #param \PHPUnit\Framework\Test $test
* #param \Throwable $e
* #param float $time
*/
public function addError(\PHPUnit\Framework\Test $test, \Throwable $e, float $time): void
{
$this->addFail(\PHPUnit\Runner\BaseTestRunner::STATUS_ERROR, $test, $e);
}
/**
* A warning occurred.
*
* #param \PHPUnit\Framework\Test $test
* #param \PHPUnit\Framework\Warning $e
* #param float $time
*
* #since Method available since Release 5.1.0
*/
public function addWarning(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\Warning $e, float $time): void
{
$this->addFail(\PHPUnit\Runner\BaseTestRunner::STATUS_ERROR, $test, $e);
}
/**
* A failure occurred.
*
* #param \PHPUnit\Framework\Test $test
* #param \PHPUnit\Framework\AssertionFailedError $e
* #param float $time
*/
public function addFailure(\PHPUnit\Framework\Test $test, \PHPUnit\Framework\AssertionFailedError $e, float $time): void
{
$parameters = [];
if ($e instanceof \PHPUnit\Framework\ExpectationFailedException) {
$comparisonFailure = $e->getComparisonFailure();
if ($comparisonFailure instanceof \SebastianBergmann\Comparator\ComparisonFailure) {
$expectedString = $comparisonFailure->getExpectedAsString();
if (is_null($expectedString) || empty($expectedString)) {
$expectedString = self::getPrimitiveValueAsString($comparisonFailure->getExpected());
}
$actualString = $comparisonFailure->getActualAsString();
if (is_null($actualString) || empty($actualString)) {
$actualString = self::getPrimitiveValueAsString($comparisonFailure->getActual());
}
if (!is_null($actualString) && !is_null($expectedString)) {
$parameters['type'] = 'comparisonFailure';
$parameters['actual'] = $actualString;
$parameters['expected'] = $expectedString;
}
}
}
$this->addFail(\PHPUnit\Runner\BaseTestRunner::STATUS_ERROR, $test, $e, $parameters);
}
/**
* Incomplete test.
*
* #param \PHPUnit\Framework\Test $test
* #param \Throwable $e
* #param float $time
*/
public function addIncompleteTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time): void
{
$this->addIgnoredTest($test, $e);
}
/**
* Risky test.
*
* #param \PHPUnit\Framework\Test $test
* #param \Throwable $e
* #param float $time
*/
public function addRiskyTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time): void
{
$this->addError($test, $e, $time);
}
/**
* Skipped test.
*
* #param \PHPUnit\Framework\Test $test
* #param \Throwable $e
* #param float $time
*/
public function addSkippedTest(\PHPUnit\Framework\Test $test, \Throwable $e, float $time): void
{
$testName = self::getTestAsString($test);
if ($this->startedTestName != $testName) {
$this->startTest($test);
$this->printEvent(
'testIgnored',
[
'name' => $testName,
'message' => self::getMessage($e),
'details' => self::getDetails($e),
]
);
$this->endTest($test, $time);
} else {
$this->addIgnoredTest($test, $e);
}
}
public function addIgnoredTest(\PHPUnit\Framework\Test $test, Exception $e) {
$this->addFail(\PHPUnit\Runner\BaseTestRunner::STATUS_SKIPPED, $test, $e);
}
private function addFail($status, \PHPUnit\Framework\Test $test, $e, $parameters = []) {
$key = self::getTestSignature($test);
$this->testStatus = $status;
$parameters['message'] = self::getMessage($e);
$parameters['details'] = self::getDetails($e);
$this->failures[$key][] = $parameters;
}
/**
* A testsuite started.
*
* #param \PHPUnit\Framework\TestSuite $suite
*/
public function startTestSuite(\PHPUnit\Framework\TestSuite $suite): void
{
if (stripos(ini_get('disable_functions'), 'getmypid') === false) {
$this->flowId = getmypid();
} else {
$this->flowId = false;
}
if (!$this->isSummaryTestCountPrinted) {
$this->isSummaryTestCountPrinted = true;
$this->printEvent(
'testCount',
['count' => count($suite)]
);
}
$suiteName = $suite->getName();
if (empty($suiteName)) {
return;
}
//TODO: configure 'locationHint' to navigate to 'unit', 'acceptance', 'functional' test suite
//TODO: configure 'locationHint' to navigate to DataProvider tests for Codeception earlier 2.2.6
$parameters = ['name' => $suiteName];
$this->printEvent('testSuiteStarted', $parameters);
}
/**
* A testsuite ended.
*
* #param \PHPUnit\Framework\TestSuite $suite
*/
public function endTestSuite(\PHPUnit\Framework\TestSuite $suite): void
{
$suiteName = $suite->getName();
if (empty($suiteName)) {
return;
}
$parameters = ['name' => $suiteName];
$this->printEvent('testSuiteFinished', $parameters);
}
public static function getTestSignature(\PHPUnit\Framework\SelfDescribing $testCase)
{
if ($testCase instanceof Codeception\Test\Interfaces\Descriptive) {
return $testCase->getSignature();
}
if ($testCase instanceof \PHPUnit\Framework\TestCase) {
return get_class($testCase) . ':' . $testCase->getName(false);
}
return $testCase->toString();
}
public static function getTestAsString(\PHPUnit\Framework\SelfDescribing $testCase)
{
if ($testCase instanceof Codeception\Test\Interfaces\Descriptive) {
return $testCase->toString();
}
if ($testCase instanceof \PHPUnit\Framework\TestCase) {
$text = $testCase->getName();
$text = preg_replace('/([A-Z]+)([A-Z][a-z])/', '\\1 \\2', $text);
$text = preg_replace('/([a-z\d])([A-Z])/', '\\1 \\2', $text);
$text = preg_replace('/^test /', '', $text);
$text = ucfirst(strtolower($text));
$text = str_replace(['::', 'with data set'], [':', '|'], $text);
return Codeception\Util\ReflectionHelper::getClassShortName($testCase) . ': ' . $text;
}
return $testCase->toString();
}
public static function getTestFileName(\PHPUnit\Framework\SelfDescribing $testCase)
{
if ($testCase instanceof Codeception\Test\Interfaces\Descriptive) {
return $testCase->getFileName();
}
return (new \ReflectionClass($testCase))->getFileName();
}
public static function getTestFullName(\PHPUnit\Framework\SelfDescribing $testCase)
{
if ($testCase instanceof Codeception\Test\Interfaces\Plain) {
return self::getTestFileName($testCase);
}
if ($testCase instanceof Codeception\Test\Interfaces\Descriptive) {
$signature = $testCase->getSignature(); // cut everything before ":" from signature
return self::getTestFileName($testCase) . '::' . preg_replace('~^(.*?):~', '', $signature);
}
if ($testCase instanceof \PHPUnit\Framework\TestCase) {
return self::getTestFileName($testCase) . '::' . $testCase->getName(false);
}
return self::getTestFileName($testCase) . '::' . $testCase->toString();
}
/**
* A test started.
*
* #param \PHPUnit\Framework\Test $test
*/
public function startTest(\PHPUnit\Framework\Test $test): void
{
$testName = self::getTestAsString($test);
$this->startedTestName = $testName;
$location = "php_qn://" . self::getTestFullName($test);
$gherkin = self::getGherkinTestLocation($test);
if ($gherkin != null) {
$location = $gherkin;
}
$params = ['name' => $testName, 'locationHint' => $location];
if ($test instanceof \Codeception\Test\Interfaces\ScenarioDriven) {
$this->printEvent('testSuiteStarted', $params);
}
else {
$this->printEvent('testStarted', $params);
}
}
/**
* A test ended.
*
* #param \PHPUnit\Framework\Test $test
* #param float $time
*/
public function endTest(\PHPUnit\Framework\Test $test, float $time): void
{
$result = null;
switch ($this->testStatus) {
case \PHPUnit\Runner\BaseTestRunner::STATUS_ERROR:
case \PHPUnit\Runner\BaseTestRunner::STATUS_FAILURE:
$result = 'testFailed';
break;
case \PHPUnit\Runner\BaseTestRunner::STATUS_SKIPPED:
$result = 'testIgnored';
break;
}
$name = self::getTestAsString($test);
if ($this->startedTestName != $name) {
$name = $this->startedTestName;
}
$gherkin = self::getGherkinTestLocation($test);
$duration = (int)(round($time, 2) * 1000);
if ($test instanceof \Codeception\Test\Interfaces\ScenarioDriven) {
$steps = $test->getScenario()->getSteps();
$len = sizeof($steps);
$printed = 0;
for ($i = 0; $i < $len; $i++) {
$step = $steps[$i];
if ($step->getAction() == null && $step->getMetaStep()) {
$step = $step->getMetaStep();
}
if ($step instanceof \Codeception\Step\Comment) {
// TODO: render comments in grey color?
// comments are not shown because at the moment it's hard to distinguish them from real tests.
// e.g. comment steps show descriptions from *.feature tests.
continue;
}
$printed++;
$testName = sprintf('%s %s %s',
ucfirst($step->getPrefix()),
$step->getHumanizedActionWithoutArguments(),
$step->getHumanizedArguments()
);
$location = $gherkin != null ? $gherkin : $step->getLine();
$this->printEvent('testStarted',
[
'name' => $testName,
'locationHint' => "file://$location"
]);
$params = ['name' => $testName];
if ($i == $len - 1) {
parent::endTest($test, $time);
$this->printError($test, $result, $testName);
$params['duration'] = $duration;
}
$this->printEvent('testFinished', $params);
}
if ($printed == 0 && $result != null) {
$this->printEvent('testStarted', ['name' => $name]);
parent::endTest($test, $time);
$this->printError($test, $result, $name);
$this->printEvent('testFinished', [
'name' => $name,
'duration' => $duration
]);
}
$this->printEvent('testSuiteFinished', ['name' => $name]);
}
else {
parent::endTest($test, $time);
$this->printError($test, $result, self::getTestAsString($test));
$this->printEvent(
'testFinished',
[
'name' => self::getTestAsString($test),
'duration' => $duration
]
);
}
}
private function printError(\PHPUnit\Framework\Test $test, $result, $name) {
if ($result != null) {
$this->testStatus = \PHPUnit\Runner\BaseTestRunner::STATUS_PASSED;
$key = self::getTestSignature($test);
if (isset($this->failures[$key])) {
$failures = $this->failures[$key];
//TODO: check if it's possible to have sizeof($params) > 1
assert(sizeof($failures) == 1);
$params = $failures[0];
$params['name'] = $name;
$this->printEvent($result, $params);
unset($this->failures[$key]);
}
}
}
/**
* #param string $eventName
* #param array $params
*/
private function printEvent($eventName, $params = [])
{
$this->write("\n##teamcity[$eventName");
if ($this->flowId) {
$params['flowId'] = $this->flowId;
}
foreach ($params as $key => $value) {
$escapedValue = self::escapeValue($value);
$this->write(" $key='$escapedValue'");
}
$this->write("]\n");
}
private static function getGherkinTestLocation(\PHPUnit\Framework\Test $test) {
if ($test instanceof \Codeception\Test\Gherkin) {
$feature = $test->getFeatureNode();
$scenario = $test->getScenarioNode();
if ($feature != null && $scenario != null) {
return "file://" . $test->getFeatureNode()->getFile() . ":" . $test->getScenarioNode()->getLine();
}
}
return null;
}
/**
* #param Exception $e
*
* #return string
*/
private static function getMessage(Exception $e)
{
$message = '';
if (!$e instanceof \PHPUnit\Framework\Exception) {
if (strlen(get_class($e)) != 0) {
$message = $message . get_class($e);
}
if (strlen($message) != 0 && strlen($e->getMessage()) != 0) {
$message = $message . ' : ';
}
}
return $message . $e->getMessage();
}
/**
* #param Exception $e
*
* #return string
*/
private static function getDetails(Exception $e)
{
$stackTrace = \PHPUnit\Util\Filter::getFilteredStacktrace($e);
$previous = $e->getPrevious();
while ($previous) {
$stackTrace .= "\nCaused by\n" .
\PHPUnit\Framework\TestFailure::exceptionToString($previous) . "\n" .
\PHPUnit\Util\Filter::getFilteredStacktrace($previous);
$previous = $previous->getPrevious();
}
return ' ' . str_replace("\n", "\n ", $stackTrace);
}
/**
* #param mixed $value
*
* #return string
*/
private static function getPrimitiveValueAsString($value)
{
if (is_null($value)) {
return 'null';
} elseif (is_bool($value)) {
return $value == true ? 'true' : 'false';
} elseif (is_scalar($value)) {
return print_r($value, true);
}
return;
}
/**
* #param $text
*
* #return string
*/
private static function escapeValue($text)
{
$text = str_replace('|', '||', $text);
$text = str_replace("'", "|'", $text);
$text = str_replace("\n", '|n', $text);
$text = str_replace("\r", '|r', $text);
$text = str_replace(']', '|]', $text);
$text = str_replace('[', '|[', $text);
return $text;
}
/**
* #param string $className
*
* #return string
*/
private static function getFileName($className)
{
$reflectionClass = new ReflectionClass($className);
$fileName = $reflectionClass->getFileName();
return $fileName;
}
}
$app = new \Codeception\Application('Codeception', \Codeception\Codecept::VERSION);
if (version_compare(\Codeception\Codecept::VERSION, "2.2.6") >= 0) {
$app->add(new \Codeception\Command\Run('run'));
$app->run();
}
else {
class PhpStorm_Codeception_Command_Run extends \Codeception\Command\Run {
public function execute(\Symfony\Component\Console\Input\InputInterface $input,
\Symfony\Component\Console\Output\OutputInterface $output)
{
$this->ensureCurlIsAvailable();
$this->options = $input->getOptions();
$this->output = $output;
$config = \Codeception\Configuration::config($this->options['config']);
if (!$this->options['colors']) {
$this->options['colors'] = $config['settings']['colors'];
}
if (!$this->options['silent']) {
$this->output->writeln(
\Codeception\Codecept::versionString() . "\nPowered by " . \PHPUnit\Runner\Version::getVersionString()
);
}
if ($this->options['debug']) {
$this->output->setVerbosity(\Symfony\Component\Console\Output\OutputInterface::VERBOSITY_VERY_VERBOSE);
}
$userOptions = array_intersect_key($this->options, array_flip($this->passedOptionKeys($input)));
$userOptions = array_merge(
$userOptions,
$this->booleanOptions($input, ['xml', 'html', 'json', 'tap', 'coverage', 'coverage-xml', 'coverage-html'])
);
$userOptions['verbosity'] = $this->output->getVerbosity();
$userOptions['interactive'] = !$input->hasParameterOption(['--no-interaction', '-n']);
$userOptions['ansi'] = (!$input->hasParameterOption('--no-ansi') xor $input->hasParameterOption('ansi'));
if ($this->options['no-colors'] || !$userOptions['ansi']) {
$userOptions['colors'] = false;
}
if ($this->options['group']) {
$userOptions['groups'] = $this->options['group'];
}
if ($this->options['skip-group']) {
$userOptions['excludeGroups'] = $this->options['skip-group'];
}
if ($this->options['report']) {
$userOptions['silent'] = true;
}
if ($this->options['coverage-xml'] or $this->options['coverage-html'] or $this->options['coverage-text']) {
$this->options['coverage'] = true;
}
if (!$userOptions['ansi'] && $input->getOption('colors')) {
$userOptions['colors'] = true; // turn on colors even in non-ansi mode if strictly passed
}
$suite = $input->getArgument('suite');
$test = $input->getArgument('test');
if (! \Codeception\Configuration::isEmpty() && ! $test && strpos($suite, $config['paths']['tests']) === 0) {
list(, $suite, $test) = $this->matchTestFromFilename($suite, $config['paths']['tests']);
}
if ($this->options['group']) {
$this->output->writeln(sprintf("[Groups] <info>%s</info> ", implode(', ', $this->options['group'])));
}
if ($input->getArgument('test')) {
$this->options['steps'] = true;
}
if ($test) {
$filter = $this->matchFilteredTestName($test);
$userOptions['filter'] = $filter;
}
$this->codecept = new PhpStorm_Codeception_Codecept($userOptions);
if ($suite and $test) {
$this->codecept->run($suite, $test);
}
if (!$test) {
$suites = $suite ? explode(',', $suite) : \Codeception\Configuration::suites();
$this->executed = $this->runSuites($suites, $this->options['skip']);
if (!empty($config['include']) and !$suite) {
$current_dir = \Codeception\Configuration::projectDir();
$suites += $config['include'];
$this->runIncludedSuites($config['include'], $current_dir);
}
if ($this->executed === 0) {
throw new \RuntimeException(
sprintf("Suite '%s' could not be found", implode(', ', $suites))
);
}
}
$this->codecept->printResult();
if (!$input->getOption('no-exit')) {
if (!$this->codecept->getResult()->wasSuccessful()) {
exit(1);
}
}
}
private function matchFilteredTestName(&$path)
{
if (version_compare(\Codeception\Codecept::VERSION, "2.2.5") >= 0) {
$test_parts = explode(':', $path, 2);
if (count($test_parts) > 1) {
list($path, $filter) = $test_parts;
// use carat to signify start of string like in normal regex
// phpunit --filter matches against the fully qualified method name, so tests actually begin with :
$carat_pos = strpos($filter, '^');
if ($carat_pos !== false) {
$filter = substr_replace($filter, ':', $carat_pos, 1);
}
return $filter;
}
return null;
}
else {
$test_parts = explode(':', $path);
if (count($test_parts) > 1) {
list($path, $filter) = $test_parts;
return $filter;
}
return null;
}
}
private function ensureCurlIsAvailable()
{
if (!extension_loaded('curl')) {
throw new \Exception(
"Codeception requires CURL extension installed to make tests run\n"
. "If you are not sure, how to install CURL, please refer to StackOverflow\n\n"
. "Notice: PHP for Apache/Nginx and CLI can have different php.ini files.\n"
. "Please make sure that your PHP you run from console has CURL enabled."
);
}
}
}
class PhpStorm_Codeception_Codecept extends \Codeception\Codecept {
public function __construct($options = [])
{
parent::__construct($options);
$printer = new PhpStorm_Codeception_ReportPrinter();
$this->runner = new \Codeception\PHPUnit\Runner();
$this->runner->setPrinter($printer);
}
}
$app->add(new PhpStorm_Codeception_Command_Run('run'));
$app->run();
}
and now it works fine with PHPUnit v7.x and Codeception 2.4.x
UPDATE:
I have uploaded here a jar file that fixes the issue, until PhpStorm fixes it officially, for anyone who don't want to go through the above steps.
UPDATE 2:
Just want to point out that this fix, does not provide backwards compatibility for versions of codeception less than 2.4.0, I'm sure PhpStorm team will provide a more elegant solution that would allow having projects that use codeception 2.4.x and other projects that use earlier verions.

I had same issue as mentioned here. Updating PHPStorm to version 2017.3.6 fixed this (also updated all dependecies for PHPStorm e.g. Symfony plugins PHPUnit plugins etc.)

The error Fatal error: Class 'PHPUnit_TextUI_ResultPrinter' can also happen after upgrading PHPUnit to a newer version.
I had to manually update PHPStorm detected version to fix this issue:

Update PHPSTORM to new version resolved issue

Related

PHP Strict Standards: Declaration of ezSQL_mysql::escape() should be compatible with ezSQLcore::escape() in /home

i have problem about ezSQL_mysql and ezSQLcore, it may be an incompatible version of PHP, I share the code below. what version of Php should I use or what should I do to tailor mysqli? (my php version 5.6 )
codes :
ezSQL_mysql
<?php
/**********************************************************************
* Author: Justin Vincent (jv#jvmultimedia.com)
* Web...: http://twitter.com/justinvincent
* Name..: ezSQL_mysql
* Desc..: mySQL component (part of ezSQL databse abstraction library)
*
*/
/**********************************************************************
* ezSQL error strings - mySQL
*/
$ezsql_mysql_str = array
(
1 => 'Require $dbuser and $dbpassword to connect to a database server',
2 => 'Error establishing mySQL database connection. Correct user/password? Correct hostname? Database server running?',
3 => 'Require $dbname to select a database',
4 => 'mySQL database connection is not active',
5 => 'Unexpected error while trying to select database'
);
/**********************************************************************
* ezSQL Database specific class - mySQL
*/
if ( ! function_exists ('mysql_connect') ) die('<b>Fatal Error:</b> ezSQL_mysql requires mySQL Lib to be compiled and or linked in to the PHP engine');
if ( ! class_exists ('ezSQLcore') ) die('<b>Fatal Error:</b> ezSQL_mysql requires ezSQLcore (ez_sql_core.php) to be included/loaded before it can be used');
class ezSQL_mysql extends ezSQLcore
{
var $dbuser = false;
var $dbpassword = false;
var $dbname = false;
var $dbhost = false;
/**********************************************************************
* Constructor - allow the user to perform a qucik connect at the
* same time as initialising the ezSQL_mysql class
*/
function ezSQL_mysql($dbuser='**', $dbpassword='**', $dbname='**', $dbhost='localhost')
{
$this->dbuser = $dbuser;
$this->dbpassword = $dbpassword;
$this->dbname = $dbname;
$this->dbhost = $dbhost;
}
/**********************************************************************
* Short hand way to connect to mySQL database server
* and select a mySQL database at the same time
*/
function quick_connect($dbuser='**', $dbpassword='**', $dbname='**', $dbhost='localhost')
{
$return_val = false;
if ( ! $this->connect($dbuser, $dbpassword, $dbhost,true) ) ;
else if ( ! $this->select($dbname) ) ;
else $return_val = true;
return $return_val;
}
/**********************************************************************
* Try to connect to mySQL database server
*/
function connect($dbuser='**', $dbpassword='**', $dbhost='localhost')
{
global $ezsql_mysql_str; $return_val = false;
// Must have a user and a password
if ( ! $dbuser )
{
$this->register_error($ezsql_mysql_str[1].' in '.__FILE__.' on line '.__LINE__);
$this->show_errors ? trigger_error($ezsql_mysql_str[1],E_USER_WARNING) : null;
}
// Try to establish the server database handle
else if ( ! $this->dbh = #mysql_connect($dbhost,$dbuser,$dbpassword,true) )
{
$this->register_error($ezsql_mysql_str[2].' in '.__FILE__.' on line '.__LINE__);
$this->show_errors ? trigger_error($ezsql_mysql_str[2],E_USER_WARNING) : null;
}
else
{
$this->dbuser = $dbuser;
$this->dbpassword = $dbpassword;
$this->dbhost = $dbhost;
$return_val = true;
}
return $return_val;
}
/**********************************************************************
* Try to select a mySQL database
*/
function select($dbname='**')
{
global $ezsql_mysql_str; $return_val = false;
// Must have a database name
if ( ! $dbname )
{
$this->register_error($ezsql_mysql_str[3].' in '.__FILE__.' on line '.__LINE__);
$this->show_errors ? trigger_error($ezsql_mysql_str[3],E_USER_WARNING) : null;
}
// Must have an active database connection
else if ( ! $this->dbh )
{
$this->register_error($ezsql_mysql_str[4].' in '.__FILE__.' on line '.__LINE__);
$this->show_errors ? trigger_error($ezsql_mysql_str[4],E_USER_WARNING) : null;
}
// Try to connect to the database
else if ( !#mysql_select_db($dbname,$this->dbh) )
{
// Try to get error supplied by mysql if not use our own
if ( !$str = #mysql_error($this->dbh))
$str = $ezsql_mysql_str[5];
$this->register_error($str.' in '.__FILE__.' on line '.__LINE__);
$this->show_errors ? trigger_error($str,E_USER_WARNING) : null;
}
else
{
include 'sql_for_tr.php';
$this->dbname = $dbname;
$return_val = true;
}
return $return_val;
}
/**********************************************************************
* Format a mySQL string correctly for safe mySQL insert
* (no mater if magic quotes are on or not)
*/
function escape($str)
{
return mysql_escape_string(stripslashes($str));
}
/**********************************************************************
* Return mySQL specific system date syntax
* i.e. Oracle: SYSDATE Mysql: NOW()
*/
function sysdate()
{
return 'NOW()';
}
/**********************************************************************
* Perform mySQL query and try to detirmin result value
*/
function query($query)
{
// Initialise return
$return_val = 0;
// Flush cached values..
$this->flush();
// For reg expressions
$query = trim($query);
// Log how the function was called
$this->func_call = "\$db->query(\"$query\")";
// Keep track of the last query for debug..
$this->last_query = $query;
// Count how many queries there have been
$this->num_queries++;
// Use core file cache function
if ( $cache = $this->get_cache($query) )
{
return $cache;
}
// If there is no existing database connection then try to connect
if ( ! isset($this->dbh) || ! $this->dbh )
{
$this->connect($this->dbuser, $this->dbpassword, $this->dbhost);
$this->select($this->dbname);
}
// Perform the query via std mysql_query function..
$this->result = #mysql_query($query,$this->dbh);
// If there is an error then take note of it..
if ( $str = #mysql_error($this->dbh) )
{
$is_insert = true;
$this->register_error($str);
$this->show_errors ? trigger_error($str,E_USER_WARNING) : null;
return true;
}
// Query was an insert, delete, update, replace
$is_insert = false;
if ( preg_match("/^(insert|delete|update|replace)\s+/i",$query) )
{
$this->rows_affected = #mysql_affected_rows();
// Take note of the insert_id
if ( preg_match("/^(insert|replace)\s+/i",$query) )
{
$this->insert_id = #mysql_insert_id($this->dbh);
}
// Return number fo rows affected
$return_val = $this->rows_affected;
}
// Query was a select
else
{
// Take note of column info
$i=0;
while ($i < #mysql_num_fields($this->result))
{
$this->col_info[$i] = #mysql_fetch_field($this->result);
$i++;
}
// Store Query Results
$num_rows=0;
while ( $row = #mysql_fetch_object($this->result) )
{
// Store relults as an objects within main array
$this->last_result[$num_rows] = $row;
$num_rows++;
}
#mysql_free_result($this->result);
// Log number of rows the query returned
$this->num_rows = $num_rows;
// Return number of rows selected
$return_val = $this->num_rows;
}
// disk caching of queries
$this->store_cache($query,$is_insert);
// If debug ALL queries
$this->trace || $this->debug_all ? $this->debug() : null ;
return $return_val;
}
}
?>
ezSQLcore
<?php
/**********************************************************************
* Author: Justin Vincent (jv#jvmultimedia.com)
* Web...: http://twitter.com/justinvincent
* Name..: ezSQL
* Desc..: ezSQL Core module - database abstraction library to make
* it very easy to deal with databases.
*
*/
/**********************************************************************
* ezSQL Constants
*/
define('EZSQL_VERSION','2.03');
define('OBJECT','OBJECT',true);
define('ARRAY_A','ARRAY_A',true);
define('ARRAY_N','ARRAY_N',true);
define('EZSQL_CORE_ERROR','ezSQLcore can not be used by itself (it is designed for use by database specific modules).');
/**********************************************************************
* Core class containg common functions to manipulate query result
* sets once returned
*/
class ezSQLcore
{
var $trace = false; // same as $debug_all
var $debug_all = false; // same as $trace
var $debug_called = false;
var $vardump_called = false;
var $show_errors = true;
var $num_queries = 0;
var $last_query = null;
var $last_error = null;
var $col_info = null;
var $captured_errors = array();
var $cache_dir = false;
var $cache_queries = false;
var $cache_inserts = false;
var $use_disk_cache = false;
var $cache_timeout = 24; // hours
// == TJH == default now needed for echo of debug function
var $debug_echo_is_on = true;
/**********************************************************************
* Constructor
*/
function ezSQLcore()
{
}
/**********************************************************************
* Connect to DB - over-ridden by specific DB class
*/
function connect()
{
die(EZSQL_CORE_ERROR);
}
/**********************************************************************
* Select DB - over-ridden by specific DB class
*/
function select()
{
die(EZSQL_CORE_ERROR);
}
/**********************************************************************
* Basic Query - over-ridden by specific DB class
*/
function query()
{
die(EZSQL_CORE_ERROR);
}
/**********************************************************************
* Format a string correctly for safe insert - over-ridden by specific
* DB class
*/
function escape()
{
die(EZSQL_CORE_ERROR);
}
/**********************************************************************
* Return database specific system date syntax
* i.e. Oracle: SYSDATE Mysql: NOW()
*/
function sysdate()
{
die(EZSQL_CORE_ERROR);
}
/**********************************************************************
* Print SQL/DB error - over-ridden by specific DB class
*/
function register_error($err_str)
{
// Keep track of last error
$this->last_error = $err_str;
// Capture all errors to an error array no matter what happens
$this->captured_errors[] = array
(
'error_str' => $err_str,
'query' => $this->last_query
);
}
/**********************************************************************
* Turn error handling on or off..
*/
function show_errors()
{
$this->show_errors = true;
}
function hide_errors()
{
$this->show_errors = false;
}
/**********************************************************************
* Kill cached query results
*/
function flush()
{
// Get rid of these
$this->last_result = null;
$this->col_info = null;
$this->last_query = null;
$this->from_disk_cache = false;
}
/**********************************************************************
* Get one variable from the DB - see docs for more detail
*/
function get_var($query=null,$x=0,$y=0)
{
// Log how the function was called
$this->func_call = "\$db->get_var(\"$query\",$x,$y)";
// If there is a query then perform it if not then use cached results..
if ( $query )
{
$this->query($query);
}
// Extract var out of cached results based x,y vals
if ( $this->last_result[$y] )
{
$values = array_values(get_object_vars($this->last_result[$y]));
}
// If there is a value return it else return null
return (isset($values[$x]) && $values[$x]!=='')?$values[$x]:null;
}
/**********************************************************************
* Get one row from the DB - see docs for more detail
*/
function get_row($query=null,$output=OBJECT,$y=0)
{
// Log how the function was called
$this->func_call = "\$db->get_row(\"$query\",$output,$y)";
// If there is a query then perform it if not then use cached results..
if ( $query )
{
$this->query($query);
}
// If the output is an object then return object using the row offset..
if ( $output == OBJECT )
{
return $this->last_result[$y]?$this->last_result[$y]:null;
}
// If the output is an associative array then return row as such..
elseif ( $output == ARRAY_A )
{
return $this->last_result[$y]?get_object_vars($this->last_result[$y]):null;
}
// If the output is an numerical array then return row as such..
elseif ( $output == ARRAY_N )
{
return $this->last_result[$y]?array_values(get_object_vars($this->last_result[$y])):null;
}
// If invalid output type was specified..
else
{
$this->print_error(" \$db->get_row(string query, output type, int offset) -- Output type must be one of: OBJECT, ARRAY_A, ARRAY_N");
}
}
/**********************************************************************
* Function to get 1 column from the cached result set based in X index
* see docs for usage and info
*/
function get_col($query=null,$x=0)
{
// If there is a query then perform it if not then use cached results..
if ( $query )
{
$this->query($query);
}
// Extract the column values
for ( $i=0; $i < count($this->last_result); $i++ )
{
$new_array[$i] = $this->get_var(null,$x,$i);
}
return $new_array;
}
/**********************************************************************
* Return the the query as a result set - see docs for more details
*/
function get_results($query=null, $output = OBJECT)
{
// Log how the function was called
$this->func_call = "\$db->get_results(\"$query\", $output)";
// If there is a query then perform it if not then use cached results..
if ( $query )
{
$this->query($query);
}
// Send back array of objects. Each row is an object
if ( $output == OBJECT )
{
return $this->last_result;
}
elseif ( $output == ARRAY_A || $output == ARRAY_N )
{
if ( $this->last_result )
{
$i=0;
foreach( $this->last_result as $row )
{
$new_array[$i] = get_object_vars($row);
if ( $output == ARRAY_N )
{
$new_array[$i] = array_values($new_array[$i]);
}
$i++;
}
return $new_array;
}
else
{
return null;
}
}
}
/**********************************************************************
* Function to get column meta data info pertaining to the last query
* see docs for more info and usage
*/
function get_col_info($info_type="name",$col_offset=-1)
{
if ( $this->col_info )
{
if ( $col_offset == -1 )
{
$i=0;
foreach($this->col_info as $col )
{
$new_array[$i] = $col->{$info_type};
$i++;
}
return $new_array;
}
else
{
return $this->col_info[$col_offset]->{$info_type};
}
}
}
/**********************************************************************
* store_cache
*/
function store_cache($query,$is_insert)
{
// The would be cache file for this query
$cache_file = $this->cache_dir.'/'.md5($query);
// disk caching of queries
if ( $this->use_disk_cache && ( $this->cache_queries && ! $is_insert ) || ( $this->cache_inserts && $is_insert ))
{
if ( ! is_dir($this->cache_dir) )
{
$this->register_error("Could not open cache dir: $this->cache_dir");
$this->show_errors ? trigger_error("Could not open cache dir: $this->cache_dir",E_USER_WARNING) : null;
}
else
{
// Cache all result values
$result_cache = array
(
'col_info' => $this->col_info,
'last_result' => $this->last_result,
'num_rows' => $this->num_rows,
'return_value' => $this->num_rows,
);
error_log ( serialize($result_cache), 3, $cache_file);
}
}
}
/**********************************************************************
* get_cache
*/
function get_cache($query)
{
// The would be cache file for this query
$cache_file = $this->cache_dir.'/'.md5($query);
// Try to get previously cached version
if ( $this->use_disk_cache && file_exists($cache_file) )
{
// Only use this cache file if less than 'cache_timeout' (hours)
if ( (time() - filemtime($cache_file)) > ($this->cache_timeout*3600) )
{
unlink($cache_file);
}
else
{
$result_cache = unserialize(file_get_contents($cache_file));
$this->col_info = $result_cache['col_info'];
$this->last_result = $result_cache['last_result'];
$this->num_rows = $result_cache['num_rows'];
$this->from_disk_cache = true;
// If debug ALL queries
$this->trace || $this->debug_all ? $this->debug() : null ;
return $result_cache['return_value'];
}
}
}
/**********************************************************************
* Dumps the contents of any input variable to screen in a nicely
* formatted and easy to understand way - any type: Object, Var or Array
*/
function vardump($mixed='')
{
// Start outup buffering
ob_start();
echo "<p><table><tr><td bgcolor=ffffff><blockquote><font color=000090>";
echo "<pre><font face=arial>";
if ( ! $this->vardump_called )
{
echo "<font color=800080><b>ezSQL</b> (v".EZSQL_VERSION.") <b>Variable Dump..</b></font>\n\n";
}
$var_type = gettype ($mixed);
print_r(($mixed?$mixed:"<font color=red>No Value / False</font>"));
echo "\n\n<b>Type:</b> " . ucfirst($var_type) . "\n";
echo "<b>Last Query</b> [$this->num_queries]<b>:</b> ".($this->last_query?$this->last_query:"NULL")."\n";
echo "<b>Last Function Call:</b> " . ($this->func_call?$this->func_call:"None")."\n";
echo "<b>Last Rows Returned:</b> ".count($this->last_result)."\n";
echo "</font></pre></font></blockquote></td></tr></table>".$this->donation();
echo "\n<hr size=1 noshade color=dddddd>";
// Stop output buffering and capture debug HTML
$html = ob_get_contents();
ob_end_clean();
// Only echo output if it is turned on
if ( $this->debug_echo_is_on )
{
echo $html;
}
$this->vardump_called = true;
return $html;
}
/**********************************************************************
* Alias for the above function
*/
function dumpvar($mixed)
{
$this->vardump($mixed);
}
/**********************************************************************
* Displays the last query string that was sent to the database & a
* table listing results (if there were any).
* (abstracted into a seperate file to save server overhead).
*/
function debug()
{
// Start outup buffering
ob_start();
echo "<blockquote>";
// Only show ezSQL credits once..
if ( ! $this->debug_called )
{
echo "<font color=800080 face=arial size=2><b>ezSQL</b> (v".EZSQL_VERSION.") <b>Debug..</b></font><p>\n";
}
if ( $this->last_error )
{
echo "<font face=arial size=2 color=000099><b>Last Error --</b> [<font color=000000><b>$this->last_error</b></font>]<p>";
}
if ( $this->from_disk_cache )
{
echo "<font face=arial size=2 color=000099><b>Results retrieved from disk cache</b></font><p>";
}
echo "<font face=arial size=2 color=000099><b>Query</b> [$this->num_queries] <b>--</b> ";
echo "[<font color=000000><b>$this->last_query</b></font>]</font><p>";
echo "<font face=arial size=2 color=000099><b>Query Result..</b></font>";
echo "<blockquote>";
if ( $this->col_info )
{
// =====================================================
// Results top rows
echo "<table cellpadding=5 cellspacing=1 bgcolor=555555>";
echo "<tr bgcolor=eeeeee><td nowrap valign=bottom><font color=555599 face=arial size=2><b>(row)</b></font></td>";
for ( $i=0; $i < count($this->col_info); $i++ )
{
echo "<td nowrap align=left valign=top><font size=1 color=555599 face=arial>{$this->col_info[$i]->type} {$this->col_info[$i]->max_length}</font><br><span style='font-family: arial; font-size: 10pt; font-weight: bold;'>{$this->col_info[$i]->name}</span></td>";
}
echo "</tr>";
// ======================================================
// print main results
if ( $this->last_result )
{
$i=0;
foreach ( $this->get_results(null,ARRAY_N) as $one_row )
{
$i++;
echo "<tr bgcolor=ffffff><td bgcolor=eeeeee nowrap align=middle><font size=2 color=555599 face=arial>$i</font></td>";
foreach ( $one_row as $item )
{
echo "<td nowrap><font face=arial size=2>$item</font></td>";
}
echo "</tr>";
}
} // if last result
else
{
echo "<tr bgcolor=ffffff><td colspan=".(count($this->col_info)+1)."><font face=arial size=2>No Results</font></td></tr>";
}
echo "</table>";
} // if col_info
else
{
echo "<font face=arial size=2>No Results</font>";
}
echo "</blockquote></blockquote>".$this->donation()."<hr noshade color=dddddd size=1>";
// Stop output buffering and capture debug HTML
$html = ob_get_contents();
ob_end_clean();
// Only echo output if it is turned on
if ( $this->debug_echo_is_on )
{
echo $html;
}
$this->debug_called = true;
return $html;
}
/**********************************************************************
* Naughty little function to ask for some remuniration!
*/
function donation()
{
return "<font size=1 face=arial color=000000>If ezSQL has helped make a donation!? <!--[ go on! you know you want to! ]--></font>";
}
}
?>
i have problem about ezSQL_mysql and ezSQLcore, it may be an incompatible version of PHP, I share the code below. what version of Php should I use or what should I do to tailor mysqli?
what kind of arrangement should I make? thanks for helping.
The problem is that in your implementation you have:
function escape($str)
While in core they have:
function escape()
They need to match exactly. So I suggest that change the signature back in the core file to escape($str)

PhpDoc generating warning

The warning messages generated in the document following is the message seen in all methods inside the document.
Warning: count(): Parameter must be an array or an object that implements Countable in C:\php72\pear\phpDocumentor\vendor\twig\twig\lib\Twig\Extension\Core.php on line 1198 Warning: count(): Parameter must be an array or an object that implements Countable in C:\php72\pear\phpDocumentor\vendor\twig\twig\lib\Twig\Extension\Core.php on line 1198 enter image description here
Ok found the solution.
see link
https://area51.phpbb.com/phpBB/viewtopic.php?t=52691
changed the function in core.php file
/**
* Returns the length of a variable.
*
* #param Twig_Environment $env
* #param mixed $thing A variable
*
* #return int The length of the value
*/
function twig_length_filter(Twig_Environment $env, $thing)
{
if (is_scalar($thing)) {
return mb_strlen($thing, $env->getCharset());
}
if (is_object($thing) && method_exists($thing, '__toString') && !$thing instanceof \Countable) {
return mb_strlen((string) $thing, $env->getCharset());
}
return count($thing);
}
to following
/**
* Returns the length of a variable.
*
* #param Twig_Environment $env
* #param mixed $thing A variable
*
* #return int The length of the value
*/
function twig_length_filter(Twig_Environment $env, $thing)
{
if (null === $thing) {
return 0;
}
if (is_scalar($thing)) {
return mb_strlen($thing, $env->getCharset());
}
if (is_object($thing) && method_exists($thing, '__toString') && !$thing instanceof \Countable) {
return mb_strlen((string) $thing, $env->getCharset());
}
if ($thing instanceof \Countable || is_array($thing)) {
return count($thing);
}
return 1;
}
I have struggled a lot to correct this same error, so I wish to share here the complete solution : what to change and how to edit the phar file :
Copy this script in a file in your phar folder and run it with the php command line with option specified in code :
<?php
//to be run with php -d phar.readonly=Off -f thisFileName
if (ini_get('phar.readonly')==1){
die("\n\nThis script must be run with option -d phar.readonly=Off so that it can write the .phar file\n\n");
}
//the file you want to change
$file = 'vendor/twig/twig/lib/Twig/Extension/Core.php';
//the function in the file you want to change
$oldFunction = '#function twig_length_filter[^{]*[^}]*}#'; //assuming it's still the actual one line function, regex might need updates in the furure
//the replacement function
$newFunction = <<<'funct'
function twig_length_filter(Twig_Environment $env, $thing)
{
if (null === $thing) {
return 0;
}
if (is_scalar($thing)) {
return mb_strlen($thing, $env->getCharset());
}
if (is_object($thing) && method_exists($thing, '__toString') && !$thing instanceof \Countable) {
return mb_strlen((string) $thing, $env->getCharset());
}
if ($thing instanceof \Countable || is_array($thing)) {
return count($thing);
}
return 1;
}
funct;
//access the phar
$p = new Phar('phpDocumentor.phar', FilesystemIterator::CURRENT_AS_FILEINFO | FilesystemIterator::KEY_AS_FILENAME);
//extract the file
$p->extractTo('./', $file, true);
//return here if you want to check the file first
//return;
//change the function
file_put_contents($file,
$newFile = preg_replace(
$oldFunction,
$newFunction,
file_get_contents($file)
)
);
//update the file
$p[$file] = $file;
//done
echo 'Done. Don\'t forget to delete the "vendor" folder extracted from the phar !';

Mysql Clone user account from database

I have a database with aproximately 200 tables. I want to clone a certain user-account in this database. Is this possible in mysql?
With cloning I mean to create a new user with the same 'settings' as the user with id 14.
A quick google search reveals that you in fact can do this. There is a utility called " "mysql user clone", that lets you surprisingly clone a user for mysql.
If you check out the manual I'm sure it provides you with great tips about how to use it, for instance, this quote:
EXAMPLES
To clone joe as sam and sally with passwords and logging in as root on the local machine, use this command:
$ mysqluserclone --source=root#localhost \
--destination=root#localhost \
joe#localhost sam:secret1#localhost sally:secret2#localhost
# Source on localhost: ... connected.
# Destination on localhost: ... connected.
# Cloning 2 users...
# Cloning joe#localhost to user sam:secret1#localhost
# Cloning joe#localhost to user sally:secret2#localhost
# ...done.
since it appears #Nanne's approach, mysqluserclone is EOL / not supported by Oracle, i wrote a similar utility in PHP, usage:
<?php
$db = new \PDO("mysql:host={$db_creds->dbhost};dbname={$db_creds->dbname};charset=utf8mb4;port=" . $db_creds->dbport, $db_creds->superuser_user, $db_creds->superuser_pass, array(
\PDO::ATTR_EMULATE_PREPARES => false,
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
\PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC
));
Mysqluserclone::clone_account($db, "original username", "cloned username");
and this part of the code may be of particular interest:
if (0) {
echo "the following SQLs will clone this account:\n";
echo implode("\n\n", $sqls_for_cloning) . "\n\n";
die();
}
<?php
class Mysqluserclone{
private function __construct(){
// by making this private, we ensure nobody try to instantiate us.
}
public static function clone_account(\PDO $db_connected_as_superuser, string $original_name, string $clone_name): void
{
$db = $db_connected_as_superuser;
$sqls_for_cloning = [];
$sql = "SELECT COUNT(*) FROM mysql.user WHERE User = " . $db->quote($clone_name);
if (0 !== $db->query($sql)->fetch(\PDO::FETCH_NUM)[0]) {
throw new \InvalidArgumentException("clone name already exists!");
}
$sql = "SELECT * FROM mysql.user WHERE User = " . $db->quote($original_name);
$current_user_one_for_each_host = $db->query($sql)->fetchAll(\PDO::FETCH_ASSOC);
foreach ($current_user_one_for_each_host as $user_record) {
$user_record["User"] = $clone_name;
$sql = "INSERT INTO mysql.user SET \n";
foreach ($user_record as $name => $val) {
$sql .= self::mysql_quote_identifier($name) . " = " . self::mysql_quote_better($db, $val) . ",\n";
}
if (! empty($user_record)) {
$sql = substr($sql, 0, - strlen(",\n"));
}
$sql .= ";";
$sqls_for_cloning[] = $sql;
$sqls_for_cloning[] = "FLUSH PRIVILEGES;"; // YES this is required, otherwise you might get "grant not allowed to create accounts" errors
$grants_raw_sql = 'SHOW GRANTS FOR ' . $db->quote($original_name) . '#' . $db->quote($user_record['Host']) . ";";
try {
$grants_raw = $db->query($grants_raw_sql)->fetchAll(\PDO::FETCH_NUM);
} catch (\Throwable $ex) {
// somehow an empty grant table is a mysql error, not an empty rowset.. ignore it.
$grants_raw = [];
}
$grants_raw = array_map(function (array $arr): string {
if (count($arr) !== 1) {
throw new \LogicException("mysql layout for SHOW GRANTS has changed? investigate");
}
return $arr[0];
}, $grants_raw);
$original_name_as_identifier = self::mysql_quote_identifier($original_name);
$clone_name_as_identifier = self::mysql_quote_identifier($clone_name);
foreach ($grants_raw as $grant) {
if (false === strpos($grant, $original_name_as_identifier)) {
throw new \LogicException("original grant without original name as identifier? investigate");
}
$grant = self::str_replace_last($original_name_as_identifier, $clone_name_as_identifier, $grant);
$grant .= ";";
$sqls_for_cloning[] = $grant;
}
}
if (! empty($sqls_for_cloning)) {
$sqls_for_cloning[] = "FLUSH PRIVILEGES;";
}
if (0) {
echo "the following SQLs will clone this account:\n";
echo implode("\n\n", $sqls_for_cloning) . "\n\n";
die();
}
foreach ($sqls_for_cloning as $clone_sql) {
$db->exec($clone_sql);
}
}
private static function mysql_quote_identifier(string $identifier): string
{
return '`' . strtr($identifier, [
'`' => '``'
]) . '`';
}
private static function mysql_quote_better(\PDO $db, $value): string
{
if (is_null($value)) {
return "NULL";
}
if (is_int($value)) {
return (string) $value;
}
if (is_float($value)) {
return number_format($value, 10, '.', '');
}
if (is_bool($value)) {
return ($value ? "1" : "0");
}
return $db->quote($value);
}
private static function str_replace_last(string $search, string $replace, string $subject): string
{
$pos = strrpos($subject, $search);
if ($pos !== false) {
$subject = substr_replace($subject, $replace, $pos, strlen($search));
}
return $subject;
}
}

Amazon Product Ads, is there an API to search for Categories?

I'm building a module that will help seller put their products catalogue in the Amazon Product Ads service. For that, I need to make a mapping between the client's category and the Amazon Product Ads categories.
Problem is, I can't find an API that will help me search for Categories, or a file containing all the existing categories for Amazon Product Ads.
I found that link http://docs.aws.amazon.com/AWSECommerceService/latest/DG/BrowseNodeIDs.html, that may be a start, but based on their API, it's not possible to search by text (like "Apparel") but by NodeId, which is not what I'm looking for :/
Can anyone of you help me please?
Thank you for your help :)
You have to use Browse Node Id of parent categories and bases on that you can search for sub categories.
Create a file named amazon_api_class.php
<?php
require_once 'aws_signed_request.php';
class AmazonProductAPI
{
/**
* Your Amazon Access Key Id
* #access private
* #var string
*/
private $public_key = "";
/**
* Your Amazon Secret Access Key
* #access private
* #var string
*/
private $private_key = "";
private $media_type = "";
private $region = "";
private $out_file_fp = "";
public function __construct($public, $private, $region) {
$this->public_key = $public;
$this->private_key = $private;
$this->region = $region;
}
public function getNode($node)
{
$parameters = array("Operation" => "BrowseNodeLookup",
"BrowseNodeId" => $node,
"ResponseGroup" => "BrowseNodeInfo");
$xml_response = aws_signed_request($parameters,
$this->public_key,
$this->private_key,
$this->region);
return ($xml_response);
}
public function setMedia($media, $file = "") {
$media_type = array("display", "csv");
if(!in_array($media,$media_type)) {
throw new Exception("Invalid Media Type");
exit();
}
$this->media_type = $media;
if($media == "csv") {
$this->out_file_fp = fopen($file,'a+');
}
}
private function writeOut($level, $name, $id, $parent) {
if($this->media_type == "display") {
$spaces = str_repeat( ' ', ( $level * 6 ) );
echo $spaces . $parent . ' : ' . $name . ' : ' . $id . "\n";
} elseif ($this->media_type == "csv") {
$csv_line = '"' . $parent . '","' . $name . '","' . $id . '"' . "\n";
fputs($this->out_file_fp, $csv_line);
} else {
throw new Exception("Invalid Media Type");
exit();
}
}
public function getBrowseNodes($nodeValue, $level = 0)
{
try {
$result = $this->getNode($nodeValue);
}
catch(Exception $e) {
echo $e->getMessage();
}
if(!isset($result->BrowseNodes->BrowseNode->Children->BrowseNode)) return;
if(count($result->BrowseNodes->BrowseNode->Children->BrowseNode) > 0) {
foreach($result->BrowseNodes->BrowseNode->Children->BrowseNode as $node) {
$this->writeOut($level, $node->Name,
$node->BrowseNodeId,
$result->BrowseNodes->BrowseNode->Name);
$this->getBrowseNodes($node->BrowseNodeId, $level+1);
}
} else {
return;
}
}
public function getNodeName($nodeValue)
{
try {
$result = $this->getNode($nodeValue);
}
catch(Exception $e) {
echo $e->getMessage();
}
if(!isset($result->BrowseNodes->BrowseNode->Name)) return;
return (string)$result->BrowseNodes->BrowseNode->Name;
}
public function getParentNode($nodeValue)
{
try {
$result = $this->getNode($nodeValue);
}
catch(Exception $e) {
echo $e->getMessage();
}
if(!isset($result->BrowseNodes->BrowseNode->Ancestors->BrowseNode->BrowseNodeId)) return;
$parent_node = array("id" => (string)$result->BrowseNodes->BrowseNode->Ancestors->BrowseNode->BrowseNodeId,
"name" => (string)$result->BrowseNodes->BrowseNode->Ancestors->BrowseNode->Name);
return $parent_node;
}}?>
Create file named as aws_signed_request.php
<?php
function aws_signed_request($params,$public_key,$private_key,$region)
{
$method = "GET";
$host = "ecs.amazonaws.".$region; // must be in small case
$uri = "/onca/xml";
$params["Service"] = "AWSECommerceService";
$params["AWSAccessKeyId"] = $public_key;
$params["AssociateTag"] = 'YOUR-ASSOCIATES-ID-HERE';
$params["Timestamp"] = gmdate("Y-m-d\TH:i:s\Z");
$params["Version"] = "2009-03-31";
/* The params need to be sorted by the key, as Amazon does this at
their end and then generates the hash of the same. If the params
are not in order then the generated hash will be different thus
failing the authetication process.
*/
ksort($params);
$canonicalized_query = array();
foreach ($params as $param=>$value)
{
$param = str_replace("%7E", "~", rawurlencode($param));
$value = str_replace("%7E", "~", rawurlencode($value));
$canonicalized_query[] = $param."=".$value;
}
$canonicalized_query = implode("&", $canonicalized_query);
$string_to_sign = $method."\n".$host."\n".$uri."\n".$canonicalized_query;
/* calculate the signature using HMAC with SHA256 and base64-encoding.
The 'hash_hmac' function is only available from PHP 5 >= 5.1.2.
*/
$signature = base64_encode(hash_hmac("sha256", $string_to_sign, $private_key, True));
/* encode the signature for the request */
$signature = str_replace("%7E", "~", rawurlencode($signature));
/* create request */
$request = "http://".$host.$uri."?".$canonicalized_query."&Signature=".$signature;
/* I prefer using CURL */
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL,$request);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_TIMEOUT, 15);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
$xml_response = curl_exec($ch);
if ($xml_response === False)
{
return False;
}
else
{
/* parse XML */
$parsed_xml = #simplexml_load_string($xml_response);
return ($parsed_xml === False) ? False : $parsed_xml;
}
}
?>
Create file named as index.php
<?php
/* Example usage of the Amazon Product Advertising API */
include("amazon_api_class.php");
$public_key = "YOUR-AMAZON-PUBLIC-KEY";
$private_key = "YOUR-AMAZON-SECRET-KEY";
$region = "com"; // or "CA" or "DE" etc.
$obj = new AmazonProductAPI($public_key, $private_key, $region);
$obj->setMedia("display");
$obj->getBrowseNodes("1036592"); //Apparel US store
?>
Dont forget to update your public and private key in index.php

How to debug a query in extbase?

$query = $this->createQuery();
return $query->matching($query->like('linker', "$linkerKey=$linkerValue"))
->setOrderings(array('crdate' => $ordering))
->execute();
How can i debug such a generated query in extbase? When creating the same query again (but without the execute() ) and trying to display it with var_dump or the internal t3lib_div::debug i just receive a blank page.
In version 8.7 or newer another way needs to be taken:
use TYPO3\CMS\Extbase\Persistence\Generic\Storage\Typo3DbQueryParser;
use TYPO3\CMS\Extbase\Utility\DebuggerUtility;
$queryParser = $this->objectManager->get(Typo3DbQueryParser::class);
$queryBuilder = $queryParser->convertQueryToDoctrineQueryBuilder($query);
DebuggerUtility::var_dump($queryBuilder->getSQL());
DebuggerUtility::var_dump($queryBuilder->getParameters());
This information is outdated and deprecated in TYPO3 8.7 and I'm only leaving the answer up for reference. Refer to #pgampe 's answer on how debug extbase queries in more recent versions of TPYO3.
Extbase now has a QueryParser for that. In your repository method, right before returning the executed query, insert:
$parser = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Extbase\\Persistence\\Generic\\Storage\\Typo3DbQueryParser');
$queryParts = $parser->parseQuery($query);
\TYPO3\CMS\Core\Utility\DebugUtility::debug($queryParts, 'query');
The result is a table view of the query parts, split by SQL keywords, e.g.:
Keep in mind that the QueryResult that your Repository returns may still be different from the SQL query result. Extbase uses the PropertyMapper to try to convert every result row into an ExtbaseObject. If the PropertyMapper is misconfigured or the row contains data that cannot be converted to the data types according to the configuration, these Objects will silently be skipped.
$query = $this->createQuery();
$result = $query->matching($query->like('linker', "$linkerKey=$linkerValue"))
->setOrderings(array('crdate' => $ordering))
->execute();
$GLOBALS['TYPO3_DB']->debugOutput = true;
return $result;
This hack to extbase is dirty, but useful:
In typo3/sysext/extbase/Classes/Persistence/Storage/Typo3DbBackend.php
edit the method buildQuery(array $sql)
before the return statement, add:
t3lib_div::debug($statement, 'SQL Query Extbase');
Remove after use, and don't forget this will affect everything that runs on extbase, so use in dev environment only
Source: http://sancer-media.net/2011/extbase-schneller-mysql-debug.html
An easy way without changing any Typo3 core code and not mentioned in any forum so far is using the php "serialize()" method:
$result = $query->execute();
echo (serialize($result));
In the result object you find the SQL query (look for "statement;" ...)
This works as long as $GLOBALS['TYPO3_DB'] is supported.
It will show you the complete build SQL query.
/**
* #param \TYPO3\CMS\Extbase\Persistence\QueryResultInterface $queryResult
* #param bool $explainOutput
* #return void
*/
public function debugQuery(
\TYPO3\CMS\Extbase\Persistence\QueryResultInterface $queryResult,
$explainOutput = false
) {
$GLOBALS['TYPO3_DB']->debugOuput = 2;
if ($explainOutput) {
$GLOBALS['TYPO3_DB']->explainOutput = true;
}
$GLOBALS['TYPO3_DB']->store_lastBuiltQuery = true;
$queryResult->toArray();
\TYPO3\CMS\Extbase\Utility\DebuggerUtility::var_dump(
$GLOBALS['TYPO3_DB']->debug_lastBuiltQuery
);
$GLOBALS['TYPO3_DB']->store_lastBuiltQuery = false;
$GLOBALS['TYPO3_DB']->explainOutput = false;
$GLOBALS['TYPO3_DB']->debugOuput = false;
}
So with that function you can do something like this in your controller:
$all = $this->repository->findAll();
$this->repository->debugQuery($all);
According to #pgampe's answer and
#FranzHolzinger's comment I created AbstractonRepository in my ext wit some extracted methods, that can be extended in your own repository to bring debug possibility little bit more comfortable.
NOTE That's tested and working for TYPO3 ver 10.x, most probably will work since 8.7+ as well, requires testing.
<?php
namespace VENDOR\Extkey\Domain\Repository;
use TYPO3\CMS\Extbase\Utility\DebuggerUtility;
use TYPO3\CMS\Core\Exception;
use TYPO3\CMS\Extbase\Persistence\Generic\Storage\Typo3DbQueryParser;
use TYPO3\CMS\Extbase\Persistence\QueryInterface;
/**
* Class AbstractRepository brings methods for query debugging in TYPO3 ver. 10.x
* Based on StackOverflow answer by `pgampe` answer and `FranzHolzinger` comment
* source https://stackoverflow.com/a/44286155/1066240
*
* All repositories in this extension should extend it.
*
* #author Marcus Biesioroff <biesior#gmail.com>
* #package VENDOR\Extkey\Domain\Repository
*/
abstract class AbstractRepository extends \TYPO3\CMS\Extbase\Persistence\Repository
{
/**
* #param mixed $query TYPO3\CMS\Core\Database\Query\QueryBuilder | TYPO3\CMS\Extbase\Persistence\Generic\Query
* #param string|null $title Optional title for var_dump()
* #param bool $replaceParams if true replaces the params in SQL statement with values, otherwise dumps the array of params. #see self::renderDebug()
*
* #throws Exception
*/
protected function debugQuery($query, string $title = null, bool $replaceParams = true): void
{
if ($query instanceof \TYPO3\CMS\Core\Database\Query\QueryBuilder) {
$sql = $query->getSQL();
$params = $query->getParameters();
$this->renderDebug($sql, $params, $title, $replaceParams);
} elseif ($query instanceof \TYPO3\CMS\Extbase\Persistence\Generic\Query) {
$this->parseTheQuery($query, $title, $replaceParams);
} else {
throw new Exception('Unhandled type for SQL query, curently only TYPO3\CMS\Core\Database\Query\QueryBuilder | TYPO3\CMS\Extbase\Persistence\Generic\Query can be debugged with ' . static::getRepositoryClassName() . '::debugQuery() method.', 1596458998);
}
}
/**
* Parses query and displays debug
*
* #param QueryInterface $query Query
* #param string|null $title Optional title
* #param bool $replaceParams if true replaces the params in SQL statement with values, otherwise dumps the array of params. #see self::renderDebug()
*/
private function parseTheQuery(QueryInterface $query, string $title = null, $replaceParams = true): void
{
/** #var Typo3DbQueryParser $queryParser */
$queryParser = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\Storage\Typo3DbQueryParser::class);
$sql = $queryParser->convertQueryToDoctrineQueryBuilder($query)->getSQL();
$params = $queryParser->convertQueryToDoctrineQueryBuilder($query)->getParameters();
$this->renderDebug($sql, $params, $title, $replaceParams);
}
/**
* Renders the output with DebuggerUtility::var_dump()
*
* #param string $sql Generated SQL
* #param array $params Params' array
* #param string|null $title Optional title for var_dump()
* #param bool $replaceParams if true replaces the params in SQL statement with values, otherwise dumps the array of params.
*/
private function renderDebug(string $sql, array $params, string $title = null, bool $replaceParams = true): void
{
if ($replaceParams) {
$search = array();
$replace = array();
foreach ($params as $k => $v) {
$search[] = ':' . $k;
$type = gettype($v);
if (in_array($type, ['integer'])) {
$replace[] = $v;
} else {
$replace[] = '\'' . $v . '\'';
}
}
$sql = str_replace($search, $replace, $sql);
DebuggerUtility::var_dump($sql, $title);
} else {
DebuggerUtility::var_dump(
[
'SQL' => $sql,
'Parameters' => $params
],
$title);
}
}
}
It can be used in your repository like in to ways:
<?php
namespace VENDOR\Extkey\Domain\Repository;
use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool;
class FooRepository extends \VENDOR\Extkey\Domain\Repository\AbstractRepository
{
public function findByName($name)
{
$query = $this->createQuery();
$query->matching(
$query->equals('name', $name)
);
$this->debugQuery($query, 'Debug SQL in repository with QueryInterface');
return $query->execute();
}
public function queryByName($name)
{
/** #var ConnectionPool $pool */
$pool = GeneralUtility::makeInstance(ConnectionPool::class);
$connection = $pool->getConnectionForTable('tx_extkey_domain_model_yourmodel');
$queryBuilder = $connection->createQueryBuilder();
$query = $queryBuilder
->select('*')
->from('tx_extkey_domain_model_yourmodel')
->where("name like :name")
->setParameter('name', "%{$name}%");
$this->debugQuery($query, 'Debug SQL in my repository with QueryBuilder');
return $query->execute()->fetchAll();
}
In v6.2x or later, you can debug result object in extBase like:
In repository:
return $query->execute(true); // "true" will return array result
Or also you can debug result object in Controller:
$resultObject = $this->yourRepository->findAll();
\TYPO3\CMS\Extbase\Utility\DebuggerUtility::var_dump($resultObject);
Here I post a method you can enter for debugging in any class, making a trait of it would be surely possible too. Authorship and source is mentioned in the comment, Usage too:
/**
* Render the generated SQL of a query in TYPO3 8
*
* #author wp_bube https://www.typo3.net/forum/user-profil/benutzer/zeige/benutzer/wp-bube/
* #src https://www.typo3.net/forum/thematik/zeige/thema/125747/
*
* Usage: $this->debugQuery($query);
*
* #param \TYPO3\CMS\Extbase\Persistence\QueryInterface $query
* #param bool $format
* #param bool $exit
*/
private function debugQuery($query, $format = true, $exit = true)
{
function getFormattedSQL($sql_raw)
{
if (empty($sql_raw) || !is_string($sql_raw)) {
return false;
}
$sql_reserved_all = array( 'ACCESSIBLE', 'ACTION', 'ADD', 'AFTER', 'AGAINST', 'AGGREGATE', 'ALGORITHM', 'ALL', 'ALTER', 'ANALYSE', 'ANALYZE', 'AND', 'AS', 'ASC', 'AUTOCOMMIT', 'AUTO_INCREMENT', 'AVG_ROW_LENGTH', 'BACKUP', 'BEGIN', 'BETWEEN', 'BINLOG', 'BOTH', 'BY', 'CASCADE', 'CASE', 'CHANGE', 'CHANGED', 'CHARSET', 'CHECK', 'CHECKSUM', 'COLLATE', 'COLLATION', 'COLUMN', 'COLUMNS', 'COMMENT', 'COMMIT', 'COMMITTED', 'COMPRESSED', 'CONCURRENT', 'CONSTRAINT', 'CONTAINS', 'CONVERT', 'CREATE', 'CROSS', 'CURRENT_TIMESTAMP', 'DATABASE', 'DATABASES', 'DAY', 'DAY_HOUR', 'DAY_MINUTE', 'DAY_SECOND', 'DEFINER', 'DELAYED', 'DELAY_KEY_WRITE', 'DELETE', 'DESC', 'DESCRIBE', 'DETERMINISTIC', 'DISTINCT', 'DISTINCTROW', 'DIV', 'DO', 'DROP', 'DUMPFILE', 'DUPLICATE', 'DYNAMIC', 'ELSE', 'ENCLOSED', 'END', 'ENGINE', 'ENGINES', 'ESCAPE', 'ESCAPED', 'EVENTS', 'EXECUTE', 'EXISTS', 'EXPLAIN', 'EXTENDED', 'FAST', 'FIELDS', 'FILE', 'FIRST', 'FIXED', 'FLUSH', 'FOR', 'FORCE', 'FOREIGN', 'FROM', 'FULL', 'FULLTEXT', 'FUNCTION', 'GEMINI', 'GEMINI_SPIN_RETRIES', 'GLOBAL', 'GRANT', 'GRANTS', 'GROUP', 'HAVING', 'HEAP', 'HIGH_PRIORITY', 'HOSTS', 'HOUR', 'HOUR_MINUTE', 'HOUR_SECOND', 'IDENTIFIED', 'IF', 'IGNORE', 'IN', 'INDEX', 'INDEXES', 'INFILE', 'INNER', 'INSERT', 'INSERT_ID', 'INSERT_METHOD', 'INTERVAL', 'INTO', 'INVOKER', 'IS', 'ISOLATION', 'JOIN', 'KEY', 'KEYS', 'KILL', 'LAST_INSERT_ID', 'LEADING', 'LEFT', 'LEVEL', 'LIKE', 'LIMIT', 'LINEAR', 'LINES', 'LOAD', 'LOCAL', 'LOCK', 'LOCKS', 'LOGS', 'LOW_PRIORITY', 'MARIA', 'MASTER', 'MASTER_CONNECT_RETRY', 'MASTER_HOST', 'MASTER_LOG_FILE', 'MASTER_LOG_POS', 'MASTER_PASSWORD', 'MASTER_PORT', 'MASTER_USER', 'MATCH', 'MAX_CONNECTIONS_PER_HOUR', 'MAX_QUERIES_PER_HOUR', 'MAX_ROWS', 'MAX_UPDATES_PER_HOUR', 'MAX_USER_CONNECTIONS', 'MEDIUM', 'MERGE', 'MINUTE', 'MINUTE_SECOND', 'MIN_ROWS', 'MODE', 'MODIFY', 'MONTH', 'MRG_MYISAM', 'MYISAM', 'NAMES', 'NATURAL', 'NOT', 'NULL', 'OFFSET', 'ON', 'OPEN', 'OPTIMIZE', 'OPTION', 'OPTIONALLY', 'OR', 'ORDER', 'OUTER', 'OUTFILE', 'PACK_KEYS', 'PAGE', 'PARTIAL', 'PARTITION', 'PARTITIONS', 'PASSWORD', 'PRIMARY', 'PRIVILEGES', 'PROCEDURE', 'PROCESS', 'PROCESSLIST', 'PURGE', 'QUICK', 'RAID0', 'RAID_CHUNKS', 'RAID_CHUNKSIZE', 'RAID_TYPE', 'RANGE', 'READ', 'READ_ONLY', 'READ_WRITE', 'REFERENCES', 'REGEXP', 'RELOAD', 'RENAME', 'REPAIR', 'REPEATABLE', 'REPLACE', 'REPLICATION', 'RESET', 'RESTORE', 'RESTRICT', 'RETURN', 'RETURNS', 'REVOKE', 'RIGHT', 'RLIKE', 'ROLLBACK', 'ROW', 'ROWS', 'ROW_FORMAT', 'SECOND', 'SECURITY', 'SELECT', 'SEPARATOR', 'SERIALIZABLE', 'SESSION', 'SET', 'SHARE', 'SHOW', 'SHUTDOWN', 'SLAVE', 'SONAME', 'SOUNDS', 'SQL', 'SQL_AUTO_IS_NULL', 'SQL_BIG_RESULT', 'SQL_BIG_SELECTS', 'SQL_BIG_TABLES', 'SQL_BUFFER_RESULT', 'SQL_CACHE', 'SQL_CALC_FOUND_ROWS', 'SQL_LOG_BIN', 'SQL_LOG_OFF', 'SQL_LOG_UPDATE', 'SQL_LOW_PRIORITY_UPDATES', 'SQL_MAX_JOIN_SIZE', 'SQL_NO_CACHE', 'SQL_QUOTE_SHOW_CREATE', 'SQL_SAFE_UPDATES', 'SQL_SELECT_LIMIT', 'SQL_SLAVE_SKIP_COUNTER', 'SQL_SMALL_RESULT', 'SQL_WARNINGS', 'START', 'STARTING', 'STATUS', 'STOP', 'STORAGE', 'STRAIGHT_JOIN', 'STRING', 'STRIPED', 'SUPER', 'TABLE', 'TABLES', 'TEMPORARY', 'TERMINATED', 'THEN', 'TO', 'TRAILING', 'TRANSACTIONAL', 'TRUNCATE', 'TYPE', 'TYPES', 'UNCOMMITTED', 'UNION', 'UNIQUE', 'UNLOCK', 'UPDATE', 'USAGE', 'USE', 'USING', 'VALUES', 'VARIABLES', 'VIEW', 'WHEN', 'WHERE', 'WITH', 'WORK', 'WRITE', 'XOR', 'YEAR_MONTH' );
$sql_skip_reserved_words = array('AS', 'ON', 'USING');
$sql_special_reserved_words = array('(', ')');
$sql_raw = str_replace("\n", " ", $sql_raw);
$sql_formatted = "";
$prev_word = "";
$word = "";
for ($i = 0, $j = strlen($sql_raw); $i < $j; $i++) {
$word .= $sql_raw[$i];
$word_trimmed = trim($word);
if ($sql_raw[$i] == " " || in_array($sql_raw[$i], $sql_special_reserved_words)) {
$word_trimmed = trim($word);
$trimmed_special = false;
if (in_array($sql_raw[$i], $sql_special_reserved_words)) {
$word_trimmed = substr($word_trimmed, 0, -1);
$trimmed_special = true;
}
$word_trimmed = strtoupper($word_trimmed);
if (in_array($word_trimmed, $sql_reserved_all) && !in_array($word_trimmed, $sql_skip_reserved_words)) {
if (in_array($prev_word, $sql_reserved_all)) {
$sql_formatted .= '<b>' . strtoupper(trim($word)) . '</b>' . ' ';
} else {
$sql_formatted .= '<br/> ';
$sql_formatted .= '<b>' . strtoupper(trim($word)) . '</b>' . ' ';
}
$prev_word = $word_trimmed;
$word = "";
} else {
$sql_formatted .= trim($word) . ' ';
$prev_word = $word_trimmed;
$word = "";
}
}
}
$sql_formatted .= trim($word);
return $sql_formatted;
}
$queryParser = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\Storage\Typo3DbQueryParser::class);
$doctrineQueryBuilder = $queryParser->convertQueryToDoctrineQueryBuilder($query);
$preparedStatement = $doctrineQueryBuilder->getSQL();
$parameters = $doctrineQueryBuilder->getParameters();
$stringParams = [];
foreach ($parameters as $key => $parameter) {
$stringParams[':' . $key] = $parameter;
}
$statement = strtr($preparedStatement, $stringParams);
if ($format) {
echo '<code>' . getFormattedSQL($statement) . '</code>';
} else {
echo $statement;
}
if ($exit) {
exit;
}
}
I build a static function of a utilitiee class for the use in TYPO3 9.5.
it should work in TYPO3 10.4. and TYPO3 11
<?php
namespace MyExtension\MyVendor\Utilities;
/**
* used in TYPO3 9.5.x
*/
use TYPO3\CMS\Core\Database\Query\QueryBuilder;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Object\ObjectManager;
use TYPO3\CMS\Extbase\Persistence\Generic\Storage\Typo3DbQueryParser;
use TYPO3\CMS\Extbase\Utility\DebuggerUtility;
class DebugQueryUtilities
{
public static function checkExtbaseQuery($query)
{
$objectManager = GeneralUtility::makeInstance(ObjectManager::class);
$queryParser = $objectManager->get(Typo3DbQueryParser::class);
/** #var QueryBuilder $doctrineQuery */
$doctrineQuery = $queryParser->convertQueryToDoctrineQueryBuilder($query);
$result = $doctrineQuery->getSQL();
$params = $doctrineQuery->getParameters();
DebuggerUtility::var_dump(
$result
); // generate an output in the frontpage
DebuggerUtility::var_dump(
$params
);
return [$result, $params]; // make result vieable with xDebug
}
}
Use in Repository
...
[$sql,$params] = DebugQueryUtilities::checkExtbaseQuery($query);
return $query->execute()->toArray();
fixed #biesior ´s answer:
<?php
declare(strict_types=1);
namespace YOUR\NAMESPACE\HERE;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Persistence\Generic\Storage\Typo3DbQueryParser;
use TYPO3\CMS\Extbase\Persistence\QueryInterface;
class Debug
{
/**
* #param mixed $query TYPO3\CMS\Core\Database\Query\QueryBuilder | TYPO3\CMS\Extbase\Persistence\Generic\Query
* #throws \Exception
*/
public static function debugQuery(\TYPO3\CMS\Core\Database\Query\QueryBuilder|\TYPO3\CMS\Extbase\Persistence\Generic\Query $query): void
{
$sql = self::getQuerySql($query);
\TYPO3\CMS\Core\Utility\DebugUtility::debug(
$var = $sql,
$header = '',
$group = 'Debug'
);
}
/**
* #param QueryInterface $query Query
* #return string
* #throws \Exception
*/
public static function getQuerySql(QueryInterface $query): string
{
if ($query instanceof \TYPO3\CMS\Core\Database\Query\QueryBuilder) {
$sql = $query->getSQL();
$params = $query->getParameters();
$executeableSql = self::replaceQueryParams($sql, $params);
} elseif ($query instanceof \TYPO3\CMS\Extbase\Persistence\Generic\Query) {
/** #var Typo3DbQueryParser $queryParser */
//$queryParser = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\Storage\Typo3DbQueryParser::class);
$queryParser = GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Persistence\Generic\Storage\Typo3DbQueryParser::class);
$sql = $queryParser->convertQueryToDoctrineQueryBuilder($query)->getSQL();
$params = $queryParser->convertQueryToDoctrineQueryBuilder($query)->getParameters();
$executeableSql = self::replaceQueryParams($sql, $params);
} else {
throw new \Exception('Unhandled type for SQL query, curently only TYPO3\CMS\Core\Database\Query\QueryBuilder | TYPO3\CMS\Extbase\Persistence\Generic\Query can be debugged with ' . static::getRepositoryClassName() . '::debugQuery() method.', 1596458998);
}
return $executeableSql;
}
/**
* #param string $sql Generated SQL
* #param array $params Params' array
* #return string
*/
private static function replaceQueryParams(string $sql, array $params): string
{
$search = array();
$replace = array();
foreach ($params as $k => $v) {
$search[] = ':' . $k;
$type = gettype($v);
if (in_array($type, ['integer'])) {
$replace[] = $v;
} else {
// escape \ (eg in TYPO3 STI)
$v = str_replace('\\', '\\\\', $v);
$replace[] = '\'' . $v . '\'';
}
}
$sql = str_replace($search, $replace, $sql);
return $sql;
}
}