1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226: 227: 228: 229: 230: 231: 232: 233: 234: 235: 236: 237: 238: 239: 240: 241: 242: 243: 244: 245: 246: 247: 248: 249: 250: 251: 252: 253: 254: 255: 256: 257: 258: 259: 260: 261: 262: 263: 264: 265: 266: 267: 268: 269: 270: 271: 272: 273: 274: 275: 276: 277: 278: 279: 280: 281: 282: 283: 284: 285: 286: 287: 288: 289: 290: 291: 292: 293:
<?php
namespace LeanMapperQuery;
use LeanMapper\Reflection\Property,
LeanMapper\Relationship,
LeanMapper\IMapper,
LeanMapper\Fluent,
LeanMapper\ImplicitFilters,
LeanMapper\Entity,
LeanMapperQuery\Exception\Exception;
class Query implements IQuery
{
protected $sourceRepository;
protected $mapper;
protected $fluent;
protected $appliedJoins = array();
public function __construct(IQueryable $sourceRepository, IMapper $mapper)
{
$this->sourceRepository = $sourceRepository;
$this->mapper = $mapper;
$this->fluent = $sourceRepository->createFluent();
}
protected final function getPropertiesByTable($tableName)
{
$entityClass = $this->mapper->getEntityClass($tableName);
$reflection = $entityClass::getReflection($this->mapper);
$properties = array();
foreach ($reflection->getEntityProperties() as $property) {
$properties[$property->getName()] = $property;
}
return array($entityClass, $properties);
}
private function joinRelatedTable($currentTable, $referencingColumn, $targetTable, $targetTablePrimaryKey, $filters = array())
{
if (!in_array($targetTable, $this->appliedJoins)) {
if (empty($filters)) {
$this->fluent->leftJoin($targetTable)->on("[$currentTable].[$referencingColumn] = [$targetTable].[$targetTablePrimaryKey]");
} else {
$subFluent = new Fluent($this->fluent->getConnection());
$subFluent->select('%n.*', $targetTable)->from($targetTable);
$targetedArgs = array();
if ($filters instanceof ImplicitFilters) {
$targetedArgs = $filters->getTargetedArgs();
$filters = $filters->getFilters();
}
foreach ($filters as $filter) {
$args = array($filter);
if (is_string($filter) and array_key_exists($filter, $targetedArgs)) {
$args = array_merge($args, $targetedArgs[$filter]);
}
call_user_func_array(array($subFluent, 'applyFilter'), $args);
}
$this->fluent->leftJoin($subFluent, $targetTable)->on("[$currentTable].[$referencingColumn] = [$targetTable].[$targetTablePrimaryKey]");
}
$this->appliedJoins[] = $targetTable;
}
}
protected final function traverseToRelatedEntity($currentTable, Property $property)
{
if (!$property->hasRelationship()) {
throw new Exception("Property '$propertyName' in entity '$entityClass' doesn't have any relationship.");
}
$implicitFilters= array();
$propertyType = $property->getType();
if (is_subclass_of($propertyType, 'LeanMapper\\Entity')) {
$caller = new Caller($this, $property);
$implicitFilters = $this->mapper->getImplicitFilters($property->getType(), $caller);
}
$relationship = $property->getRelationship();
if ($relationship instanceof Relationship\HasOne) {
$targetTable = $relationship->getTargetTable();
$targetTablePrimaryKey = $this->mapper->getPrimaryKey($targetTable);
$referencingColumn = $relationship->getColumnReferencingTargetTable();
$this->joinRelatedTable($currentTable, $referencingColumn, $targetTable, $targetTablePrimaryKey, $implicitFilters);
} elseif ($relationship instanceof Relationship\BelongsTo) {
$targetTable = $relationship->getTargetTable();
$sourceTablePrimaryKey = $this->mapper->getPrimaryKey($currentTable);
$referencingColumn = $relationship->getColumnReferencingSourceTable();
$this->joinRelatedTable($currentTable, $sourceTablePrimaryKey, $targetTable, $referencingColumn, $implicitFilters);
} elseif ($relationship instanceof Relationship\HasMany) {
$sourceTablePrimaryKey = $this->mapper->getPrimaryKey($currentTable);
$relationshipTable = $relationship->getRelationshipTable();
$sourceReferencingColumn = $relationship->getColumnReferencingSourceTable();
$targetReferencingColumn = $relationship->getColumnReferencingTargetTable();
$targetTable = $relationship->getTargetTable();
$targetTablePrimaryKey = $this->mapper->getPrimaryKey($targetTable);
$this->joinRelatedTable($currentTable, $sourceTablePrimaryKey, $relationshipTable, $sourceReferencingColumn);
$this->joinRelatedTable($relationshipTable, $targetReferencingColumn, $targetTable, $targetTablePrimaryKey, $implicitFilters);
} else {
throw new Exception("Unknown relationship type. " . get_class($relationship) . ' given.');
}
return array_merge(array($targetTable), $this->getPropertiesByTable($targetTable));
}
protected final function parseStatement($statement)
{
if (!is_string($statement)) {
throw new Exception('Type of argument $statement is expected to be string. ' . gettype($statement) . ' given.');
}
$rootTableName = $this->sourceRepository->getTable();
list($rootEntityClass, $rootProperties) = $this->getPropertiesByTable($rootTableName);
$switches = array(
'@' => FALSE,
'"' => FALSE,
"'" => FALSE,
);
$output = '';
for ($i = 0; $i < strlen($statement) + 1; $i++) {
$ch = @$statement{$i} ?: '';
if ($switches['@'] === TRUE) {
if (preg_match('#^[a-zA-Z_]$#', $ch)) {
$propertyName .= $ch;
} else {
if (!array_key_exists($propertyName, $properties)) {
throw new Exception("Entity '$entityClass' doesn't have property '$propertyName'.");
}
$property = $properties[$propertyName];
if ($ch === '.') {
list($tableName, $entityClass, $properties) = $this->traverseToRelatedEntity($tableName, $property);
$propertyName = '';
} else {
if ($property->getColumn() === NULL)
{
if ($property->hasRelationship()) {
list($tableName, $entityClass) = $this->traverseToRelatedEntity($tableName, $property);
$column = $this->mapper->getPrimaryKey($tableName);
} else {
throw new Exception("Column missing in property '$propertyName' in entity '$entityClass'");
}
} else {
$column = $property->getColumn();
}
$output .= "[$tableName].[$column]";
$switches['@'] = FALSE;
$output .= $ch;
}
}
} elseif ($ch === '@' && $switches["'"] === FALSE && $switches['"'] === FALSE) {
$switches['@'] = TRUE;
$propertyName = '';
$properties = $rootProperties;
$tableName = $rootTableName;
$entityClass = $rootEntityClass;
} else {
if ($ch === '"' && $switches["'"] === FALSE) {
$switches['"'] = !$switches['"'];
} elseif ($ch === "'" && $switches['"'] === FALSE) {
$switches["'"] = !$switches["'"];
}
$output .= $ch;
}
}
return $output;
}
public function createQuery()
{
return $this->fluent->fetchAll();
}
protected function processToFluent($method, array $args = array())
{
call_user_func_array(array($this->fluent, $method), $args);
}
public function where($cond)
{
if (is_array($cond)) {
if (func_num_args() > 1) {
throw new Exception('Number of arguments is limited to 1 if the first argument is array.');
}
foreach ($cond as $key => $value) {
if (is_string($key)) {
$this->where($key, $value);
} else {
$this->where($value);
}
}
} else {
$args = func_get_args();
if (count($args) === 2 && preg_match('#^@[a-zA-Z_.]+$#', trim($args[0]))) {
$field = &$args[0];
$value = &$args[1];
if (is_array($value)) {
$field .= ' IN %in';
} else {
$field .= ' = ?';
}
}
$statement = &$args[0];
$statement = $this->parseStatement($statement);
$statement = "($statement)";
foreach ($args as &$arg) {
if ($arg instanceof Entity) {
$entityTable = $this->mapper->getTable(get_class($arg));
$idField = $this->mapper->getEntityField($entityTable, $this->mapper->getPrimaryKey($entityTable));
$arg = $arg->$idField;
}
}
$this->processToFluent('where', $args);
}
return $this;
}
public function orderBy($field)
{
if (is_array($field)) {
foreach ($field as $key => $value) {
if (is_string($key)) {
$this->orderBy($key)->asc($value);
} else {
$this->orderBy($value);
}
}
} else {
$field = $this->parseStatement($field);
$this->processToFluent('orderBy', array($field));
}
return $this;
}
public function asc($asc = TRUE)
{
if ($asc) {
$this->processToFluent('asc');
} else {
$this->processToFluent('desc');
}
return $this;
}
public function desc($desc = TRUE)
{
$this->asc(!$desc);
return $this;
}
public function limit($limit)
{
$this->processToFluent('limit', array($limit));
return $this;
}
public function offset($offset)
{
$this->processToFluent('offset', array($offset));
return $this;
}
}