<?php
namespace App\Services\Search;
use Symfony\Component\HttpKernel\KernelInterface;
use Doctrine\ORM\EntityManagerInterface;
use Knp\Component\Pager\PaginatorInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use App\Services\Search\SearchInterface;
use \DateTime;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use XLabs\SocialBundle\Engines\Friendship;
use XLabs\SocialBundle\Engines\Views;
use App\Services\Timeline;
abstract class BaseSearch implements SearchInterface
{
protected $kernel;
protected $em;
protected $paginator;
protected $request;
protected $default_locale;
protected $options;
protected $friendshipEngine;
protected $timelineEngine;
protected $viewsEngine;
public function __construct(KernelInterface $kernel, EntityManagerInterface $em, PaginatorInterface $knp_paginator, RequestStack $request_stack, ParameterBagInterface $params)
{
$this->em = $em;
$this->paginator = $knp_paginator;
$this->request = $request_stack->getMainRequest();
$this->default_locale = $params->get('kernel.default_locale');
$this->options = [
'show_suggestions' => false, // 'did you mean ...' results; must be a number of results to show; only used when it is a sphinx search without results
'no_cache' => false,
'paginator_params' => array(), // paginator overridden params, for the pagination template
'paginator_route' => false, // to force a url when mixing ajax/non-ajax pagination route gets corrupted
'return_sphinx_results' => false, // if true, will return sphinx results (sphinx data, no queries)
'return_query' => false // if true, query object is returned
];
if($kernel->getEnvironment() != 'dev')
{
$this->em->getConnection()->getConfiguration()->setSQLLogger(null);
}
}
public function getOptions($default_options, $options)
{
$aOptions = array_merge(array_merge($this->options, $default_options), $options);
// When on ajax, params come as POST params, but booleans turned into strings
array_walk_recursive($aOptions, function(&$aOption){
if(is_string($aOption))
{
switch($aOption)
{
case 'true':
$aOption = true;
break;
case 'false':
$aOption = false;
break;
case 'null':
$aOption = null;
break;
}
}
});
// Fix; some sortings add HIDDEN selects that make getSingleScalarResult() crash
if(isset($aOptions['return_count']) && $aOptions['return_count'] && isset($aOptions['sorting']) && $aOptions['sorting'])
{
$aOptions['sorting'] = false;
}
return $aOptions;
}
public function paginate($aOptions)
{
$default_options = [
'aResults' => [],
'repository_class' => false,
'max_results' => false,
'page' => 1,
'show_pagination' => true,
'paginator_params' => [],
'paginator_route' => false,
'searchType' => 'doctrine'
];
$aOptions = array_merge($default_options, $aOptions);
$result_ids = [];
$total_results = 0;
switch($aOptions['searchType'])
{
case 'doctrine':
$aResults = array_map(function($s){
return $s['id'];
}, $aOptions['aResults']);
$result_ids = $aResults;
$total_results = count($result_ids);
break;
case 'redis':
$aResults = $aOptions['aResults']; // this is a query object with a custom hint (items are sliced by redis)
$result_ids = array_map(function($s){
return $s['id'];
}, $aResults->getArrayResult());
$total_results = count($result_ids);
break;
case 'sphinx':
$aResults = $aOptions['aResults'];
$result_ids = isset($aResults['matches']) ? array_keys($aResults['matches']) : [];
$total_results = isset($aResults) && is_array($aResults) && isset($aResults['total_found']) ? $aResults['total_found'] : count($result_ids);
break;
}
$pagination = false;
$aOptions['max_results'] = $aOptions['max_results'] ?: PHP_INT_MAX;
//if($aOptions['show_pagination'] && $aOptions['max_results'])
if($aOptions['max_results'])
{
if($aOptions['page'] == 'last')
{
$pagination = $this->paginator->paginate(
$aResults,
1,
$aOptions['max_results']
);
$aOptions['page'] = ceil($pagination->getTotalItemCount() / $aOptions['max_results']);
}
$aOptions['paginator_params']['searchType'] = $aOptions['searchType'];
$pagination = $this->paginator->paginate(
$aResults,
$aOptions['page'],
$aOptions['max_results'],
$aOptions['paginator_params']
);
if($aOptions['paginator_route'])
{
$pagination->setUsedRoute($aOptions['paginator_route']);
}
if($aOptions['searchType'] == 'doctrine')
{
$result_ids = $pagination->getItems();
}
}
$locale = $this->request ? $this->request->getLocale() : $this->default_locale;
$repository = $this->em->getRepository($aOptions['repository_class']);
if(method_exists($repository, 'setLocale'))
{
$repository = $repository->setLocale($locale);
}
$results = $repository->getCollectionById([
'result_ids' => $result_ids,
'sorting' => 'field'
]);
/*$cache = new FilesystemAdapter();
$key = md5(json_encode($params));
$cache->deleteItem($key);
$_posts = $cache->getItem($key);
if($_posts->isHit())
{
$posts = $_posts->get();
} else {
$posts = $this->em->getRepository('MMMediaBundle:Post')->getPostsById($params);
$cache->save($_posts->set($posts));
}*/
return [
'result_ids' => $result_ids,
'results' => $results,
'pagination' => $pagination,
//'total_results' => isset($aResults) && is_array($aResults) && isset($aResults['total_found']) ? $aResults['total_found'] : count($result_ids)
'total_results' => $total_results
];
}
public function getSphinxSorting($arrValues)
{
$set = [];
$arrValues = array_reverse($arrValues);
for ($i=0;$i < count($arrValues);$i++)
{
$set[$arrValues[$i]] = $i + 1; // turns the array into array(document_id1 => position, document_id2 => position, ...)
}
return $set;
}
/*
* Results for "did you mean ..." when no sphinx results found
* Requires 'id' and 'value' to be included in the $query
* MySQL driven
*/
protected function getSuggestedResults($aOptions, $query)
{
$cache = new FilesystemAdapter();
$cache_key = 'suggested_results.'.md5($query);
$cache_ttl = 86400; // 24h
if(!$cache->has($cache_key))
{
$conn = $this->em->getConnection();
$conn->getConfiguration()->setSQLLogger(null); // disable logging
$stmt = $conn->prepare($query);
$stmt->execute();
$cache->set($cache_key, json_encode($stmt->fetchAll()), $cache_ttl);
}
$results = json_decode($cache->get($cache_key), true);
$shortest = 100;
//$closest = false;
$closeIds = [];
$closeWords = [];
// loop through words to find the closest
foreach($results as $result)
{
$id = $result['id'];
$word = $result['value'];
// calculate the distance between the input word and the current word
//$lev = levenshtein($aOptions['search_terms'], $word);
$lev = levenshtein(preg_replace('/\s+/', '-', $aOptions['search_terms']), preg_replace('/\s+/', '-', $word));
//if the distance is shorter than the last shortest one, replace it.
if($lev <= $shortest)
{
// set the closest match, and shortest distance
$closest = $word;
$shortest = $lev;
array_unshift($closeWords, $closest);
array_unshift($closeIds, $id);
}
}
//return array_slice($closeWords, 0, $aOptions['show_suggestions']);
return array_slice($closeIds, 0, $aOptions['show_suggestions']);
//return $closest;
}
public function setFriendshipEngine(Friendship $friendshipEngine)
{
$this->friendshipEngine = $friendshipEngine;
}
public function setTimelineEngine(Timeline $timelineEngine)
{
$this->timelineEngine = $timelineEngine;
}
public function setViewsEngine(Views $viewsEngine)
{
$this->viewsEngine = $viewsEngine;
}
}