Yii2 DataFilter builds wrong condition - php

I have to make filtering by date in reqest. I know about existing yii\data\DataFilter class, so I used it to solve the issue.
Actual request URL(example): https://api.site.com/module/post?filter[from_date][>=]=100
Don't worry about value 100, we use UNIX in our policy.
I use yii\rest\ActiveController to perform actions, so I defined dataFilter property in [[actions()]]:
public function actions()
{
$actions = parent::actions();
$actions['index']['dataFilter'] = [
'class' => DataFilter::class,
'attributeMap' => [
'from_date' => 'date_success',
'to_date' => 'date_success',
],
'searchModel' => function () {
return (new DynamicModel(['from_date', 'to_date']))
->addRule(['from_date', 'to_date'], 'integer', ['min' => 0]);
},
];
return $actions;
}
After the query is completed, an empty array is returned []. I traced SQL queries:
As you can see there is "EQUALS" operand instead ">", which defined in query-string ?filter[from_date][>]=100.
By default in yii\rest\IndexAction variable $query calls method where($filter):
/*
After successful filter build $filter variable becomes:
$filter = [
'date_success' => [
'>' => 100,
],
]
*/
$query = $modelClass::find();
if (!empty($filter)) {
$query->andWhere($filter);
}
Why it's happens? I debugged the code, and I find one interesting feature! In yii\db\conditions\HashConditionBuilder:
public function build(ExpressionInterface $expression, array &$params = [])
{
$hash = $expression->getHash();
$parts = [];
foreach ($hash as $column => $value) {
if (ArrayHelper::isTraversable($value) || $value instanceof Query) {
// IN condition
// Executing will be here.
// Yii2 thinks thats 'IN' condition, and builds as 'IN'.
$parts[] = $this->queryBuilder->buildCondition(new InCondition($column, 'IN', $value), $params);
} else {
if (strpos($column, '(') === false) {
$column = $this->queryBuilder->db->quoteColumnName($column);
}
if ($value === null) {
$parts[] = "$column IS NULL";
} elseif ($value instanceof ExpressionInterface) {
$parts[] = "$column=" . $this->queryBuilder->buildExpression($value, $params);
} else {
$phName = $this->queryBuilder->bindParam($value, $params);
$parts[] = "$column=$phName";
}
}
}
return count($parts) === 1 ? $parts[0] : '(' . implode(') AND (', $parts) . ')';
}
This problem repeats with different 'conditionOperators', the result absolutely same.
filter[from_date][=]=100
filter[from_date][<]=100
filter[from_date][gt]=100
filter[from_date][gte]=100

There were one sticky difference between ActiveDataFilter and DataFilter. It concluse in method [[buildInternal()]].
DataFilter method:
protected function buildInternal()
{
return $this->normalize(false);
}
ActiveDataFilter method:
protected function buildInternal()
{
$filter = $this->normalize(false);
if (empty($filter)) {
return [];
}
return $this->buildCondition($filter);
}
By calling $this->buildCondition($filter) ActiveDataFilter passes $filter variable and then applyes QueryBilders, so $filter variable becomes:
$filter = ['>', 'date_success', 100];

Related

Problem of Curly Brackets in my controller Php Symfony

