Commit c70d2e28 authored by François Agneray's avatar François Agneray
Browse files

Merge branch '25-fusionner-searchdata-et-searchmeta' into 'develop'

#25 => done

Closes #25

See merge request !22
parents 6a248a90 56bad766
Pipeline #1188 passed with stages
in 4 minutes and 18 seconds
......@@ -12,22 +12,25 @@ namespace App\Action\Search;
use Psr\Log\LoggerInterface;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\DBAL\Query\QueryBuilder;
use Doctrine\DBAL\Query\Expression\CompositeExpression;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
use App\Entity\Dataset;
use App\Entity\Attribute;
use App\Utils\ActionTrait;
use App\Utils\DBALConnectionFactory;
use App\utils\AnisQueryBuilderFactory;
use App\Search\SearchException;
use App\Search\Operator\IOperatorFactory;
use App\Entity\Dataset;
use App\Entity\Attribute;
/**
* Get anis metadata search (GET)
* Get anis data search (GET)
*
* @author François Agneray <francois.agneray@lam.fr>
* @package App\Action\Search
*/
final class SearchMetaAction
final class SearchAction
{
use ActionTrait;
......@@ -53,11 +56,10 @@ final class SearchMetaAction
private $dcf;
/**
* Factory method allows to retrieve an anis query builder object used to construct the SQL request
*
* @var AnisQueryBuilderFactory
*
* @var IOperatorFactory
*/
private $aqbf;
private $operatorFactory;
/**
* The encryption key used by anis to encrypt and decrypt sensitive data like passwords
......@@ -73,24 +75,24 @@ final class SearchMetaAction
* @param LoggerInterface $logger
* @param EntityManagerInterface $em
* @param DBALConnectionFactory $dcf
* @param AnisQueryBuilderFactory $aqbf
* @param IOperatorFactory $operatorFactory
*/
public function __construct(
LoggerInterface $logger,
EntityManagerInterface $em,
DBALConnectionFactory $dcf,
AnisQueryBuilderFactory $aqbf,
IOperatorFactory $operatorFactory,
string $encryptionKey
) {
$this->logger = $logger;
$this->em = $em;
$this->dcf = $dcf;
$this->aqbf = $aqbf;
$this->operatorFactory = $operatorFactory;
$this->encryptionKey = $encryptionKey;
}
/**
* This action retrieves meta information about an anis request (ex: number of objects)
* This action returns data from an anis request
*
* @param ServerRequestInterface $request This object contains the HTTP request
* @param ResponseInterface $response This object represents the HTTP response
......@@ -107,6 +109,7 @@ final class SearchMetaAction
}
$dataset = $this->em->find('App\Entity\Dataset', $args['dname']);
if (is_null($dataset)) {
return $this->dispatchHttpError(
$response,
......@@ -114,7 +117,7 @@ final class SearchMetaAction
'Dataset with id ' . $args['dname'] . ' is not found'
)->withStatus(404);
}
$queryParams = $request->getQueryParams();
if (!array_key_exists('a', $queryParams)) {
return $this->dispatchHttpError(
......@@ -124,39 +127,111 @@ final class SearchMetaAction
)->withStatus(400);
}
$listOfIds = explode(';', $queryParams['a']);
$attributesSelected = array();
foreach ($listOfIds as $id) {
$attribute = $this->getAttribute($dataset, (int) $id);
$attributesSelected[] = array(
'name' => $attribute->getName(),
'label' => $attribute->getLabel()
);
}
$database = $dataset->getProject()->getDatabase();
$decryptedPassword = $this->decryptData($database->getPassword());
$connection = $this->dcf->create($database, $decryptedPassword);
$queryBuilder = $connection->createQueryBuilder();
$anisQueryBuilder = $this->aqbf->create($queryBuilder, $dataset);
$anisQueryBuilder->count();
$queryBuilder->from($dataset->getTableRef());
if (array_key_exists('c', $queryParams)) {
$anisQueryBuilder->where(explode(';', $queryParams['c']));
$this->where($queryBuilder, $dataset, explode(';', $queryParams['c']));
}
$this->logger->info('SQL: ' . $anisQueryBuilder->getSQL());
$result = $anisQueryBuilder->fetchAll();
if ($args['type'] === 'data') {
$this->select($queryBuilder, $dataset, explode(';', $queryParams['a']));
$meta = array();
$meta['dataset_selected'] = $dataset->getLabel();
$meta['attributes_selected'] = $attributesSelected;
$meta['total_items'] = $result[0]['nb'];
// $meta['url'] = $dataset->getName() . '?' . $request->getUri()->getQuery();
if (array_key_exists('o', $queryParams)) {
$this->order($queryBuilder, $dataset, explode(';', $queryParams['o']));
}
if (array_key_exists('p', $queryParams)) {
$this->limit($queryBuilder, $queryParams['p']);
}
$result = $this->fetchAll($queryBuilder);
} else if ($args['type'] === 'meta') {
$queryBuilder->select('COUNT(*) as nb');
$count = $this->fetchAll($queryBuilder);
$result = array();
$result['dataset_selected'] = $dataset->getLabel();
$result['total_items'] = $count[0]['nb'];
} else {
throw SearchException::typeOfSearchDoesNotExist($args['type']);
}
$this->logger->info('SQL: ' . $queryBuilder->getSQL());
return $response->withJson($meta);
return $response->withJson($result);
}
private function select(QueryBuilder $queryBuilder, Dataset $dataset, array $listOfIds): void
{
$columns = array();
foreach ($listOfIds as $id) {
$attribute = $this->getAttribute($dataset, (int) $id);
$columns[] = $attribute->getTableName() . '.' . $attribute->getName() . ' as ' . $attribute->getLabel();
}
$queryBuilder->select($columns);
}
private function where(QueryBuilder $queryBuilder, Dataset $dataset, array $criteria): void
{
$expressions = array();
foreach ($criteria as $criterion) {
$params = $this->getCriterionParams($criterion);
$attribute = $this->getAttribute($dataset, (int) $params[0]);
$column = $attribute->getTableName() . '.' . $attribute->getName();
$columnType = $attribute->getType();
if (array_key_exists(2, $params)) {
$values = explode('|', $params[2]);
} else {
$values = array();
}
$operator = $this->operatorFactory->create($params[1], $queryBuilder->expr(), $column, $columnType, $values);
$expressions[] = $operator->getExpression();
}
$queryBuilder->where(new CompositeExpression(CompositeExpression::TYPE_AND, $expressions));
}
private function order(QueryBuilder $queryBuilder, Dataset $dataset, array $orders): void
{
foreach ($orders as $order) {
$o = explode(':', $order);
$attribute = $this->getAttribute($dataset, (int) $o[0]);
if ($o[1] === 'a') {
$aord = 'ASC';
} else if ($o[1] === 'd') {
$aord = 'DESC';
}
$queryBuilder->orderBy($attribute->getTableName() . '.' . $attribute->getName(), $aord);
}
}
private function limit(QueryBuilder $queryBuilder, string $param): void
{
$p = explode(':', $param);
$limit = $p[0];
$offset = ($p[1] - 1) * $limit;
$queryBuilder
->setFirstResult($offset)
->setMaxResults($limit);
}
private function fetchAll(QueryBuilder $queryBuilder): array
{
$stmt = $queryBuilder->execute();
return $stmt->fetchAll();
}
private function getCriterionParams(string $criterion): array
{
$params = explode(':', $criterion);
$numberOfParams = count($params);
if ($numberOfParams < 2) {
throw SearchException::numberOfCriterionParams($numberOfParams, $criterion);
}
return $params;
}
private function getAttribute(Dataset $dataset, int $id): Attribute
......
<?php declare(strict_types=1);
/*
* This file is part of ANIS SERVER API.
*
* (c) François Agneray <francois.agneray@lam.fr>
* (c) Chrystel Moreau <chrystel.moreau@lam.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Action\Search;
use Psr\Log\LoggerInterface;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;
use App\Utils\ActionTrait;
use App\Utils\DBALConnectionFactory;
use App\utils\AnisQueryBuilderFactory;
/**
* Get anis data search (GET)
*
* @author François Agneray <francois.agneray@lam.fr>
* @package App\Action\Search
*/
final class SearchDataAction
{
use ActionTrait;
/**
* The logger interface is the central access point to log anis information
*
* @var LoggerInterface
*/
private $logger;
/**
* The EntityManager is the central access point to Doctrine ORM functionality (metadata database)
*
* @var EntityManagerInterface
*/
private $em;
/**
* Factory method used to retrieve a PDO connection object to the business database
*
* @var DBALConnectionFactory
*/
private $dcf;
/**
* Factory method allows to retrieve an anis query builder object used to construct the SQL request
*
* @var AnisQueryBuilderFactory
*/
private $aqbf;
/**
* The encryption key used by anis to encrypt and decrypt sensitive data like passwords
* This key is provided by configuration (see the config file)
*
* @var string
*/
private $encryptionKey;
/**
* This class is creates before call __invoke to execute the action
*
* @param LoggerInterface $logger
* @param EntityManagerInterface $em
* @param DBALConnectionFactory $dcf
* @param AnisQueryBuilderFactory $aqbf
*/
public function __construct(
LoggerInterface $logger,
EntityManagerInterface $em,
DBALConnectionFactory $dcf,
AnisQueryBuilderFactory $aqbf,
string $encryptionKey
) {
$this->logger = $logger;
$this->em = $em;
$this->dcf = $dcf;
$this->aqbf = $aqbf;
$this->encryptionKey = $encryptionKey;
}
/**
* This action returns data from an anis request
*
* @param ServerRequestInterface $request This object contains the HTTP request
* @param ResponseInterface $response This object represents the HTTP response
* @param array $args This table contains information transmitted in the URL (see routes.php)
*
* @return ResponseInterface
*/
public function __invoke(Request $request, Response $response, array $args): Response
{
$this->logger->info('Search meta action dispatched');
if ($request->isOptions()) {
return $response->withHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
}
$dataset = $this->em->find('App\Entity\Dataset', $args['dname']);
if (is_null($dataset)) {
return $this->dispatchHttpError(
$response,
'Invalid request',
'Dataset with id ' . $args['dname'] . ' is not found'
)->withStatus(404);
}
$queryParams = $request->getQueryParams();
if (!array_key_exists('a', $queryParams)) {
return $this->dispatchHttpError(
$response,
'Invalid request',
'Param a is required for this request'
)->withStatus(400);
}
$database = $dataset->getProject()->getDatabase();
$decryptedPassword = $this->decryptData($database->getPassword());
$connection = $this->dcf->create($database, $decryptedPassword);
$queryBuilder = $connection->createQueryBuilder();
$anisQueryBuilder = $this->aqbf->create($queryBuilder, $dataset);
$anisQueryBuilder->select(explode(';', $queryParams['a']));
if (array_key_exists('c', $queryParams)) {
$anisQueryBuilder->where(explode(';', $queryParams['c']));
}
if (array_key_exists('o', $queryParams)) {
$anisQueryBuilder->order(explode(';', $queryParams['o']));
}
if (array_key_exists('p', $queryParams)) {
$anisQueryBuilder->limit($queryParams['p']);
}
$this->logger->info('SQL: ' . $anisQueryBuilder->getSQL());
$result = $anisQueryBuilder->fetchAll();
return $response->withJson($result);
}
}
<?php declare(strict_types=1);
/*
* This file is part of ANIS SERVER API.
*
* (c) François Agneray <francois.agneray@lam.fr>
* (c) Chrystel Moreau <chrystel.moreau@lam.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Search;
use Doctrine\DBAL\Query\QueryBuilder;
use Doctrine\DBAL\Query\Expression\CompositeExpression;
use App\Search\Operator\IOperatorFactory;
use App\Entity\Dataset;
use App\Entity\Attribute;
class AnisQueryBuilder
{
private $queryBuilder;
private $dataset;
private $operatorFactory;
public function __construct(QueryBuilder $queryBuilder, Dataset $dataset, IOperatorFactory $operatorFactory)
{
$this->queryBuilder = $queryBuilder->from($dataset->getTableRef());
$this->dataset = $dataset;
$this->operatorFactory = $operatorFactory;
}
public function setOperatorFactory(string $operatorFactory): void
{
$this->$operatorFactory = $operatorFactory;
}
public function count(): AnisQueryBuilder
{
$this->queryBuilder->select('COUNT(*) as nb');
return $this;
}
public function select(array $listOfIds): AnisQueryBuilder
{
$columns = array();
foreach ($listOfIds as $id) {
$attribute = $this->getAttribute((int) $id);
$columns[] = $attribute->getTableName() . '.' . $attribute->getName() . ' as ' . $attribute->getLabel();
}
$this->queryBuilder->select($columns);
return $this;
}
public function where(array $criteria): AnisQueryBuilder
{
$expressions = array();
foreach ($criteria as $criterion) {
$params = $this->getCriterionParams($criterion);
$attribute = $this->getAttribute((int) $params[0]);
$column = $attribute->getTableName() . '.' . $attribute->getName();
$columnType = $attribute->getType();
if (array_key_exists(2, $params)) {
$values = explode('|', $params[2]);
} else {
$values = array();
}
$operator = $this->operatorFactory->create($params[1], $this->queryBuilder->expr(), $column, $columnType, $values);
$expressions[] = $operator->getExpression();
}
$this->queryBuilder->where(new CompositeExpression(CompositeExpression::TYPE_AND, $expressions));
return $this;
}
public function order(array $orders): AnisQueryBuilder
{
foreach ($orders as $order) {
$o = explode(':', $order);
$attribute = $this->getAttribute((int) $o[0]);
if ($o[1] === 'a') {
$aord = 'ASC';
} else if ($o[1] === 'd') {
$aord = 'DESC';
}
$this->queryBuilder->orderBy($attribute->getTableName() . '.' . $attribute->getName(), $aord);
}
return $this;
}
public function limit(string $param): AnisQueryBuilder
{
$p = explode(':', $param);
$limit = $p[0];
$offset = ($p[1] - 1) * $limit;
$this->queryBuilder
->setFirstResult($offset)
->setMaxResults($limit);
return $this;
}
public function getSQL(): string
{
return $this->queryBuilder->getSQL();
}
public function fetchAll(): array
{
$stmt = $this->queryBuilder->execute();
return $stmt->fetchAll();
}
private function getCriterionParams(string $criterion): array
{
$params = explode(':', $criterion);
$numberOfParams = count($params);
if ($numberOfParams < 2) {
throw SearchException::numberOfCriterionParams($numberOfParams, $criterion);
}
return $params;
}
private function getAttribute(int $id): Attribute
{
$attributes = $this->dataset->getAttributes();
foreach ($attributes as $attribute) {
if ($attribute->getId() === $id) {
return $attribute;
}
}
throw SearchException::attributeNotFound($id, $this->dataset->getLabel());
}
}
......@@ -39,4 +39,9 @@ class SearchException extends Exception
{
return new self("List of ids is not correct");
}
public static function typeOfSearchDoesNotExist($param)
{
return new self("The type of search '" . $param . "' does not exists");
}
}
<?php declare(strict_types=1);
/*
* This file is part of ANIS SERVER API.
*
* (c) François Agneray <francois.agneray@lam.fr>
* (c) Chrystel Moreau <chrystel.moreau@lam.fr>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace App\Utils;
use Doctrine\DBAL\Query\QueryBuilder;
use App\Entity\Dataset;
use App\Search\Operator\IOperatorFactory;
use App\Search\AnisQueryBuilder;
/**
* Factory used to create an anis query builder
*
* @author François Agneray <francois.agneray@lam.fr>
* @package App\Utils
*/
class AnisQueryBuilderFactory
{
private $operatorFactory;
public function __construct(IOperatorFactory $operatorFactory)
{
$this->operatorFactory = $operatorFactory;
}
public function create(QueryBuilder $queryBuilder, Dataset $dataset): AnisQueryBuilder
{
return new AnisQueryBuilder($queryBuilder, $dataset, $this->operatorFactory);
}
}
......@@ -62,11 +62,6 @@ $container['dcf'] = function ($c) {
return new App\Utils\DBALConnectionFactory();
};
// ANIS Query Builder Factory
$container['aqbf'] = function ($c) {
return new App\Utils\AnisQueryBuilderFactory($c->get('of'));
};
// Middleware
$container['App\Middleware\CorsMiddleware'] = function ($c) {
return new App\Middleware\CorsMiddleware();
......@@ -226,12 +221,7 @@ $container['App\Action\Meta\UserAction'] = function ($c) {
};
// Search actions
$container['App\Action\Search\SearchMetaAction'] = function ($c) {
$settings = $c->get('settings');
return new App\Action\Search\SearchMetaAction($c->get('logger'), $c->get('em'), $c->get('dcf'), $c->get('aqbf'), $settings['encryption_key']);
};
$container['App\Action\Search\SearchDataAction'] = function ($c) {
$container['App\Action\Search\SearchAction'] = function ($c) {
$settings = $c->get('settings');
return new App\Action\Search\SearchDataAction($c->get('logger'), $c->get('em'), $c->get('dcf'), $c->get('aqbf'), $settings['encryption_key']);
return new App\Action\Search\SearchAction($c->get('logger'), $c->get('em'), $c->get('dcf'), $c->get('of'), $settings['encryption_key']);
};
......@@ -48,5 +48,4 @@ $app->map(['OPTIONS', 'POST'], '/metadata/superuser', App\Action\Meta\SuperuserA
$app->map(['OPTIONS', 'GET'], '/metadata/user', App\Action\Meta\UserListAction::class);
$app->map(['OPTIONS', 'GET', 'PUT', 'DELETE'], '/metadata/user/{email}', App\Action\Meta\UserAction::class);
$app->map(['OPTIONS', 'GET'], '/search/meta/{dname}', App\Action\Search\SearchMetaAction::class);
$app->map(['OPTIONS', 'GET'], '/search/data/{dname}', App\Action\Search\SearchDataAction::class);
$app->map(['OPTIONS', 'GET'], '/search/{type}/{dname}', App\Action\Search\SearchAction::class);
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment