How to debug a query in extbase? - mysql

$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;
}
}

Related

PhpStorm v2017.3.4 & Codeception v2.4.0 incompatibility

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

wordpress json rest API to get custom field data

I am currently using the JSON REST API (WP API) plug-in to get my post and page data.
I have noticed that none of my custom field data is returned in the json, and looking at the routes, i don't think i can obtain these.
Any ideas via the current plug-in, or how I can accomplish this otherwise?
If you are using 'advanced custom fields' - until something more official is decided, you can use this plugin: https://github.com/times/acf-to-wp-api (and now on the shelf in standard wp plugin area too.)
It will include the custom fields under acf: [], in your json structure.
To grab a custom field value using native WP functions only, add the following to your functions.php
function my_rest_prepare_post( $data, $post, $request ) {
$_data = $data->data;
$_data[$field] = get_post_meta( $post->ID, 'my_custom_field_key', true );
$data->data = $_data;
return $data;
}
add_filter( 'rest_prepare_post', 'my_rest_prepare_post', 10, 3 );
Replace 'my_custom_field_key' with your custom field key name.
For multiple fields:
function my_rest_prepare_post( $data, $post, $request ) {
$_data = $data->data;
// My custom fields that I want to include in the WP API v2 responce
$fields = ['writer', 'publisher', 'year', 'youtube_link'];
foreach ( $fields as $field ) {
$_data[$field] = get_post_meta( $post->ID, $field, true );
}
$data->data = $_data;
return $data;
}
add_filter( 'rest_prepare_post', 'my_rest_prepare_post', 10, 3 );
You need to create this file contain following code in
wp-content\themes\name\inc\functions
<?php
if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
/*
* init function
*/
if ( ! function_exists( 'mnu_rest_init' ) ) {
function mnu_rest_init() {
register_rest_route( 'guider/v1', '/booking', array(
'methods' => 'GET',
'callback' => 'handle_get_all',
'permission_callback' => function () {
return current_user_can( 'edit_others_posts' );
}
) );
register_rest_route( 'guider/v1', '/booking', array(
'methods' => 'POST',
'callback' => 'handle_post_booking',
'permission_callback' => function () {
return current_user_can( 'edit_others_posts' );
}
) );
}
}
//GET QUERY PARMS
function handle_get_all( $request_data) {
$parameters = $request_data->get_params();
$userId = $parameters["Id"];
global $wpdb;
$query = "SELECT * FROM `wp_trav_tour_bookings` WHERE `user_id` = $userId";
$list = $wpdb->get_results($query);
return $list;
}
// GET BODY PARMS
function handle_post_booking( $request_data) {
$parameters = $request_data->get_body();
$params = json_decode( $parameters , true );
// $userId = $parameters["Id"];
// global $wpdb;
// $query = "SELECT * FROM `wp_trav_tour_bookings` WHERE `user_id` = $userId";
// $list = $wpdb->get_results($query);
return $params ;
}
then you need to add
//actions
add_action( 'rest_api_init', 'mnu_rest_init');
to your main.php in
wp-content\themes\name\inc\functions
to do that you need to require this file to main.php
require_once dirname( __FILE__ ) . '/filename.php';
You can manipulate the response and add custom fields to the JSON. I'm using Advanced Custom Fields in my example but you can just add any key/value-pairs to the data object before returning it.
// In functions.php
function modify_rest_post( $data, $post, $request ) {
if (is_admin()) {
return $data;
}
$data->my_favorite_data = get_field('my_custom_field', $post->ID);
return $data;
}
add_filter( 'rest_prepare_post', 'modify_rest_post', 10, 3 );

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

JOIN on a subquery with Zend Framework 2 TableGateway