I want to call my function but when I call it I have a problem with curly Brackets at the end of my code and i have this error Error SYMFONY ( {} ) in my Controller.
I have no idea where to put them for my code to work. I have this problem when I add my function that allows me to retrieve the
history of the action. The mentioned function goes as this:
$this->logHistory->addHistoryConnection($project->getId(), $user->getId(), 'Delete Local Suf', $sf_code);
Function Supp Suf
/**
* #Route("/creation/suf/supp", name="suf_supp")
*/
public function suf(
Request $request,
ShapesRepository $shapesRepository
) {
$params = $this->requestStack->getSession();
$projet = $params->get('projet');
$modules = $params->get('modules');
$fonctionnalites = $params->get('fonctionnalites');
$user = $this->getUser()->getUserEntity();
$manager = $this->graceManager;
$mapManager = $this->mapManager;
$countElements = $mapManager->getCount();
$shapes = $shapesRepository->findBy(array('projet' => $projet->getId()));
$adresseWeb = $this->getParameter('adresse_web');
$carto = $params->get('paramCarto');
$centrage = $params->get('centrage');
$cableColor = $params->get('cableColor');
$sf_code = '';
if ($request->get('suf') != '') {
$sf_code = $request->get('suf');
}
$suf = $manager->getSuf($sf_code);
$success = '';
$error = '';
$warning = '';
if ($request->query->get('success')) {
$success = $request->query->get('success');
} elseif ($request->query->get('error')) {
$error = $request->query->get('error');
} elseif ($request->query->get('warning')) {
$warning = $request->query->get('warning');
}
if ($request->isMethod('POST')) {
if ($request->request->get('sf_code') != '') {
$sf_code = $request->request->get('sf_code');
}
if ($request->get('val') != '') {
$val = $request->get('val');
}
$dir = $this->getparameter('client_directory');
$dossier = str_replace(' ', '_', $projet->getProjet());
$dir = $dir . $dossier . '/documents/';
$cable = $val[0];
$chem = $val[1];
$t_suf = $this->graceCreator->supprimeSuf($sf_code, $cable, $chem);
if ($t_suf[0][0] == '00000') {
$this->logHistorique->addHistoryConnection($projet->getId(), $user->getId(), 'Suppression Suf Local', $sf_code);
// $creator->delDirObjet( $st_code, $dir );
$data = new JsonResponse(array("success" => "create!"));
return $data;
} else {
$data = new JsonResponse(array("error" => "Error : " . $t_suf));
return $data;
}
return $this->render('Modifications/supSuf.html.twig', array(
'user' => $user,
'paramCarto' => $carto,
'cableColor' => $cableColor,
'suf' => $suf,
'adresseWeb' => $adresseWeb,
'centrage' => $centrage,
'shapes' => $shapes,
'projet' => $projet,
'modules' => $modules,
'fonctionnalites' => $fonctionnalites,
'countElements' => $countElements
));
}
}
Your only return statement is inside of an if condition. If the code does not pass the condition, it has nothing to return. The code must return something in all possible cases. If you are not still used to these practices, an IDE might guide you until it becomes a routine. PHPStorm is my personal preference.
BTW, I recommend you to switch from the array() syntax to the more globally accepted [] although you must be in PHP 5.4 or higher.

How do I return a variable instead of a string?

In this example, the function validateJsonNotEmptyImage does not work as expected?
Just to clarify.
'src' => $image->src, // WORKS
'height' => $this->validateJsonNotEmptyImage('type', 'width'), // DOES NOT WORK
public function validateJsonNotEmptyImage($type, $array1) {
if ($type, $array1) {
if (isset($type->$array1)) {
return $type . '->' . $array1;
} else {
return null;
}
}
}
$productsImagesToSiteArray = array();
foreach ($this->resource->images as $image) {
$productsImagesToSiteArray[] = array(
'height' => $this->validateJsonNotEmptyImage('$image', 'width'),
'src' => $image->src,
);
}
I see two mistakes
1) your method validateJsonNotEmptyImage, in body you have wrong IF it should be check with null[here1] and you should return value not string[here2].
And also you should return everytime any value or null[here3].
public function validateJsonNotEmptyImage($type, $array1) {
if ($type != null && $array1 != null) { <----------- here1
if (isset($type->$array1)) {
return $type->$array1; <----------- here2
} else {
return null;
}
}
return null; <----------- here3
}
2) you should pass variable not string in your call (remove quotes from $image)
$this->validateJsonNotEmptyImage($image, 'width')

CakePHP allow searching by field using API?

I am trying to create an API using CakePHP that allows searching. For example:
http://localhost:8765/users/index/?username=admin
Which should return users with usernames equal to 'admin':
users: [
{
id: 3,
username: "admin",
image: "",
firstName: "Jeremy",
lastName: "Quick",
userTypeId: 1,
email: "jrquick#test.com",
groupId: 2
}
]
So far, I have been able to accomplish this with a custom get() in the AppController which checks the $_GET and $_POST array for fields on the model. But the function is getting more and more complicated and verging on hackiness as I add more functionality (range search, collection search, and child table filtering). Is there a better, more CakePHP friendly way of accomplishing this? Whether through pure cakephp or a plugin?
I think you want to use the Cakephp Search plugin. It has good documentation and uses a PRG method similar to what you are currently using. It will function just fine through an API. Here's a link to that plugin: github.com/FriendsOfCake/search
If You want to create API, You should create a MiddleWare at first, which will filter tokens, keys etc. to make Your API more protected.
Also, You should use Plugins and RESTful Routes, which will be very helpful.
To create plugin:
bin/cake bake plugin Api
Create Model:
bin/cake bake model Users
For example, You want to have UsersController in Api plugin:
<?php
namespace Api\Controller;
/* This controller will be extending like parent */
use Api\Controller\AppController;
use Api\Model\Table\UsersTable;
/**
* Class UsersController
* #package Api\Controller
* #property UsersTable $Users
*
*/
class UsersController extends AppController{
public function initialize(){
parent::initialize();
$this->loadModel('Api.Users');
}
public function getUser($field ='username', $username = false){
return $this->_jsonResponse(
[
'users' => $this->Users->findBy{ucfirst($field)}($username)
];
)
}
public function _jsonResponse($data, $code = 200){
$this->response->type('json');
$this->response->statusCode($code);
$this->response->body(
json_encode((array)$data)
);
return $this->response;
}
}
Route will be descripbed in plugins/config/routes.php. You need to create Route Map for API in /api path:
function (RouteBuilder $routes) {
$routes->resources('Users', [
'map' => [
'get-user' => [
'action' => 'getUser',
'method' => 'GET' /* Can be also as array ['GET', 'PUT', 'DELETE'] */
]
]
]);
$routes->fallbacks('DashedRoute');
}
If You have frequent calls, You should use Cache that calls and save them for some amount of time. For example - 10 minutes. Cache can be configured in config/app.php. You should create separate Cache prefix and use it in this way:
<?php
use Cake\Cache\Cache;
$data = [];
Cache::write('some_key', $data, 'prefix')
dump(Cache::read('some_key', 'prefix'));
It's just examples. If You will face some problems - just tell in comments :)
Also, use Migrations and Seeds instead dumping sql files
If You want to filter data from Middleware - You should have Event as argument, that will contain request data ($_POST) and request query($_GET) variables that You will be able to easily handle with.
From controllers You need to use $this->request->data to get POST data array or $this->request->query to get GET data array.
I haven't found an answer that seems to work exactly how I am wanting, so here is my current get command. It does allow searching by fields, join tables, greater/less than, in array, and like.
If anyone has recommendations to improve I will update my answer.
public function get() {
$response = new Response();
$model = $this->loadModel();
$fields = $this->getFields();
$joins = $this->getJoins();
$order = $this->getOrder();
$params = $this->getParams();
$limit = $this->getLimit();
$offset = $this->getOffset();
$query = $model->find('all', ['fields' => $fields]);
if (!is_null($joins)) {
$query->contain($joins);
}
if (sizeof($params['equals']) > 0) {
foreach ($params['equals'] as $equalsKey=>$equalsValue) {
$query->andWhere([$equalsKey => $equalsValue]);
}
}
if (sizeof($params['or']) > 0) {
foreach ($params['or'] as $orKey=>$orValue) {
$query->orWhere([$orKey => $orValue]);
}
}
if (!is_null($order)) {
$query->order([$order]);
}
if (!is_null($limit)) {
$query->limit($limit);
if (!is_null($offset)) {
$query->offset($offset);
}
}
$response->addMessage($model->table(), $query->toArray());
$response->respond($this);
}
private function getFields() {
$fields = [];
if (array_key_exists('fields', $_GET)) {
$fields = explode(',', $_GET['fields']);
}
return $fields;
}
private function getLimit() {
$limit = null;
if (array_key_exists('limit', $_GET)) {
$limit = $_GET['limit'];
}
return $limit;
}
private function getJoins() {
$joins = null;
if (array_key_exists('joins', $_GET)) {
$joins = explode(',', $_GET['joins']);
}
return $joins;
}
private function getOffset() {
$offset = null;
if (array_key_exists('offset', $_GET)) {
$offset = $_GET['limit'];
}
return $offset;
}
private function getOrder() {
$results = [];
if (array_key_exists('order', $_GET)) {
$orders = explode(',', $_GET['order']);
foreach ($orders as $order) {
$sign = substr($order, 0, 1);
$direction = 'ASC';
if (in_array($sign, ['+', '-'])) {
if ($sign === '-') {
$direction = 'DESC';
}
$order = substr($order, 1);
}
$result = $order;
if (strpos($result, '.') === false) {
$result = $this->loadModel()->alias() . '.' . $order;
}
$result = $result . ' ' . $direction;
$results[] = $result;
}
}
return (sizeof($results) == 0) ? null : implode(',', $results);
}
private function getParams() {
$params = [
'equals' => [],
'or' => []
];
$parentModel = $this->loadModel();
$array = array_merge($_GET, $_POST);
foreach ($array as $field=>$value) {
$comparisonType = 'equals';
$operator = substr($field, strlen($field) - 1);
if (in_array($operator, ['!', '>', '<'])) {
$field = substr($field, 0, strlen($field) - 1);
$operator .= '=';
} else if (in_array($operator, ['|'])) {
$field = substr($field, 0, strlen($field) - 1);
$comparisonType = 'or';
$operator = '=';
} else if (in_array($operator, ['%'])) {
$field = substr($field, 0, strlen($field) - 1);
$operator = 'LIKE';
$value = '%'.$value.'%';
} else {
$operator = '=';
}
if ($value == 'null') {
$operator = (strpos($operator, '!') === false) ? 'IS' : 'IS NOT';
$value = null;
}
$field = str_replace('_', '.', $field);
if (strpos($field, '.') === false) {
$alias = $parentModel->alias();
} else {
$fieldExplosion = explode('.', $field);
$alias = $fieldExplosion[0];
$field = $fieldExplosion[1];
}
$model = null;
if ($parentModel->alias() !== $alias) {
$association = $parentModel->associations()->get($alias);
if (!is_null($association)) {
$model = $this->loadModel($association->className());
}
} else {
$model = $parentModel;
}
if (!is_null($model)) {
if ($model->hasField(rtrim($field, 's')) && !$model->hasField($field)) {
$field = rtrim($field, 's');
$value = '(' . $value . ')';
$operator = ' IN';
}
if ($model->hasField($field)) {
$params[$comparisonType][$alias.'.'.$field . ' ' . $operator] = $value;
}
}
}
return $params;
}

