/** * @param string $alias * @param mixed $setting * @return Field|NULL * @throws Exception */ public static function fromJSONAliasSetting($alias, $setting) { $fieldSetting = NULL; if (!isset($setting['resources'])) { // not many-to-one $field = isset($setting['field']) ? $setting['field'] : $alias; $type = isset($setting['type']) ? Type\Variable::typeFromString($setting['type']) : Type\Variable::STRING; if (!isset($type)) { throw new Exception(Exception::Config, "Type '{$setting['type']}' is invalid for alias '{$alias}'"); } $fieldSetting = Settings::field($alias, $field, $type); } return $fieldSetting; }
/** * @param FREST\Resource $resource * @throws FREST\Exception */ public function setupWithResource($resource) { parent::setupWithResource($resource); // Resource ID if (isset($this->resourceID)) { /** @var Setting\Field $fieldSetting */ $this->resource->getIDField($fieldSetting); $idType = $fieldSetting->getVariableType(); $parsedResourceID = Type\Variable::castValue($this->resourceID, $idType); if (!isset($parsedResourceID)) { $typeString = Type\Variable::getString($idType); throw new FREST\Exception(FREST\Exception::InvalidType, "Resource ID needs to be of type '{$typeString}' but was supplied with '{$this->resourceID}'"); } $this->resourceID = $parsedResourceID; } }
/** * @param bool $forceRegen * @return Result\Delete * @throws FREST\Exception */ public function generateResult($forceRegen = FALSE) { $this->frest->startTimingForLabel(Type\Timing::PROCESSING, 'delete'); $otherResult = parent::generateResult($forceRegen); if (isset($otherResult)) { return $otherResult; } $pdo = $this->frest->getConfig()->getPDO(); /** @var Setting\Field $idFieldSetting */ $this->resource->getIDField($idFieldSetting); $isPerformingTransaction = FALSE; if (count($this->tableDeleteSpecs) > 1) { $pdo->beginTransaction(); $isPerformingTransaction = TRUE; } $this->frest->stopTimingForLabel(Type\Timing::PROCESSING, 'delete'); /** @var Spec\TableDelete $tableDeleteSpec */ foreach ($this->tableDeleteSpecs as $tableDeleteSpec) { $table = $tableDeleteSpec->getTable(); $idFieldName = $this->resource->getIDFieldForTable($table); $this->frest->startTimingForLabel(Type\Timing::SQL, 'delete'); $sql = "DELETE FROM {$table} WHERE {$idFieldName} = :_id"; $deleteStmt = $pdo->prepare($sql); $deleteStmt->bindValue(':_id', $this->resourceID, Type\Variable::pdoTypeFromVariableType($idFieldSetting->getVariableType())); if (!$deleteStmt->execute()) { if ($isPerformingTransaction) { $pdo->rollBack(); } throw new FREST\Exception(FREST\Exception::SQLError, 'Error deleting from database'); } $this->frest->stopTimingForLabel(Type\Timing::SQL, 'delete'); } $this->frest->startTimingForLabel(Type\Timing::SQL, 'delete'); if ($isPerformingTransaction) { $pdo->commit(); } $this->frest->stopTimingForLabel(Type\Timing::SQL, 'delete'); $this->result = new Result\Delete(); return $this->result; }
/** * @param string $valueString * * @return bool */ public function validateValue($valueString) { $values = explode(',', $valueString); $valueCount = count($values); $minParamsRequired = 0; /** @var FunctionParam $parameter */ foreach ($this->parameters as $parameter) { if (!$parameter->getRequired()) { break; } $minParamsRequired++; } if ($valueCount < $minParamsRequired || $valueCount > count($this->parameters)) { return FALSE; } foreach ($values as $i => $value) { /** @var FunctionParam $parameter */ $parameter = $this->parameters[$i]; $castedValue = Type\Variable::castValue($value, $parameter->getVariableType()); if (!isset($castedValue)) { return FALSE; } } return TRUE; }
/** * @throws FREST\Exception */ protected function setupResourceFunction() { $resourceFunctions = $this->resource->getResourceFunctions(); // check if valid Func name if (!isset($resourceFunctions) || !isset($resourceFunctions[$this->resourceFunctionName])) { throw new FREST\Exception(FREST\Exception::ResourceFunctionDoesntExist, "Function name: '{$this->resourceFunctionName}'"); } /** @var Func\Resource $resourceFunction */ $resourceFunction = $resourceFunctions[$this->resourceFunctionName]; $resourceFunctionParameters = $resourceFunction->getParameters(); // check method $requiredMethod = $resourceFunction->getMethod(); $currentMethod = $this->frest->getMethod(); if ($requiredMethod != $currentMethod) { $currentMethodString = Type\Method::getString($currentMethod); $requiredMethodString = Type\Method::getString($requiredMethod); throw new FREST\Exception(FREST\Exception::MismatchingResourceFunctionMethod, "Requires '{$requiredMethodString}' but using '{$currentMethodString}'"); } // check for invalid parameters and build parameter list for Func $functionParameters = array(); foreach ($this->parameters as $parameterName => $value) { $isValidMiscParam = isset($this->miscParameters[$parameterName]); if (!$isValidMiscParam) { if (!isset($resourceFunctionParameters[$parameterName])) { throw new FREST\Exception(FREST\Exception::InvalidFunctionParameter, "Parameter name: '{$parameterName}'"); } $functionParameters[$parameterName] = $value; } } if (isset($resourceFunctionParameters) && count($resourceFunctionParameters) > 0) { // check for all required parameters /** @var Func\FunctionParam $parameter */ $missingParameterNames = array(); foreach ($resourceFunctionParameters as $parameterName => $parameter) { // check type if (isset($functionParameters[$parameterName])) { $variableType = $parameter->getVariableType(); $value = $functionParameters[$parameterName]; $castedValue = Type\Variable::castValue($value, $variableType); if (!isset($castedValue)) { $typeString = Type\Variable::getString($variableType); throw new FREST\Exception(FREST\Exception::InvalidType, "Expecting parameter '{$parameterName}' to be of type '{$typeString}' but received '{$value}'"); } $functionParameters[$parameterName] = $castedValue; } else { if ($parameter->getRequired()) { $missingParameterNames[] = $parameterName; } } } if (count($missingParameterNames) > 0) { $missingString = implode(', ', $missingParameterNames); throw new FREST\Exception(FREST\Exception::MissingRequiredFunctionParameter, "Parameter name: '{$missingString}'"); } } // Check for Func implementation existence if (!method_exists($this->resource, $this->resourceFunctionName)) { throw new FREST\Exception(FREST\Exception::ResourceFunctionMissing, "Function name: '{$this->resourceFunctionName}', resource: '{$this->resource->getName()}'"); } if ($resourceFunction->getRequiresResourceID()) { $functionParameters['resourceID'] = $this->resourceID; } $this->resourceFunction = $resourceFunction; $this->resourceFunctionParameters = $functionParameters; }
/** * @param mixed $valueToCheck * @param int $valueVariableType * @param array $functions * @param mixed $parsedValue * @param int $parsedValueVariableType * * @return Func\Condition * @throws FREST\Exception */ private function checkForFunctions($valueToCheck, $valueVariableType, $functions, &$parsedValue, &$parsedValueVariableType) { $functionUsed = NULL; /** @var Func\Condition $function */ foreach ($functions as $function) { $functionName = $function->getName(); $functionParameters = $function->getParameters(); $functionReplacements = $function->getReplacements(); /** @var Func\FunctionParam $firstParameter */ $firstParameter = $functionParameters[0]; $beginsWithFunctionOpen = substr_compare("{$functionName}(", $valueToCheck, 0, strlen($functionName) + 1, TRUE) === 0; $endsWithFunctionClose = substr($valueToCheck, -1) === ')'; if ($beginsWithFunctionOpen && $endsWithFunctionClose) { $functionUsed = $function; $innerValue = trim(substr($valueToCheck, strlen($functionName) + 1, -1)); if (strlen($innerValue) == 0) { throw new FREST\Exception(FREST\Exception::InvalidValue, "Empty value specified in Func '{$functionName}'"); } if (isset($functionReplacements)) { foreach ($functionReplacements as $old => $new) { $innerValue = str_replace($old, $new, $innerValue); } } $valuesToConcatenate = NULL; $innerValueVariableType = Type\Variable::stricterVariableType($firstParameter->getVariableType(), $valueVariableType); $castedValue = Type\Variable::castValue($innerValue, $innerValueVariableType); if (!isset($castedValue)) { $variableTypeString = Type\Variable::getString($innerValueVariableType); throw new FREST\Exception(FREST\Exception::InvalidType, "Expecting value for Func '{$functionName}' to be of type '{$variableTypeString}' but received '{$innerValue}'"); } $parsedValue = $castedValue; $parsedValueVariableType = $innerValueVariableType; break; // found use of Func already, don't keep checking } } return $functionUsed; }
/** * @param FREST\Resource $resource * @return array * @throws FREST\Exception */ private function generateCreateSpecs($resource) { $createSpecs = array(); $createSettings = $resource->getCreateSettings(); if (isset($this->resourceID)) { /** @var Setting\Field $idFieldSetting */ $idFieldName = $this->resource->getIDField($idFieldSetting); $idAlias = $this->resource->getAliasForField($idFieldName); $idCreateSpec = new Spec\Create($idAlias, $idFieldName, $this->resourceID, $idFieldSetting->getVariableType()); $createSpecs[$idAlias] = $idCreateSpec; } /** @var Setting\Create $createSetting */ foreach ($createSettings as $createSetting) { $alias = $createSetting->getAlias(); if (isset($this->parameters[$alias])) { $value = $this->parameters[$alias]; $field = $resource->getFieldForAlias($alias); $fieldSetting = $resource->getFieldSettingForAlias($alias); $variableType = $fieldSetting->getVariableType(); // Type checking $castedValue = Type\Variable::castValue($value, $variableType); if (!isset($castedValue)) { $typeString = Type\Variable::getString($variableType); throw new FREST\Exception(FREST\Exception::InvalidType, "Expecting '{$alias}' to be of type '{$typeString}' but received '{$value}'"); } // Condition Func $conditionFunction = $createSetting->getConditionFunction(); if (isset($conditionFunction)) { if (!method_exists($resource, $conditionFunction)) { throw new FREST\Exception(FREST\Exception::ConditionFunctionMissing, "Function name: '{$conditionFunction}', resource: '{$this->resource->getName()}'"); } $isValueValid = $resource->{$conditionFunction}($castedValue); if (!$isValueValid) { throw new FREST\Exception(FREST\Exception::InvalidFieldValue, "Field: '{$alias}'"); } } // Filter Func $filterFunction = $createSetting->getFilterFunction(); if (isset($filterFunction)) { if (!method_exists($resource, $filterFunction)) { throw new FREST\Exception(FREST\Exception::FilterFunctionMissing, "Function name: '{$filterFunction}', resource: '{$resource->getName()}'"); } $castedValue = $resource->{$filterFunction}($castedValue); } $createSpec = new Spec\Create($alias, $field, $castedValue, $variableType); $createSpecs[$alias] = $createSpec; } else { if ($createSetting->getRequired()) { // get list of all parameters required but not set $missingParameters = array(); /** @var Setting\Create $aCreateSetting */ foreach ($createSettings as $aCreateSetting) { $alias = $aCreateSetting->getAlias(); if (!isset($this->parameters[$alias]) && $aCreateSetting->getRequired()) { $missingParameters[] = $alias; } } $missingParametersString = implode(', ', $missingParameters); throw new FREST\Exception(FREST\Exception::MissingRequiredParams, "Missing parameters: {$missingParametersString}"); } } } if (count($createSpecs) > 0) { return $createSpecs; } return NULL; }
/** * Insert any data after object is retrieved such as computed aliases and converting * sub-resource fields into child objects. * * @param FREST\Resource $resource * @param array $objects * @param array $readSettings * @param string $resourceAlias * * @throws \FREST\Exception * @throws Exception */ protected function parseObjects($resource, &$objects, $readSettings, $resourceAlias = NULL) { // stores the read Setting that are just an ComputedRead $computedReadSettings = array(); $timerInstance = isset($resourceAlias) ? $resourceAlias . '-parse' : 'parse'; if (isset($readSettings)) { /** @var Setting\Read $readSetting */ foreach ($readSettings as $readSetting) { $this->frest->startTimingForLabel(Type\Timing::POST_PROCESSING, $timerInstance); $alias = $readSetting->getAlias(); $partialSubKey = isset($resourceAlias) ? "{$resourceAlias}.{$alias}" : $alias; if ($readSetting instanceof Setting\ComputedRead) { $computedReadSettings[$alias] = $readSetting; } else { if ($readSetting instanceof Setting\PluralResourceRead) { /** @var Setting\PluralResourceRead $readSetting */ $parameters = $readSetting->getParameters(); // overwrite 'fields' parameter if partial syntax found $newFields = $this->generateNewFields($partialSubKey); if (isset($newFields)) { $parameters['fields'] = implode(',', $newFields); } $requiredAliases = $readSetting->getRequiredAliases(); $loadedResource = $this->getLoadedResource($readSetting->getResourceName()); foreach ($objects as &$object) { $requestParameters = array(); foreach ($parameters as $field => $parameter) { if (isset($requiredAliases[$field])) { $requiredAlias = $requiredAliases[$field]; $requiredAliasValuePlaceholder = $loadedResource->injectValue($requiredAlias); $parameter = str_replace($requiredAliasValuePlaceholder, $object->{$requiredAlias}, $parameter); } $requestParameters[$field] = $parameter; } $this->frest->stopTimingForLabel(Type\Timing::POST_PROCESSING, $timerInstance); $request = new PluralRead($this->frest, $requestParameters, NULL, $readSetting->getAlias()); $request->setWasInternallyLoaded(TRUE); $request->setupWithResource($loadedResource); /** @var Result\PluralRead $result */ $result = $request->generateResult(); $this->frest->startTimingForLabel(Type\Timing::POST_PROCESSING, $timerInstance); $object->{$alias} = $result->getResourceObjects(); } } else { if ($readSetting instanceof Setting\SingularResourceRead) { /** @var Setting\SingularResourceRead $readSetting */ $loadedResource = $this->getLoadedResource($readSetting->getResourceName()); if (isset($this->partialSubReadSettings[$partialSubKey])) { $subReadSettings = $this->partialSubReadSettings[$partialSubKey]; $this->addRequiredReadSettings($loadedResource, $subReadSettings); } else { $subReadSettings = $this->getLoadedResourceReadSettings($loadedResource, $readSetting); } $subObjects = array(); // remove table-prefixed properties on object as they should be on a sub-object instead foreach ($objects as &$object) { if (!isset($object->{$alias})) { $object->{$alias} = new \stdClass(); $subObjects[] =& $object->{$alias}; } if (!isset($subReadSettings)) { continue; } foreach ($subReadSettings as $subReadSetting) { if ($subReadSetting instanceof Setting\FieldRead) { /** @var Setting\FieldRead $subReadSetting */ $subAlias = $subReadSetting->getAlias(); $subField = $loadedResource->getFieldForAlias($subAlias); $subTable = $loadedResource->getTableForField($subField); $subTableKey = isset($resourceAlias) ? "{$subTable}-{$resourceAlias}-{$alias}" : "{$subTable}-{$alias}"; $subTableAbbrv = $this->getTableAbbreviation($subTableKey); $subProperty = "{$subTableAbbrv}_{$subAlias}"; $subValue = $object->{$subProperty}; $object->{$alias}->{$subAlias} = $subValue; unset($object->{$subProperty}); } else { if ($subReadSetting instanceof Setting\SingularResourceRead) { // move properties of object that should belong to subObject (only nests once? idk) $subLoadedResource = $this->getLoadedResource($subReadSetting->getResourceName()); $subReadAlias = $subReadSetting->getAlias(); $partialDeepKey = isset($resourceAlias) ? "{$resourceAlias}.{$alias}.{$subReadAlias}" : "{$alias}.{$subReadAlias}"; if (isset($this->partialSubReadSettings[$partialDeepKey])) { $deepReadSettings = $this->partialSubReadSettings[$partialDeepKey]; $this->addRequiredReadSettings($subLoadedResource, $deepReadSettings); } else { $deepReadSettings = $this->getLoadedResourceReadSettings($subLoadedResource, $subReadSetting); } foreach ($deepReadSettings as $deepReadSetting) { if ($deepReadSetting instanceof Setting\FieldRead) { $deepAlias = $deepReadSetting->getAlias(); $deepField = $subLoadedResource->getFieldForAlias($deepAlias); $deepTable = $subLoadedResource->getTableForField($deepField); $deepTableKey = "{$deepTable}-{$alias}-{$subReadAlias}"; $deepTableAbbrv = $this->getTableAbbreviation($deepTableKey); $deepProperty = "{$deepTableAbbrv}_{$deepAlias}"; $deepValue = $object->{$deepProperty}; $object->{$alias}->{$deepProperty} = $deepValue; unset($object->{$deepProperty}); } } } } } } // parse sub-objects as well $subAlias = $readSetting->getAlias(); $subResourceAlias = isset($resourceAlias) ? "{$resourceAlias}.{$subAlias}" : $subAlias; $this->parseObjects($loadedResource, $subObjects, $subReadSettings, $subResourceAlias); } else { if ($readSetting instanceof Setting\FieldRead) { /** @var Setting\FieldRead $readSetting */ $fieldSetting = $resource->getFieldSettingForAlias($alias); $variableType = $fieldSetting->getVariableType(); foreach ($objects as &$object) { $value = Type\Variable::castValue($object->{$alias}, $variableType); $filterFunction = $readSetting->getFilterFunction(); if (isset($filterFunction)) { if (!method_exists($resource, $filterFunction)) { throw new FREST\Exception(FREST\Exception::FilterFunctionMissing, "Function name: '{$filterFunction}', resource: '{$resource->getName()}'"); } $value = $resource->{$filterFunction}($value); } $object->{$alias} = $value; } } } } } $this->frest->stopTimingForLabel(Type\Timing::POST_PROCESSING, $timerInstance); } } $this->frest->startTimingForLabel(Type\Timing::POST_PROCESSING, $timerInstance); // use first object as reference to see what aliases have been set so far (used for computed aliases below) $oldObjects = reset($objects); $refObject =& $oldObjects; /* // generate a list of aliases that have so-far been set on each object (used for computed aliases) $aliasesDefinedOnObjects = array(); $objectVars = get_object_vars($firstObject); foreach ($objectVars as $alias=>$value) { if (isset($value)) { $aliasesDefinedOnObjects[$alias] = $alias; } } */ // computed aliases $lastComputedReadSettingCount = count($computedReadSettings); $resourceComputer = NULL; if ($lastComputedReadSettingCount > 0) { $frestConfig = $this->getFREST()->getConfig(); $formattedResourceName = ucfirst($resource->getName()); $resourceDir = $frestConfig->getResourceDirectory(); $computerClassPath = "{$resourceDir}/{$formattedResourceName}.php"; $computerClassName = "\\FREST\\Computer\\{$formattedResourceName}"; /** @noinspection PhpIncludeInspection */ require_once $computerClassPath; if (!class_exists($computerClassName, FALSE)) { throw new Exception(Exception::Config, "Class '{$formattedResourceName}' not found in file '{$classPath}'"); } $resourceComputer = new $computerClassName($frestConfig->getContext()); } $failedComputingSettings = FALSE; while ($lastComputedReadSettingCount > 0) { /** @var Setting\ComputedRead $computedReadSetting */ foreach ($computedReadSettings as $computedReadSetting) { $alias = $computedReadSetting->getAlias(); // determine if all aliases required for this computed alias have been defined // (should only NOT be set if the required alias is also a computed column and // hasn't been computed yet) $hasAllAliasesRequired = TRUE; $requiredAliases = $computedReadSetting->getRequiredAliases(); foreach ($requiredAliases as $requiredAlias) { $subAliases = NULL; $requiredAliasFromPartial = self::getHandleAndValues($requiredAlias, $subAliases) ?: $requiredAlias; // TODO: error check required alias partial syntax if (!isset($refObject->{$requiredAliasFromPartial})) { $hasAllAliasesRequired = FALSE; break; } if (isset($subAliases)) { foreach ($subAliases as $subAlias) { if (!isset($refObject->{$requiredAliasFromPartial}->{$subAlias})) { $hasAllAliasesRequired = FALSE; break; } } } if (!$hasAllAliasesRequired) { break; } } // move on to the next computed alias (assuming there is one) // we'll compute this alias later once its required/computed alias // is determined if (!$hasAllAliasesRequired) { continue; } $function = $computedReadSetting->getFunction(); if (!method_exists($resourceComputer, $function)) { throw new FREST\Exception(FREST\Exception::ComputationFunctionMissing, "The function '{$function}' is not defined in resource computer '{$resource->getName()}'"); } // keep track of what aliases have been defined for use by other computed aliases unset($computedReadSettings[$alias]); foreach ($objects as &$object) { $object->{$alias} = $resourceComputer->{$function}($object); } } // if we run through loop and no aliases have been computed, break out of loop and fail $currentComputedReadSettingCount = count($computedReadSettings); if ($currentComputedReadSettingCount >= $lastComputedReadSettingCount) { $failedComputingSettings = TRUE; break; } $lastComputedReadSettingCount = $currentComputedReadSettingCount; } if ($failedComputingSettings) { throw new FREST\Exception(FREST\Exception::Config, 'All computed aliases could not be computed. Check your config and make sure there are no malformed requiredField settings'); } $resourceName = $resource->getName(); if (isset($readSettings)) { foreach ($readSettings as $readSetting) { $alias = $readSetting->getAlias(); // remove property if added only by requirement of other properties $subAliasesToRemove = isset($this->requiredAliasesAdded[$resourceName][$alias]) ? $this->requiredAliasesAdded[$resourceName][$alias] : NULL; if (isset($subAliasesToRemove)) { if (is_array($subAliasesToRemove)) { foreach ($objects as &$object) { foreach ($subAliasesToRemove as $subAlias) { unset($object->{$alias}->{$subAlias}); } $isEmpty = count((array) $object->{$alias}) == 0; if ($isEmpty) { unset($object->{$alias}); } } } else { foreach ($objects as &$object) { unset($object->{$alias}); } } } } } $this->frest->stopTimingForLabel(Type\Timing::POST_PROCESSING, $timerInstance); }
/** * @param FREST\Resource $resource * @return array * @throws FREST\Exception */ private function generateUpdateSpecs($resource) { $updateSpecs = array(); $updateSettings = $resource->getUpdateSettings(); /** @var Setting\Update $updateSetting */ foreach ($updateSettings as $updateSetting) { $alias = $updateSetting->getAlias(); if (isset($this->parameters[$alias])) { $value = $this->parameters[$alias]; $field = $resource->getFieldForAlias($alias); $fieldSetting = $resource->getFieldSettingForAlias($alias); $variableType = $fieldSetting->getVariableType(); // Type checking $castedValue = Type\Variable::castValue($value, $variableType); if (!isset($castedValue)) { $typeString = Type\Variable::getString($variableType); throw new FREST\Exception(FREST\Exception::InvalidType, "Expecting '{$alias}' to be of type '{$typeString}' but received '{$value}'"); } // Condition Func $conditionFunction = $updateSetting->getConditionFunction(); if (isset($conditionFunction)) { if (!method_exists($resource, $conditionFunction)) { throw new FREST\Exception(FREST\Exception::ConditionFunctionMissing, "Function name: '{$conditionFunction}', resource: '{$resource->getName()}'"); } $isValueValid = $resource->{$conditionFunction}($castedValue); if (!$isValueValid) { throw new FREST\Exception(FREST\Exception::InvalidFieldValue, "Field: '{$alias}'"); } } // Filter Func $filterFunction = $updateSetting->getFilterFunction(); if (isset($filterFunction)) { if (!method_exists($resource, $filterFunction)) { throw new FREST\Exception(FREST\Exception::FilterFunctionMissing, "Function name: '{$filterFunction}', resource: '{$resource->getName()}'"); } $castedValue = $resource->{$filterFunction}($castedValue); } $updateSpec = new Spec\Update($alias, $field, $castedValue, $variableType); $updateSpecs[$alias] = $updateSpec; } } return $updateSpecs; }