I want to extend the Doctrine Parser: Doctrine\ORM\Query\Parser. In detail I want to add a ComparisonOperator that I need for a custom Query.
Is this possible without changing the File?
In general it is not possible to extend the Parser
But it is possible to create a workaround to add the #> functionality.
class JsonbContains extends FunctionNode
{
public $identifier = null;
public $value = null;
public function parse(\Doctrine\ORM\Query\Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$this->identifier = $parser->ArithmeticPrimary();
$parser->match(Lexer::T_NONE);
$parser->match(Lexer::T_GREATER_THAN);
$this->value = $parser->ArithmeticPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
{
return '(' .
$this->identifier->dispatch($sqlWalker) . ' #> ' .
$this->value->dispatch($sqlWalker) .
')';
}
}
SELECT p
FROM AppBundle:Project p
WHERE JSONB_CONTAINS( p.values #> '{\"ACTIVE\": \"1\"}' ) = TRUE
Related
I'm trying to write test code for monosilic code like below.
Q1. How to write test code which has access to DB?
Q2. How to refactor these code testable?
Q3. Is there any way to write test code with fewer change to production code?
I need your help!
Thanks!!
Example Production Code)
<?
class Sample_Model_Service_A
{
private $_result
private $_options
private $_someValue
public function __construct($params, $ids, $data) {
$this->_options = Sample_Model_Service_B::getOption($data);
}
private function setSomeValue() {
// some code shaping $_params to $someValue with $this->_options
$this->_someValue= $someValue;
}
// want to write test for this function
// changed this function's logic
private function setResult() {
// some code shaping $_someValue to $result
$this->_result = $result;
}
public function getter() {
retrn $this->_result;
}
}
?>
<?
class Sample_Model_Service_B
{
// get option from DB
public static function getOption($data) {
$dao = new Model_Dao_Option();
$option = $dao->getOption($data['id']);
return $option;
}
}
?>
My Test Code so far)
public function testsetResult()
{
// just make sure these variables are defined
$params = $ids = $data = [];
// try to make test for private function
$sample = new Sample_Model_Service_A($params, $ids, $data);
$reflection = new ReflectionClass($sample);
// get Method
$method = $reflection->getMethod('setresult');
$method->setAccessible(true);
// wondering how to get $result
$result = $method->invoke($sample);
// assert
$this->assertSame($result);
}
Mockery solved my issue.
Sample Code)
/**
* #dataProvider sampleProvider
*/
public function testsetResult($sampleData)
{
// mock Sample_Model_Service_B
$mockSample_Model_Service_B = Mockery::mock('alias:' . Sample_Model_Service_B::class);
$mockSample_Model_Service_B->shouldReceive('getOption')->andReturn($sampleData['option']);
$sample = new Sample_Model_Service_A($sampleData['params'], $sampleData['ids'], $sampleData['data']);
$sample->setResult();
$result = $sample->getter();
// assert
$this->assertSame($result, $sampleData['result']);
}
I've constructed the following regex:
(public\s\$log_path\s=\s'\/var\/www\/)(.*?)(?=\/)(.*)
I'm wanting to match the public $log_path line in the config file below and replace it using the following pattern:
$1test/$2$3
Which should give me:
public $log_path = '/var/www/test/administrator/logs';
My Perl one-liner is:
perl -pe "s/(public\s\$log_path\s=\s'\/var\/www\/)(.*?)(?=\/)(.*)/$1test$2$3/" <file-name>.php
But this doesn't seem to change anything and just prints the file back to the console.
The file contents are below:
<?php
class JConfig {
public $MetaAuthor = '1';
public $MetaDesc = '';
public $MetaKeys = '';
public $MetaRights = '';
public $MetaTitle = '1';
public $MetaVersion = '0';
public $access = '1';
public $cache_handler = 'file';
public $cachetime = '15';
public $caching = '0';
public $captcha = '0';
public $cookie_domain = '';
public $cookie_path = '';
public $log_path = '/var/www/website/administrator/logs';
}
Any pointers for where I'm going wrong?
Use single quotes and define the single quote between double quotes:
perl -pe 's/(public\s\$log_path\s=\s'"'"'\/var\/www\/)(.*?)(?=\/)(.*)/$1test$2$3/' <file-name>.php
You're definitely getting hung up in the shell quoting, which can be nasty on the best of days. This works under Bash:
perl -pe 's{^(\s*public\s+\$log_path\s+=\s+./var/www)/}{$1/test/};' test.php
It simplifies the escaping of the front-slashes by using alternate delimiters in the Perl substitution, and I cheat on the matching of the single-quote by matching anything in that position.
</tmp> $ cat test.php
<?php
class JConfig {
public $MetaAuthor = '1';
public $MetaDesc = '';
public $MetaKeys = '';
public $MetaRights = '';
public $MetaTitle = '1';
public $MetaVersion = '0';
public $access = '1';
public $cache_handler = 'file';
public $cachetime = '15';
public $caching = '0';
public $captcha = '0';
public $cookie_domain = '';
public $cookie_path = '';
public $log_path = '/var/www/website/administrator/logs';
}
</tmp> $ perl -pe 's{^(\s*public\s+\$log_path\s+=\s+./var/www)/}{$1/test/};' test.php
<?php
class JConfig {
public $MetaAuthor = '1';
public $MetaDesc = '';
public $MetaKeys = '';
public $MetaRights = '';
public $MetaTitle = '1';
public $MetaVersion = '0';
public $access = '1';
public $cache_handler = 'file';
public $cachetime = '15';
public $caching = '0';
public $captcha = '0';
public $cookie_domain = '';
public $cookie_path = '';
public $log_path = '/var/www/test/website/administrator/logs';
}
If you really must match the single quote at that position, you can do as #Ryszard Czech suggests and put them in double-quotes:
perl -pe 's{^(\s*public\s+\$log_path\s+=\s+'"'"'/var/www)/}{$1/test/};' test.php
For something like this, I find it best to get it working inside of a shell script first, then trying to convert it into a one-liner; you're not fighting the regex and the shell quoting both at the same time that way.
In my repositories, I have methods with too many arguments (for use in where) :
Example :
class ProchaineOperationRepository extends EntityRepository
{
public function getProchaineOperation(
$id = null, // Search by ID
\DateTime $dateMax = null, // Search by DateMax
\DateTime $dateMin = null, // Search by DateMin
$title = null // Search by title
)
In my controllers, I have differents action ... for get with ID, for get with ID and DateMin, for get ID and Title, ...
My method is too illegible because too many arguments ... and it would be difficult to create many methods because they are almost identical ...
What is the best practice ?
You have two main concerns in your question
You have too many arguments in your repository method which will be used in 'where' condition of the eventual query. You want to organize them in a better way
The repository method should be callable from the controller in a meaningful way because of possible complexity of arguments passed
I suggest you to write a Repository method like:
namespace AcmeBundle\Repository;
/**
* ProchaineOperationRepository
*
*/
class ProchaineOperationRepository extends \Doctrine\ORM\EntityRepository
{
public function search($filters, $sortBy = "id", $orderBy = "DESC")
{
$qb = $this->createQueryBuilder("po");
foreach ($filters as $key => $value){
$qb->andWhere("po.$key='$value'");
}
$qb->addOrderBy("po.$sortBy", $orderBy);
return $qb->getQuery()->getArrayResult();
}
}
The $filters variable here is an array which is supposed to hold the filters you are going to use in 'where' condition. $sortBy and $orderBy should also be useful to get the result in properly sequenced way
Now, you can call the repository method from your controller like:
class ProchaineOperationController extends Controller
{
/**
* #Route("/getById/{id}")
*/
public function getByIdAction($id)
{
$filters = ['id' => $id];
$result = $this->getDoctrine()->getRepository("AcmeBundle:ProchaineOperation")->search($filters);
//process $result
}
/**
* #Route("/getByTitle/{title}")
*/
public function getByTitleAction($title)
{
$filters = ['title' => $title];
$sortBy = 'title';
$result = $this->getDoctrine()->getRepository("AcmeBundle:ProchaineOperation")->search($filters, $sortBy);
//process $result
}
/**
* #Route("/getByIdAndDateMin/{id}/{dateMin}")
*/
public function getByIdAndDateMinAction($id, $dateMin)
{
$filters = ['id' => $id, 'dateMin' => $dateMin];
$sortBy = "dateMin";
$orderBy = "ASC";
$result = $this->getDoctrine()->getRepository("AcmeBundle:ProchaineOperation")->search($filters, $sortBy, $orderBy);
//process $result
}
}
Note that you are calling the same repository method for all controller actions with minor changes according to your parameters. Also note that $sortBy and $orderBy are optionally passed.
Hope it helps!
If your objective is only to query with an AND operator between each properties, the best way could be to use the method proposed by doctrine for that : findBy() cf : this part of the doc
for instance :
$results = $this
->getDoctrine()
->getRepository('AppBundle:ProchaineOperation')
->findBy(array('dateMax' => $myDate, 'title' => 'Hello world');
EDIT : after comment
Then use the same way as Doctrine do : Pass only an array with id, dateMax... as keys if these are set. This should be solve the method signature problem which gives you so much trouble. :)
My Select function of my QueryManager:
/**
* Führt eine SELECT - Query durch
*
* #param $select = array( array(column, [...]), table, shortcut )
* $orderby = array(column, sorting-type)
* $where = array( array( column, value, type[or, and] ), [...] )
* $innerjoin = array( table, shortcut, condition )
* $pagination = array( page, limit )
*
* #return array $data
*/
public function select($select,$orderby, $where, $innerjoin, $pagination)
{
$qb = $this->conn->createQueryBuilder()
->select($select[0])
->from($select[1], $select[2])
;
if ($orderby) {
$qb->orderBy($orderby);
}
if ($where) {
foreach($where as $cond) {
$x = 0;
if ( key($cond) == 0 ) {
$qb
->where($cond[0] . ' = ?')
->setParameter($x,$cond[1]);
}
elseif ( $cond[2] == 'and' ) {
$qb
->andWhere($cond[0] . ' = ?')
->setParameter($x,$cond[1]);
}
elseif ( $cond[2] == 'and' ) {
$qb
->orWhere($cond[0] . ' = :' . $x)
->setParameter($x,$cond[1]);
}
$x++;
}
}
if ($innerjoin) {
$qb->join($select[2],$innerjoin);
}
$this->sql = $qb->getSQL();
$this->totalRowCount = count( $qb->execute() ) ;
if ($pagination) {
$max = $pagination[0] * $pagination[1];
$first = $max - $limit;
$qb
->setFirstResult($first)
->setMaxResults($max)
;
}
$stmt = $qb->execute();
return $stmt->fetchAll();
}
I don't know why, but in action, this function produces a select query without inserted values for the parameters:
/**
* Lädt einen User nach dessen Username
*
* #param $username
* #return User $user | null
*/
public function getUser($username)
{
if($data = $this->select(array('*','users','u'), null, array( array('username',$username) ), null,null)) {
return $user = $this->hydrate($data);
}
return null;
}
I didn't get a result, and the query is not setup correctly:
array(0) { }
SELECT * FROM users u WHERE username = ?
In my opinion the Builder doesn't supstitute my parameters with the provided values ...
I got the latest version of Doctrine DBAL (2.4) and this version should support this features!
Thanks for Help and Suggestions :)
I also had this Problem. I have readed here doctrine 2 querybuilder with set parameters not working that:
You cant bind parameters to QueryBuilder, only to Query
But im creating SQL conditions as collected AND & OR experssions in deep nested objects, and the toppest object creates the query object. So i cant create the query object before, i always return expression objects.
So i solved the problem with direct including the variable into the prepared variable's position.
$qb->where($cond[0] . '=' . $cond[1]);
And because i expect strings there i added hard coded quotes. This is not the desired way, but at the moment i dont know how to solve that in an other way with binding parameters to the QueryBuilder object.
$expr = $d_qb->expr()->between($t_c, "'" . $date_from . "'", "'" . $date_from . "'");
Other suggestions?
Following codes results:
$expr = $d_qb->expr()->between($t_c, ':from', ':to');
$d_qb->setParameter('from', 1);
$d_qb->setParameter('to', 1);
or
$expr = $d_qb->expr()->between($t_c, ':from', ':to');
$d_qb->setParameter(':from', 1);
$d_qb->setParameter(':to', 1);
Results:
e0_.created BETWEEN ? AND ?
I want to recive the Parameter from the JSP to the Skelton class of ApacheAxis2 .
Please see my program below :
package samples.quickstart;
public class StockQuoteServiceSkeleton implements
StockQuoteServiceSkeletonInterface {
public samples.quickstart.xsd.GetPriceResponse getPrice(
samples.quickstart.xsd.GetPrice getPrice0) {
System.out.println(getPrice0.toString());
samples.quickstart.xsd.GetPriceResponse response = new samples.quickstart.xsd.GetPriceResponse();
HashMap map = new HashMap();
map.put("Kiran", "122");
map.put("Kiran", "122");
map.put("Kiran", "122");
map.put("Kiran", "122");
map.put("Kiran", "122");
response.set_return("Kiran");
return response;
}
}
Please help me , thank you .
I found out the answer , may be useful to somebody .
public samples.quickstart.xsd.GetPriceResponse getPrice(
samples.quickstart.xsd.GetPrice getPrice0) {
getPrice0.getSymbol()) + "")
}