Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Whitelist #181

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 21 additions & 25 deletions src/AbstractTDBMObject.php
Original file line number Diff line number Diff line change
Expand Up @@ -110,18 +110,26 @@ public function __construct(?string $tableName = null, array $primaryKeys = [],
/**
* Alternative constructor called when data is fetched from database via a SELECT.
*
* @param array[] $beanData array<table, array<column, value>>
* @param array<string, array<string, mixed>> $beanData array<table, array<column, value>>
* @param TDBMService $tdbmService
*/
public function _constructFromData(array $beanData, TDBMService $tdbmService): void
public function _constructFromData(array $beanData, TDBMService $tdbmService, bool $isFullyLoaced): void
{
$this->tdbmService = $tdbmService;

foreach ($beanData as $table => $columns) {
$this->dbRows[$table] = new DbRow($this, $table, static::getForeignKeys($table), $tdbmService->_getPrimaryKeysFromObjectData($table, $columns), $tdbmService, $columns);
$this->dbRows[$table] = new DbRow(
$this,
$table,
static::getForeignKeys($table),
$tdbmService->_getPrimaryKeysFromObjectData($table, $columns),
$tdbmService,
$columns,
$isFullyLoaced
);
}

$this->status = TDBMObjectStateEnum::STATE_LOADED;
$this->status = $isFullyLoaced ? TDBMObjectStateEnum::STATE_LOADED : TDBMObjectStateEnum::STATE_PARTIALLY_LOADED;
}

/**
Expand Down Expand Up @@ -251,27 +259,14 @@ protected function set(string $var, $value, ?string $tableName = null): void
}
}

/**
* @param string $foreignKeyName
* @param AbstractTDBMObject $bean
*/
protected function setRef(string $foreignKeyName, AbstractTDBMObject $bean = null, string $tableName = null): void
protected function setRef(string $foreignKeyName, ?AbstractTDBMObject $bean, string $tableName, string $className, string $resultIteratorClass): void
{
if ($tableName === null) {
if (count($this->dbRows) > 1) {
throw new TDBMException('This object is based on several tables. You must specify which table you are retrieving data from.');
} elseif (count($this->dbRows) === 1) {
$tableName = (string) array_keys($this->dbRows)[0];
} else {
throw new TDBMException('Please specify a table for this object.');
}
}

assert($bean === null || is_a($bean, $className), new TDBMInvalidArgumentException('$bean should be `null` or `' . $className . '`. `' . ($bean === null ? 'null' : get_class($bean)) . '` provided.'));
if (!isset($this->dbRows[$tableName])) {
$this->registerTable($tableName);
}

$oldLinkedBean = $this->dbRows[$tableName]->getRef($foreignKeyName);
$oldLinkedBean = $this->dbRows[$tableName]->getRef($foreignKeyName, $className, $resultIteratorClass);
if ($oldLinkedBean !== null) {
$oldLinkedBean->removeManyToOneRelationship($tableName, $foreignKeyName, $this);
}
Expand All @@ -291,19 +286,19 @@ protected function setRef(string $foreignKeyName, AbstractTDBMObject $bean = nul
*
* @return AbstractTDBMObject|null
*/
protected function getRef(string $foreignKeyName, ?string $tableName = null) : ?AbstractTDBMObject
protected function getRef(string $foreignKeyName, string $tableName, string $className, string $resultIteratorClass) : ?AbstractTDBMObject
{
$tableName = $this->checkTableName($tableName);

if (!isset($this->dbRows[$tableName])) {
return null;
}

return $this->dbRows[$tableName]->getRef($foreignKeyName);
return $this->dbRows[$tableName]->getRef($foreignKeyName, $className, $resultIteratorClass);
}

/**
* Adds a many to many relationship to this bean.
* Adds a many to many$table relationship to this bean.
*
* @param string $pivotTableName
* @param AbstractTDBMObject $remoteBean
Expand Down Expand Up @@ -525,15 +520,16 @@ private function removeManyToOneRelationship(string $tableName, string $foreignK
*
* @return AlterableResultIterator
*/
protected function retrieveManyToOneRelationshipsStorage(string $tableName, string $foreignKeyName, array $searchFilter, string $orderString = null) : AlterableResultIterator
protected function retrieveManyToOneRelationshipsStorage(string $tableName, string $foreignKeyName, array $searchFilter, ?string $orderString, string $resultIteratorClass) : AlterableResultIterator
{
assert(is_a($resultIteratorClass, ResultIterator::class, true), new TDBMInvalidArgumentException('$resultIteratorClass should be a `'. ResultIterator::class. '`. `' . $resultIteratorClass . '` provided.'));
$key = $tableName.'___'.$foreignKeyName;
$alterableResultIterator = $this->getManyToOneAlterableResultIterator($tableName, $foreignKeyName);
if ($this->status === TDBMObjectStateEnum::STATE_DETACHED || $this->status === TDBMObjectStateEnum::STATE_NEW || (isset($this->manyToOneRelationships[$key]) && $this->manyToOneRelationships[$key]->getUnderlyingResultIterator() !== null)) {
return $alterableResultIterator;
}

$unalteredResultIterator = $this->tdbmService->findObjects($tableName, $searchFilter, [], $orderString);
$unalteredResultIterator = $this->tdbmService->findObjects($tableName, $searchFilter, [], $orderString, [], null, null, $resultIteratorClass);

$alterableResultIterator->setResultIterator($unalteredResultIterator->getIterator());

Expand Down
7 changes: 2 additions & 5 deletions src/AlterableResultIterator.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,11 +75,8 @@ public function add($object): void
{
$this->alterations->attach($object, 'add');

if ($this->resultArray !== null) {
$foundKey = array_search($object, $this->resultArray, true);
if ($foundKey === false) {
$this->resultArray[] = $object;
}
if ($this->resultArray !== null && !in_array($object, $this->resultArray, true)) {
$this->resultArray[] = $object;
}
}

Expand Down
48 changes: 26 additions & 22 deletions src/DbRow.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/

use TheCodingMachine\TDBM\Exception\TDBMPartialQueryException;
use TheCodingMachine\TDBM\Schema\ForeignKeys;

/**
Expand Down Expand Up @@ -86,14 +87,14 @@ class DbRow
/**
* A list of modified columns, indexed by column name. Value is always true.
*
* @var array
* @var array<string, bool>
*/
private $modifiedColumns = [];

/**
* A list of modified references, indexed by foreign key name. Value is always true.
*
* @var array
* @var array<string, bool>
*/
private $modifiedReferences = [];
/**
Expand All @@ -105,18 +106,23 @@ class DbRow
* You should never call the constructor directly. Instead, you should use the
* TDBMService class that will create TDBMObjects for you.
*
* Used with id!=false when we want to retrieve an existing object
* and id==false if we want a new object
*
* @param AbstractTDBMObject $object The object containing this db row
* @param string $tableName
* @param ForeignKeys $foreignKeys
* @param mixed[] $primaryKeys
* @param TDBMService $tdbmService
* @param mixed[] $dbRow
* @throws TDBMException
*/
public function __construct(AbstractTDBMObject $object, string $tableName, ForeignKeys $foreignKeys, array $primaryKeys = array(), TDBMService $tdbmService = null, array $dbRow = [])
{
public function __construct(
AbstractTDBMObject $object,
string $tableName,
ForeignKeys $foreignKeys,
array $primaryKeys = array(),
TDBMService $tdbmService = null,
array $dbRow = [],
bool $isFullyLoaced = null
) {
$this->object = $object;
$this->dbTableName = $tableName;
$this->foreignKeys = $foreignKeys;
Expand All @@ -133,8 +139,11 @@ public function __construct(AbstractTDBMObject $object, string $tableName, Forei
if (!empty($primaryKeys)) {
$this->_setPrimaryKeys($primaryKeys);
if (!empty($dbRow)) {
if ($isFullyLoaced === null) {
throw new TDBMInvalidArgumentException('$isFullyLoaced need to be provided if the DbRow is not empty.');
}
$this->dbRow = $dbRow;
$this->status = TDBMObjectStateEnum::STATE_LOADED;
$this->status = $isFullyLoaced ? TDBMObjectStateEnum::STATE_LOADED : TDBMObjectStateEnum::STATE_PARTIALLY_LOADED;
} else {
$this->status = TDBMObjectStateEnum::STATE_NOT_LOADED;
}
Expand Down Expand Up @@ -172,6 +181,8 @@ public function _setStatus(string $state) : void
// after saving we are back to a loaded state, hence unmodified.
$this->modifiedColumns = [];
$this->modifiedReferences = [];
} elseif ($state === TDBMObjectStateEnum::STATE_NOT_LOADED) {
$this->dbRow = [];
}
}

Expand Down Expand Up @@ -219,7 +230,10 @@ public function _dbLoadIfNotLoaded(): void
*/
public function get(string $var)
{
if (!isset($this->primaryKeys[$var])) {
if (!array_key_exists($var, $this->dbRow)) {
if ($this->_getStatus() === TDBMObjectStateEnum::STATE_PARTIALLY_LOADED) {
throw new TDBMPartialQueryException($var, $this->dbRow);
}
$this->_dbLoadIfNotLoaded();
}

Expand All @@ -235,16 +249,6 @@ public function set(string $var, $value): void
{
$this->_dbLoadIfNotLoaded();

/*
// Ok, let's start by checking the column type
$type = $this->db_connection->getColumnType($this->dbTableName, $var);

// Throws an exception if the type is not ok.
if (!$this->db_connection->checkType($value, $type)) {
throw new TDBMException("Error! Invalid value passed for attribute '$var' of table '$this->dbTableName'. Passed '$value', but expecting '$type'");
}
*/

/*if ($var == $this->getPrimaryKey() && isset($this->dbRow[$var]))
throw new TDBMException("Error! Changing primary key value is forbidden.");*/
$this->dbRow[$var] = $value;
Expand Down Expand Up @@ -275,7 +279,7 @@ public function setRef(string $foreignKeyName, AbstractTDBMObject $bean = null):
*
* @return AbstractTDBMObject|null
*/
public function getRef(string $foreignKeyName) : ?AbstractTDBMObject
public function getRef(string $foreignKeyName, string $className, string $resultIteratorClass) : ?AbstractTDBMObject
{
if (array_key_exists($foreignKeyName, $this->references)) {
return $this->references[$foreignKeyName];
Expand Down Expand Up @@ -303,9 +307,9 @@ public function getRef(string $foreignKeyName) : ?AbstractTDBMObject

// If the foreign key points to the primary key, let's use findObjectByPk
if ($this->tdbmService->getPrimaryKeyColumns($foreignTableName) === $foreignColumns) {
return $this->tdbmService->findObjectByPk($foreignTableName, $filter, [], true);
return $this->tdbmService->findObjectByPk($foreignTableName, $filter, [], true, $className, $resultIteratorClass);
} else {
return $this->tdbmService->findObject($foreignTableName, $filter);
return $this->tdbmService->findObject($foreignTableName, $filter, [], [], $className, $resultIteratorClass);
}
}
}
Expand Down
32 changes: 27 additions & 5 deletions src/InnerResultIterator.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,14 @@ class InnerResultIterator implements \Iterator, InnerResultIteratorInterface
private $objectStorage;
private $className;

/** @var TDBMService */
private $tdbmService;
private $magicSql;
private $parameters;
private $limit;
private $offset;
private $columnDescriptors;
/** @var MagicQuery */
private $magicQuery;

/**
Expand All @@ -65,6 +67,8 @@ class InnerResultIterator implements \Iterator, InnerResultIteratorInterface
* @var LoggerInterface
*/
private $logger;
/** @var bool */
private $hasExcludedColumns;

protected $count = null;

Expand All @@ -76,8 +80,19 @@ private function __construct()
* @param mixed[] $parameters
* @param array[] $columnDescriptors
*/
public static function createInnerResultIterator(string $magicSql, array $parameters, ?int $limit, ?int $offset, array $columnDescriptors, ObjectStorageInterface $objectStorage, ?string $className, TDBMService $tdbmService, MagicQuery $magicQuery, LoggerInterface $logger): self
{
public static function createInnerResultIterator(
string $magicSql,
array $parameters,
?int $limit,
?int $offset,
array $columnDescriptors,
ObjectStorageInterface $objectStorage,
?string $className,
TDBMService $tdbmService,
MagicQuery $magicQuery,
LoggerInterface $logger,
bool $hasExcludedColumns
): self {
$iterator = new static();
$iterator->magicSql = $magicSql;
$iterator->objectStorage = $objectStorage;
Expand All @@ -90,6 +105,7 @@ public static function createInnerResultIterator(string $magicSql, array $parame
$iterator->magicQuery = $magicQuery;
$iterator->databasePlatform = $iterator->tdbmService->getConnection()->getDatabasePlatform();
$iterator->logger = $logger;
$iterator->hasExcludedColumns = $hasExcludedColumns;
return $iterator;
}

Expand Down Expand Up @@ -170,10 +186,12 @@ public function key()
*/
public function next()
{
/** @var array<string, string> $row */
$row = $this->statement->fetch(\PDO::FETCH_ASSOC);
if ($row) {

// array<tablegroup, array<table, array<column, value>>>
/** @var array<string, array<string, array<string, mixed>>> $beansData */
$beansData = [];
foreach ($row as $key => $value) {
if (!isset($this->columnDescriptors[$key])) {
Expand Down Expand Up @@ -201,13 +219,15 @@ public function next()

list($actualClassName, $mainBeanTableName, $tablesUsed) = $this->tdbmService->_getClassNameFromBeanData($beanData);

if ($this->className !== null) {
// @TODO (gua) this is a weird hack to be able to force a TDBMObject...
// ClassName could be used to override $actualClassName
if ($this->className !== null && is_a($this->className, TDBMObject::class, true)) {
$actualClassName = $this->className;
}

// Let's filter out the beanData that is not used (because it belongs to a part of the hierarchy that is not fetched:
foreach ($beanData as $tableName => $descriptors) {
if (!in_array($tableName, $tablesUsed)) {
if (!in_array($tableName, $tablesUsed, true)) {
unset($beanData[$tableName]);
}
}
Expand All @@ -219,6 +239,7 @@ public function next()
$primaryKeys = $this->tdbmService->_getPrimaryKeysFromObjectData($mainBeanTableName, $beanData[$mainBeanTableName]);
$hash = $this->tdbmService->getObjectHash($primaryKeys);

/** @var DbRow|null $dbRow */
$dbRow = $this->objectStorage->get($mainBeanTableName, $hash);
if ($dbRow !== null) {
$bean = $dbRow->getTDBMObject();
Expand All @@ -228,8 +249,9 @@ public function next()
$reflectionClassCache[$actualClassName] = new \ReflectionClass($actualClassName);
}
// Let's bypass the constructor when creating the bean!
/** @var AbstractTDBMObject $bean */
$bean = $reflectionClassCache[$actualClassName]->newInstanceWithoutConstructor();
$bean->_constructFromData($beanData, $this->tdbmService);
$bean->_constructFromData($beanData, $this->tdbmService, !$this->hasExcludedColumns);
}

// The first bean is the one containing the main table.
Expand Down
5 changes: 3 additions & 2 deletions src/OrderByAnalyzer.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

use Doctrine\Common\Cache\Cache;
use PHPSQLParser\PHPSQLParser;
use PHPSQLParser\utils\ExpressionType;

/**
* Class in charge of analyzing order by clauses.
Expand Down Expand Up @@ -85,7 +86,7 @@ private function analyzeOrderByNoCache(string $orderBy) : array

for ($i = 0, $count = count($parsed['ORDER']); $i < $count; ++$i) {
$orderItem = $parsed['ORDER'][$i];
if ($orderItem['expr_type'] === 'colref') {
if ($orderItem['expr_type'] === ExpressionType::COLREF) {
$parts = $orderItem['no_quotes']['parts'];
$columnName = array_pop($parts);
if (!empty($parts)) {
Expand All @@ -95,7 +96,7 @@ private function analyzeOrderByNoCache(string $orderBy) : array
}

$results[] = [
'type' => 'colref',
'type' => ExpressionType::COLREF,
'table' => $tableName,
'column' => $columnName,
'direction' => $orderItem['direction'],
Expand Down
Loading