I'm trying to do a query with Zend Framework 2 where I have a SELECT inside a JOIN statement. So far, here's what I've tried, but injecting the SELECT object into the first parameter of join() doesn't seem to be working. I've resorted to such an approach since I need to order the results first before doing any grouping. Any ideas on how to get it working?
public function getSearchKeyword($keyword, $limit)
{
$select = $this->keywords->getSql()->select();
$subquery = $this->pages->getSql()->select();
$subWhere = new \Zend\Db\Sql\Where();
$subWhere->equalTo('delete_flag', 'n')
->equalTo('published_flag', 'y');
$subquery->where($subWhere);
$where = new \Zend\Db\Sql\Where();
$where->like('keyword', '%' . $keyword . '%')
->equalTo('delete_flag', 'n');
$select->columns(array('display' => 'keyword', 'url'))
->join(array('sub' => $subquery), 'sub.page_id = keywords.page_id', array())
->where($where)
->group(array('keywords.page_id', 'keywords.keyword'))
->order(array('rank', 'keyword'))
->limit($limit);
$row = $this->tableGateway->selectWith($select);
return $row;
}
The query I'm trying to write is below:
SELECT keywords.keyword AS display, keywords.url
FROM keywords
INNER JOIN
(
SELECT * FROM pages WHERE published_flag = 'y' AND delete_flag = 'n' ORDER BY page_id DESC
) pages
ON pages.page_id = keywords.page_id
WHERE published_flag = 'y'
AND delete_flag = 'n'
AND keywords.keyword LIKE '%?%'
GROUP BY display, page_id;
I was working around the same problem and did not found a standard way to solve it. So I got a working but not zf2 standard one
Create a small interface to mannage Db conections
Implements it as a small class to get a connection PDO object to
your database
execute your arbitrary querys
Code sample
// Filename: /module/MyTools/src/MyTools/Service/DbModelServiceInterface.php
namespace MyTools\Service;
interface DbModelServiceInterface
{
/**
* Will return the result of querying the curret database
*
* #param type $query
* #result mixed
*/
public function dbQuery($query);
/**
* Will return a connection object that links to curret database
*
* #result mixed
*/
public function getConnection();
}
The class implementing the interface. It creates and offers a PDO connection. Note: It needs extra code to close conns and to perfeorm security adm...
It test it and is completely functional.
code:
// Filename: /module/MyTools/src/MyTools/Service/DbModelServiceMySql.php
namespace MyTools\Service;
use MyTools\Service\DbModelServiceInterface;
use PDO;
class DbModelServiceMySql implements DbModelServiceInterface
{
protected $driverConfig;
protected $connection;
protected $isconnected = FALSE;
protected $dbname = '';
/**
* Creates a connection to main database
*/
public function __construct()
{
$driverConfig = self::getDriverDef();
$this->driverConfig = $driverConfig; // new PDO($driverConfig['dsn'], $driverConfig['username'], $driverConfig['password']);
$this->_connect();
}
protected function _connect(){
$dsn = (isset($this->driverConfig['dsn'])) ? $this->driverConfig['dsn'] : '';
$username = (isset($this->driverConfig['username'])) ? $this->driverConfig['username'] : '';
$password = (isset($this->driverConfig['password'])) ? $this->driverConfig['password'] : '';
if( ($dsn) && ($username) && ($password)){
$options = [PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8', ];
try {
$this->connection = new PDO($dsn, $username, $password, $options);
$this->isconnected = TRUE;
$this->_setdbname($dsn);
} catch (Exception $ex) {
throw new RuntimeException('YOUR ERROR MESSAGE.');
}
}
return $this->isconnected;
}
protected function _setdbname($dsn){
if($dsn){
$chunks = explode(';', ''.$dsn);
foreach($chunks as $chunk){
if(strpos('***'.$chunk, 'dbname') > 2){
$nombre = explode('=', $chunk);
$this->dbname = $nombre[1];
break;
}
}
}
}
/**
* {#inheritDoc}
*/
public function dbQuery($query) {
if($this->connection){
$resultset = $this->connection->query($query);
if($resultset){
return $resultset->fetchAll(PDO::FETCH_ASSOC);
}else{
return ['Error' => 'YOUR CUSTOM ERROR MESSAGE.'];
}
}else{
return ['Error' => 'OTHER CUSTOM ERROR MESSAGE'];
}
}
public static function getDriverDef()
{
$autoloadDir = __DIR__ . '../../../../../../config/autoload/';
$credentialsdb = include $autoloadDir . 'local.php';
$globaldb = include $autoloadDir . 'global.php';
$def = (isset($globaldb['db'])) ? $globaldb['db'] : array();
$credentials = (isset($credentialsdb['db'])) ? $credentialsdb['db'] : $credentialsdb;
return array_merge($def, $credentials);
}
/**
* {#inheritDoc}
*/
public function getConnection() {
if($this->connection){
return $this->connection;
}else{
return 'Error: YOUR CUSTOM ERROR MESSAGE';
}
}
/**
* {#inheritDoc}
*/
public function getDbName(){
return $this->dbname;
}
}
Now you have a class you can instantiate elsewhere to perform the querys you need.
use:
code:
$myQuery = 'the very very complex query you need to execute'
$myDbConn = new MyTools\Service\DbModelServiceMySql();
$result = $myDbConn->dbQuery($myQuery);
If success you got a resulset array of pairs columnName => value
You can try this one.
$select->columns(array('display' => 'keyword', 'url'))
->join(array('sub' => 'pages'), 'sub.page_id = keywords.page_id',
array(), $select::JOIN_INNER)
->where($where)
->group(array('keywords.page_id', 'keywords.keyword'))
->order(array('rank', 'keyword'))
->limit($limit);
In your code, you are getting all keywords which page_id's is in sub page_id where delete_flag = 'n' and published_flag = 'y'.
join(..., 'sub.page_id = keywords.page_id', array())
When you don't need any columns of pages table, you can use IN instead of JOIN.
For example when you need to know which keywords are in which pages, you should use JOIN, but when you need to know which keyboards are in any pages, you can use IN statement.
Anyway :
There is no standard way in ZF2 but you can try following code.
public function getSearchKeyword($keyword, $limit)
{
$select = $this->keywords->getSql()->select();
$subquery = $this->pages->getSql()->select();
$subWhere = new \Zend\Db\Sql\Where();
$subWhere->equalTo('delete_flag', 'n')
->equalTo('published_flag', 'y');
$subquery->columns(array('page_id'))
->where($subWhere);
$where = new \Zend\Db\Sql\Where();
$where->like('keyword', '%' . $keyword . '%')
->equalTo('delete_flag', 'n')
->in('keywords.page_id', $subquery);
$select->columns(array('display' => 'keyword', 'url'))
->where($where)
->group(array('keywords.page_id', 'keywords.keyword'))
->order(array('rank', 'keyword'))
->limit($limit);
$row = $this->tableGateway->selectWith($select);
return $row;
}
I've faced a similar issue. Since the FROM table and Subquery's FROM table were different i got an error.
My workaround was to extract the SQL and create a statement.
$sql = $select->getSqlString(new \Zend\Db\Adapter\Platform\Mysql());
$stmt = $this->getAdapter()->createStatement($sql);
$stmt->prepare($sql);
$result = $stmt->execute();
$resultSet = new ResultSet(); \\ Class Zend\Db\ResultSet\ResultSet
$resultSet->initialize($result);

query function in cake php

I am writing code in php to basically 'map' data from a mySQL database to another database. I am using the code as follows:
$results = $this->query("select PT_FS_DATA_ID from PATIENT_FLOWSHEET_DATA where
DT_LAST_UPDATED_TIME = (select top 1 DT_LAST_UPDATED_TIME from PATIENT_FLOWSHEET_DATA
order by DT_LAST_UPDATED TIME desc) group by PT_FS_DATA_ID;");
however, I am getting an error:
syntax error, unexpected T_VARIABLE, expecting T_FUNCTION
Everywhere I look this seems to be the correct syntax. Is there something I'm missing here?
I tried putting the controller in there as well $this->controllerName->query, but that didn't work either.
Full Code:
class CaExtraFlowsheetFields extends CaBase {
public $name = 'CaExtraFlowsheetFields';
/*
NOTE: This is to take all the fields in flowsheet and
maps their id's.
*/
//public $useTable = 'ANSWER_ENTRY';
public $useTable = 'PATIENT_FLOWSHEET_DATA';
public $primaryKey = 'PT_FS_DATA_ID';
protected function getPrimaryKeyValue(
$hospital_id,
$patient_id,
$admission_id = null
) {
return $patient_id;
}
//*CHANGE BEGIN*
$results = $this->query("select PT_FS_DATA_ID from PATIENT_FLOWSHEET_DATA where
DT_LAST_UPDATED_TIME = (select top 1 DT_LAST_UPDATED_TIME from PATIENT_FLOWSHEET_DATA
order by DT_LAST_UPDATED TIME desc) group by PT_FS_DATA_ID;");
protected $filedMethodMappings = array(
'Method_GO' => array(
CaBase::KEY_MAPPING_LOGIC_COMPLEXITY => CaBase::LEVEL2_COMPLEXITY,
CaBase::KEY_FIELD_LOGIC_NAME => 'wsMethod_GO',
);
//########################################################################//
//Note[]>Block[] //
//>Method that calls LookUpField for every field in flowsheet // //
//########################################################################//
public function wsMethod_GO ($params) {
foreach($results as $value){
$questionName = ''.$value;
$msg_prefix = $this->name . "::" . __FUNCTION__ . ": ". "arrivez-vouz" ;
$ret = $this->wsLookUpField($params,$questionName,$msg_prefix);
return $ret;
}
unset($value);
}
//########################################################################//
public function wsLookUpField($params,$questionName,$msg_prefix){
$arrayValues=array();
try{
$hospital_id = $params[Constants::KEY_HOSPITAL_ID];
$patient_id = $params[Constants::KEY_PATIENT_ID];
$admission_id = $params[Constants::KEY_ADMISSION_ID];
$msg_prefix = $this->name . "::" . __FUNCTION__ . ": ". "attendez-vouz: l'hopital= ".$hospital_id.
" patient= ".$patient_id." admission= ".$admission_id;
//shows info about given question name
$msg_prefix = "*!*!*!*Show me ---> ".$questionName." : ".$answer_entry_id.
" = aic: " .$answer_id_check;
$ret = array();
//now with needed fields, grab the A_NAME:
$params = array(
'conditions' => array(
$this->name . '.PID' => $patient_id,
$this->name . '.PT_FS_DATA_ID' => $questionName,
),
'order' => array(
$this->name . '.' . $this->primaryKey . ' DESC'
),
'fields' => array(
$this->name . '.FS_VALUE_TEXT',
)
);
$rs = $this->find('first', $params);
/* check to make sure $rs has received an answer from the query
and check to make sure this answer is a part of the most recent
database entries for this note */
if (false != $rs) {
try {
$msg = $msg_prefix . "Data obtained successfully."."<br>".$result;
$result = $rs;
$ret = WsResponse::getResponse_Success($msg, $result);
} catch (Exception $e) {
$msg = $msg_prefix . "Exception occurred.";
$ret = WsResponse::getResponse_Error($msg);
}
/*answer was not part of most recent database entries, meaning no
answer was given for this particular question the last time this
particular note was filled out. Message is given accordingly.*/
} else {
$msg = $msg_prefix . "/No answer given.";
$ret = WsResponse::getResponse_Error($msg);
}
} catch (Exception $e) {
$msg = $msg_prefix . "Exception occurred.";
$ret = WsResponse::getResponse_Error($msg);
}
return $ret;
}
Here is what you are doing:
class ABC {
$result = 'whatever';
}
You can't declare a variable there!
Code needs to be inside a method/function...
class ABC
{
public function wsMethod_GO ($params)
{
$result = 'whatever';
}
}