netdudes/DataSourceryBundle

View on GitHub
DataSource/Driver/Doctrine/DoctrineDriver.php

Summary

Maintainability
A
2 hrs
Test Coverage
<?php
namespace Netdudes\DataSourceryBundle\DataSource\Driver\Doctrine;

use Doctrine\ORM\NoResultException;
use Doctrine\ORM\QueryBuilder;
use Netdudes\DataSourceryBundle\DataSource\Configuration\Field;
use Netdudes\DataSourceryBundle\DataSource\DataSourceInterface;
use Netdudes\DataSourceryBundle\DataSource\Driver\Doctrine\Events\PostFetchEvent;
use Netdudes\DataSourceryBundle\DataSource\Driver\Doctrine\Exception\ColumnNotFoundException;
use Netdudes\DataSourceryBundle\DataSource\Driver\Doctrine\Exception\ColumnNotSelectedException;
use Netdudes\DataSourceryBundle\DataSource\Driver\Doctrine\QueryBuilder\BuilderFactory;
use Netdudes\DataSourceryBundle\DataSource\Driver\DriverInterface;
use Netdudes\DataSourceryBundle\Query\Query;
use Netdudes\DataSourceryBundle\Query\QueryInterface;

class DoctrineDriver implements DriverInterface
{
    const EVENT_GENERATE_SELECTS = 1;
    const EVENT_GENERATE_JOINS = 2;
    const EVENT_POST_GENERATE_QUERY_BUILDER = 3;
    const EVENT_POST_FETCH = 4;

    /**
     * @var BuilderFactory
     */
    private $queryBuilderBuilderFactory;

    /**
     * @param BuilderFactory $queryBuilderBuilderFactory
     */
    public function __construct(BuilderFactory $queryBuilderBuilderFactory)
    {
        $this->queryBuilderBuilderFactory = $queryBuilderBuilderFactory;
    }

    /**
     * {@inheritdoc}
     */
    public function getData(DataSourceInterface $dataSource, QueryInterface $query)
    {
        $queryBuilder = $this->getQueryBuilder($dataSource, $query);
        $rows = $this->fetchData($queryBuilder, $query, $dataSource);

        return $rows;
    }

    /**
     * {@inheritdoc}
     */
    public function getRecordCount(DataSourceInterface $dataSource, QueryInterface $query)
    {
        $queryBuilder = $this->getQueryBuilder($dataSource, $query);
        $queryBuilder->select('count(DISTINCT ' . $queryBuilder->getDQLPart('from')[0]->getAlias() . ')');
        $queryBuilder->resetDQLPart('groupBy');
        $queryBuilder->resetDQLPart('orderBy');
        $queryBuilder->setMaxResults(null);
        $queryBuilder->setFirstResult(null);
        try {
            return $queryBuilder->getQuery()->getSingleScalarResult();
        } catch (NoResultException $e) {
            return 0;
        }
    }

    /**
     * @param DataSourceInterface $dataSource
     * @param QueryInterface      $query
     *
     * @return \Doctrine\DBAL\Query\QueryBuilder|null
     */
    public function getQueryBuilder(DataSourceInterface $dataSource, QueryInterface $query)
    {
        $queryBuilderBuilder = $this->queryBuilderBuilderFactory->create($dataSource);
        $queryBuilder = $queryBuilderBuilder->buildQueryBuilder($query, $dataSource->getEntityClass());

        return $queryBuilder;
    }

    /**
     * Transforms a fully-built query builder into a row collection with the results
     *
     * @param QueryBuilder        $queryBuilder
     *
     * @param Query               $query
     * @param DataSourceInterface $dataSource
     *
     * @return array
     * @throws ColumnNotSelectedException
     * @throws \Netdudes\DataSourceryBundle\DataSource\Driver\Doctrine\Exception\ColumnNotFoundException
     *
     */
    protected function fetchData(QueryBuilder $queryBuilder, Query $query, DataSourceInterface $dataSource)
    {
        $fields = $dataSource->getFields();
        $rowCollection = [];
        $queryResults = $queryBuilder->getQuery()->getResult();
        foreach ($queryResults as $queryResultsRow) {
            $row = [];
            foreach ($fields as $queryBuilderDataSourceField) {
                if (!in_array($queryBuilderDataSourceField->getUniqueName(), $query->getSelect(), true)) {
                    continue;
                }

                $row[$queryBuilderDataSourceField->getUniqueName()] =
                    $this->getCellValueByDataSourceField($queryResultsRow, $queryBuilderDataSourceField, $fields);
            }
            $rowCollection[] = $row;
        }

        $event = new PostFetchEvent($dataSource, $rowCollection);
        $dataSource->getEventDispatcher()->dispatch(
            self::EVENT_POST_FETCH,
            $event
        );

        return $event->data;
    }

    /**
     * Helper method: gets the data of a defined data source field
     * from a row of the database scalar results
     *
     * @param array $dataRow Plain array of data for a single row, from the database
     * @param Field $dataSourceField
     *
     * @throws \Exception
     *
     * @return mixed
     */
    protected function getCellValueByDataSourceField(array $dataRow, Field $dataSourceField, $fields)
    {
        $selectAlias = $dataSourceField->getDatabaseSelectAlias();

        if (is_array($selectAlias)) {
            // Alias is an array of aliases. The cell value is an array then too.
            $cellValue = [];
            foreach ($selectAlias as $key => $subAlias) {
                // Resolve recursively
                $field = $this->findQueryBuilderDataSourceFieldByUniqueName($subAlias, $fields);
                if (is_null($field)) {
                    throw new ColumnNotFoundException("Could not find data source field $subAlias, alias of  $selectAlias");
                }
                foreach ($fields as $field) {
                    if ($field->getUniqueName() === $subAlias) {
                        $cellValue[$key] = $this->getCellValueByDataSourceField($dataRow, $field, $fields);
                        break;
                    }
                }
            }

            return $cellValue;
        }

        // Try to get the data from the result row
        if (array_key_exists($selectAlias, $dataRow)) {
            return $dataRow[$selectAlias];
        }

        throw new ColumnNotSelectedException("Value for column '$selectAlias' cannot be found as the field was not selected");
    }

    private function findQueryBuilderDataSourceFieldByUniqueName($uniqueName, $fields)
    {
        foreach ($fields as $field) {
            if ($field->getUniqueName() === $uniqueName) {
                return $field;
            }
        }

        return null;
    }
}