I try to use a function which calculate distance between $user lat/lng and coordinates in my BDD with limited distance.
It's an SQL request and I try to use Doctrine to implement it.
Here is my code
$config = new \Doctrine\ORM\Configuration();
$config->addCustomNumericFunction('COS', 'DoctrineExtensions\Query\Mysql\Cos');
$config->addCustomNumericFunction('ACOS', 'DoctrineExtensions\Query\Mysql\Acos');
$config->addCustomNumericFunction('RADIANS', 'DoctrineExtensions\Query\Mysql\Radians');
$config->addCustomNumericFunction('SIN', 'DoctrineExtensions\Query\Mysql\Sin');
$maxLat = $form_citylat + rad2deg($rad / $R);
$minLat = $form_citylat - rad2deg($rad / $R);
$maxLng = $form_citylng + rad2deg(asin($rad / $R) / cos(deg2rad($form_citylat)));
$minLng = $form_citylng - rad2deg(asin($rad / $R) / cos(deg2rad($form_citylat)));
$qb = $this->createQueryBuilder('u')->select('u.lat, u.lng')
->addSelect('acos(sin(:lat)*sin(radians(u.Lat)) + cos(:lat)*cos(radians(u.Lat))*cos(radians(u.lng)-:lng)) * :R As D')
->where('lat Between :minlat And :maxlat And lng Between :minlng And :maxlng And acos(sin(:lat)*sin(radians(u.Lat)) + cos(:lat)*cos(radians(u.Lat))*cos(radians(u.Lng)-:lng)) * :R < :rad')
->setParameter('lat',deg2rad($form_citylat))
->setParameter('lng',deg2rad($form_citylng))
->setParameter('minlat',$minLat)
->setParameter('minlng',$minLng)
->setParameter('maxlat',$maxLat)
->setParameter('maxlng',$maxLng)
->setParameter('rad',$rad)
->setParameter('R',$R)
->orderBy('D');
return $qb->getQuery()->getResult();`
But I got this error message :
[Syntax Error] line 0, col 40: Error: Expected
Doctrine\ORM\Query\Lexer::T_CLOSE_PARENTHESIS, got '.'
I tried different options but it doesn't work.
Anyone has an answer please ?
public function findByThemeAndDistance($theme,$distance,$user){
$latUser=$user->getAddress()->getLnt();
$lngUser = $user->getAddress()->getLgt();
return $this->createQueryBuilder('a')
//->select('a as activity,dist('.$distance.') as distance' )
->join('a.author','u')
->join('u.address','add')
->andWhere('a.theme=:val')
->andWhere( '(6378 * acos(cos(radians( add.lnt)) * cos(radians(' . $latUser . ')) * cos(radians(' . $lngUser . ') - radians(add.lgt)) + sin(radians(add.lnt )) * sin(radians(' . $latUser . '))))< :distance')
->setParameter('distance', $distance)
->setParameter('val',$theme)
->orderBy( 'a.author','ASC')
->getQuery()
->getResult();
}
Related
I am struggling to migrate a complex SQL query to the Doctrine DBAL in TYPO3. My old repository query looks like this:
$enableFields = $GLOBALS['TSFE']->sys_page->enableFields('tx_test');
// calculate distance between geo coordinates
$distance = '';
if ($geoData) {
$distance = ', 3956 * 2 * ASIN( SQRT (POWER ( SIN((' . $geoData['latitude'] . ' - abs(tx_test.latitude)) * pi() / 180 / 2), 2) + COS(' . $geoData['latitude'] . ' * pi() / 180) * ' . 'COS(abs(tx_test.latitude)' .
' * pi() / 180) * POWER (SIN((' . $geoData['longitude'] .
' - tx_test.longitude)' .
' * pi() / 180 / 2), 2))) AS distance';
}
// General query
$sql = 'SELECT * ' . $distance .
' FROM tx_test ' .
' WHERE DATE_FORMAT(FROM_UNIXTIME(start), "%Y") = '.(int)$settings['flexformYear'].' AND published = 1 ' . $enableFields;
if($headline) {
$sql .= ' AND headline like '.$GLOBALS['TYPO3_DB']->quoteStr($headline).'%';
}
// ... and some more ...
$query = $this->createQuery();
$results = $query->statement($sql)->execute();
To migrate to Doctrine
Now I could easily remove the $GLOBALS['TYPO3_DB']->quoteStr() and replace the last two lines of the code above with this:
$connection = GeneralUtility::makeInstance(ConnectionPool::class)->getConnectionForTable('tx_test');
$results = $connection->executeQuery($sql)->fetchAll();
But this returns an array of the results and not the objects with the attached sub objects, eg. a FileReference object or another attached model.
Is there another way to achieve the desired result? If not, how can I secure/sanitize the user input for $headline? And do I need to write the SQL for the joined tables myself?
Here is a part of some distance calculations that should fit your use case regarding the use of Doctrine and the quoting.
Using Doctrine you'll get records as array, too. See below if you want Extbase objects.
Get the TYPO3 flavor of the Doctrine DBAL QueryBuilder:
$q = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable($table = 'tx_....');
$q->select(
...
'a.lat',
'a.lng'
)
->from($table /*, 'alias if you want' */);
I am not using the fluent interface much when doing calculations like that (although it is of course possible to do so by using any MySQL function with $q->selectLiteral()).
Parameters
To prevent SQL injection you should quote all possible user input with $q->quote() / $q->quoteIdentifier() or use parameters $q->createNamedParameter().
Constraints example
This is just a part of the constraints. It contains an example how to combine them based on conditions as is common in a search function.
if ((float)$searchObject->radiusKm > .5) {
$_radiusOrs = [
'IF (
' . $q->quoteIdentifier('lat') . ' = 0,
100000,
12742 * ASIN(
SQRT(
POWER(
SIN(
( ' . $q->quote((float)$searchObject->lat) . '
- ABS( ' . $q->quoteIdentifier('lat') . ' )
) * 0.0087266
),
2
)
+
COS( ' . $q->quote((float)$searchObject->lng) . ' * 0.01745329 ) * COS(
ABS( ' . $q->quoteIdentifier('lat') . ' ) * 0.01745329
) * POWER(
SIN(
( ' . $q->quote((float)$searchObject->lng) . '
- ' . $q->quoteIdentifier('lng') . '
) * 0.0087266
),
2
)
)
)
) < ' . $q->quote((float)$searchObject->radiusKm),
];
$q->andWhere(
$q->expr()->orX(...$_radiusOrs)
);
}
...
$aRes = $q->execute()->fetchAll();
(If you want to debug: You get the SQL with $q->getSQL(), $q->getParameters())
Mapping to Extbase objects
$dataMapper = $this->objectManager->get(\TYPO3\CMS\Extbase\Persistence\Generic\Mapper\DataMapper::class);
$objects = $dataMapper->map(YourExtbaseModel::class, $aRes);
I would suggest: Use Extbase objects only if you have few objects or if you have enough memory and you don't care about performance. You should mostly get away with plain arrays just as good.
The Extbase query builder allowed for a little optimization when outputting into a Fluid template: passing the \TYPO3\CMS\Extbase\Persistence\Generic\QueryResult allowed to use the f:paginate ViewHelper on it which did not have to instantiate all the objects but only those shown on current page. That possiblity is not there (yet?) when using the Doctrine QueryBuilder. So using Extbase models should rather be last resort now than being the default. It seemed to be "best practice" - that is not true any more IMHO.
I want calculate time different between 2 row and calculate rank.
this is my code:
public function testtima($sid,$rid){
$calcs=users::where([['si',$sid]])->first();
$calcr=users::where([['ri',$rid]])->first();
$stime=$calcs['created_at'];
$rtime=$calcr['created_at'];
$show['stime']=$stime;
$show['rtime']=$rtime;
$show['now']=carbon::now();
return $show;
}
how can i calculate $rtime-$stime ?
Use Carbon's method, diffInSeconds, diffInDays or diffInMonths etc:
public function testtima($sid,$rid){
$stime = users::where('si',$sid)->first()->created_at;
$rtime = users::where('ri',$rid)->first()->created_at;
$diff = (new Carbon($stime))->diffInSeconds($rtime);
//$diff = (new Carbon($stime))->diffInDays($rtime);
return $diff;
}
$row1= "2007-03-24";
$row2= "2009-06-26";
$diff = abs(strtotime($row2) - strtotime($row1));
$years = floor($diff / (365*60*60*24));
$months = floor(($diff - $years * 365*60*60*24) / (30*60*60*24));
$days = floor(($diff - $years * 365*60*60*24 - $months*30*60*60*24)/ (60*60*24));
printf("%d years, %d months, %d days\n", $years, $months, $days);
I'm working on Symfony4 with doctrine.
On my repository, i get informations but i can't do an order by with a variable parameter( distance between a user an many other users)
public function findByThemeAndDistance($theme,$distance,$user){
$latUser=$user->getAddress()->getLnt();
$lngUser = $user->getAddress()->getLgt();
return $this->createQueryBuilder('a')
// ->select('a as activity,dist(distance) as distance' )
->join('a.author','u')
->join('u.address','add')
->andWhere('a.theme=:val')
->andWhere( '(6378 * acos(cos(radians( add.lnt)) * cos(radians(' . $latUser . ')) * cos(radians(' . $lngUser . ') - radians(add.lgt)) + sin(radians(add.lnt )) * sin(radians(' . $latUser . '))))< :distance')
->setParameter('distance', $distance)
->setParameter('val',$theme)
->orderBy('distance','ASC')
->getQuery()
->getResult()
;
}
I try to get e result ordered by distance but I get This :
Error: Expected Doctrine\ORM\Query\Lexer::T_IDENTIFIER, got '1' and
when i erase the alais, I get This : [Semantical Error] line 0, col
287 near 'distance ASC': Error: 'distance' is not defined.
As the distance field does not exist on your entity you have to add the select and order by its alias
->addSelect('dist(distance) as distance')
->orderBy('distance','ASC')
I stored a Controller#Action in the database, but when I try to run it it don't has value.
I know that eval is very dangerous. If there is an alternative what I can use in this situation I'm open for it.
My code where I wan't to run it:
//$action = "ExchangeController::module_getAllValutaByBank";
//$params = "K&H";
$test = eval('\App\Http\Controllers\ModuleControllers' . "\\" . $action . "('" . $params . "');");
Debugbar::info($test);
The code what I want to eval:
It's an API request to the local ForexChange site. #param bank's name
#return stuctured string
public static function module_getAllValutaByBank($bankName){
$return = '';
$data['bankName'] = $bankName;
$response = self::getRequest($data);
if(is_array($response)){
$return .= $response[0]['bank'] . "\n";
foreach($response as $key => $item){
$return .= $item['valuta'] . " - " . round($item['buy'], 2) . " - " . round($item['sell'], 2) . "\n";
}
} else {
$return = $response;
}
Debugbar::info($return);
return $return;
}
Output of Debugbar::info($return);
K&H Bank GBP - 338.51 - 363.07 AUD - 189.9 - 207.8 DKK - 39.75 - 43.93
JPY - 2.26 - 2.48 CAD - 188.73 - 206.51 NOK - 30.92 - 34.18 CHF -
256.89 - 275.53 SEK - 29.18 - 32.26 USD - 246.14 - 260.32 CZK - 11.51 - 12.97 PLN - 69.87 - 78.79 EUR - 302.92 - 320.38 HRK - 39.39 - 44.41
Output of Debugbar::info($test);
null
Where it went wrong?
EDIT:
Found the solution:
$test = call_user_func('\App\Http\Controllers\ModuleControllers' . "\\" . $action, $params));
Found the solution:
$test = call_user_func('\App\Http\Controllers\ModuleControllers' . "\\" . $action, $params));
I was going to advice some fellow Perl developers against eval-ing formulas as a string each time the formula is calculated, and instead eval - once - functions for each formula. Thus, instead of calling eval thousands of times, a function is called. I.e:
Instead of:
sub calc_formulas_naive
{
my %formulas;
$formulas{formula_a} = '$ref->{metric_a} + $ref->{metric_b} * $ref->{metric_c} - 100 * $ref->{metric_c} / 1000 + 33.33 * $ref->{metric_d}';
$formulas{formula_b} = '$ref->{metric_a} * $ref->{metric_c} - 33';
$formulas{formula_c} = '$ref->{metric_a} * $ref->{metric_c} - 33 + 0.265 * ($ref->{metric_d} / $ref->{metric_c}) - $ref->{metric_a} + $ref->{metric_e} * ($ref->{metric_d} + 1)';
my $ref = shift #_;
my $form_a_val = eval $formulas{formula_a};
my $form_b_val = eval $formulas{formula_b};
my $form_c_val = eval $formulas{formula_c};
}
Use:
my %formulas_adv;
sub prep_formulas_adv # called only once prior to parsing!
{
my $sub = 'sub { my $ref = shift #_; my $result = $ref->{metric_a} + $ref->{metric_b} * $ref->{metric_c} - 100 * $ref->{metric_c} / 1000 + 33.33 * $ref->{metric_d}; return $result; }';
$formulas_adv{formula_a} = eval $sub;
$sub = 'sub { my $ref = shift #_; my $result = $ref->{metric_a} * $ref->{metric_c} - 33; return $result; }';
$formulas_adv{formula_b} = eval $sub;
$sub = 'sub { my $ref = shift #_; my $result = $ref->{metric_a} * $ref->{metric_c} - 33 + 0.265 * ($ref->{metric_d} / $ref->{metric_c}) - $ref->{metric_a} + $ref->{metric_e} * ($ref->{metric_d} + 1); return $result; }';
$formulas_adv{formula_c} = eval $sub;
}
sub calc_formulas_advanced
{
my $ref = shift #_;
my $form_a_val = $formulas_adv{formula_a}($ref);
my $form_b_val = $formulas_adv{formula_b}($ref);
my $form_c_val = $formulas_adv{formula_c}($ref);
}
Now the question itself. I was totally sure that 2nd approach gives noticeable advantage when processing big amounts of data. I haven't had access to my old projects, but I also was quite sure I saw the 2nd approach working faster than the 1st. After all, Perl doesn't have to compile anything on-the-run in this case.
But before giving the advice, I wrote a small artificial example with 5K text files populated with rand generated metrics, ran both implementations, and... I don't observe any advantage of using unnamed functions over strings evaluation.
What's the matter? Was I hallucinating all the time, when I thought that eval is less effective than a function?