How to find all conditional both false and true in nested conditional by using PHP and return it to array

I'm create a function to insert and update with many conditional and I want to find out all that conditional both false and true and return it as array to client for checking if some data is missing or can't update or insert.
I've used $return to find out which one conditional is work or not.
public function trans_issue_till() {
if (Request::ajax()) {
$return = [];
$rule = [
'till_account_id' => 'required',
'from_account' => 'required',
];
$fromacc = Request::input('from_account');
if ($this->CheckPermId_from_session(90) == true) {
$fromacc = Request::input('coa_name');
}
$data = [
'till_account_id' => Request::input('till_account_id'),
'from_account' => $fromacc,
];
$v = Validator::make($data, $rule);
if ($v->fails()) {
$return['form'] = false;
} else {
$update = Teller::where('id', '=', $data['till_account_id'])->update(array('balance' => $data['balance']));
$insert = DB::table('till_transaction')->insert($data);
if(!$update) {
$return['upd_bl'] = false;
} if(!$insert){
$return['ins_trans'] = false;
}else{
$return;
}
}
} else {
$return['ins_trans'] = 'login';
return redirect()->route('login');
}
foreach($return as $items){
var_dump($items);
}
return $...........
}
as the final sentence I used foreach for check all the array index which one is true and false after that return to browser.

php passing arguments in constructor

Can anyone tell me why $Role is being ignored?
I am trying to pass an argument and it is always getting a null value, however, when I call the method the var_dump shows that the $Role is 2.
When I use the var_dump inside getListFromDB the $Role is being set to null.
Method getListFromDB()
function getListFromDB($tableName, $orderBy = 'Description', $where = null, $Role = null) {
DO_Common::debugLevel(0);
if (empty($tableName) || empty($orderBy))
throw new Exception("tableName and orderBy cannot be left empty");
var_dump($Role);
if (!empty($Role))
{
echo "here";
if ($Role === 2)
{
if ($tableName == 'AssetTypes')
{
$params = array('tableName' => 'AssetTypes',
'orderBy' => $orderBy,
'whereAdd' => 'Restricted = 1');
}
var_dump($params);
}
else
{
$params = array('tableName' => $tableName,
'orderBy' => $orderBy);
var_dump($params);
}
}
else
{
$params = array('tableName' => $tableName,
'orderBy' => $orderBy);
//var_dump($params);
}
if (!empty($where) && $table != 'AssetTypes') {
if (strpos(strtolower($where), 'flag') === false)
$where .= " AND Flag != " . fDELETED;
$params += array('whereAdd' => $where);
}
return DO_Common::toAssocArray($params);
}
How the method is being called:
$AssetTypesOptions = getListFromDB('AssetTypes', $Role);
Is there something I am missing here?
$Role is the fourth argument for the function but you are sending it as the second argument:
$AssetTypesOptions = getListFromDB('AssetTypes', 'Description', null, $Role);
You should call it as the fourth argument, like so:
getListFromDB("tablename", "some fancy description", "here", $Role);
I made them up obviously...

Categories