/** * Invoke a prototyped resource from a caller context * * @param Next\Components\Object $caller * Caller Object * * @param string $method * Callable resource name * * @param array $args * Calling Arguments * * @return Next\Components\Object * Caller Object updated * * @throws Next\Components\Debug\Exception * Called resource is not known as a prototype nor as a extended method */ public function call(Object $caller, $method, array $args = array()) { if (isset(self::$prototypes[$method])) { // Merging always optional arguments with called arguments if (count($args) > 0) { ArrayUtils::insert(self::$prototypes[$method][1], $args); } else { // Nothing to Merge? OK! $args =& self::$prototypes[$method][1]; } $result = call_user_func_array(self::$prototypes[$method][0], $args); /** * @internal * * If operation results in an Object, let's return it * * This ensures operations of one type can return a different type */ if ($result instanceof Object) { return $result; } // Otherwise let's update caller Object return $caller->set($result); } throw \Next\Components\Debug\Exception::wrongUse('Method <strong>%s</strong> could not be matched against any methods in extended Context or prototyped functions', array($method)); }
/** * Add ORDER BY Clause(s) * * @param string|array $field * * <p>As string, field to lead the ordenation.</p> * * <p> * As associative array, keys are the fields and values * ordenation types * </p> * * @param string|optional $type * * <p>Type of ordenation: ASC or DESC.</p> * * <p> * If <strong>$field</strong> is an array, this value is not * immediately used * </p> * * @return Next\DB\Query\Builder * Builder Instance (Fluent Interface) * * @throws Next\DB\Query\QueryException * Ordenation field is empty or was considered an empty string */ public function order($field, $type = self::SQL_ORDER_ASC) { // Do we have multiple ORDER BY clause? if (is_array($field)) { if (is_array($type)) { // Both arguments are arrays, let's equalize their lengths... ArrayUtils::equalize($field, $type); // ... and combine to make recursion easily $field = array_combine($field, $type); } /** * @internal * Only first argument is array, so we expect it to be * an associative pair/value as field to order => ordenation type */ foreach ($field as $f => $t) { $this->order($f, $t); } } else { // Ensuring we have a Field to lead the ordenation $field = trim((string) $field); if (empty($field)) { throw QueryException::logic('Field to order results must be set as non-empty string'); } // Ensuring we have a ordening direction (even optional for most RDBMS) $type = trim((string) $type); if (empty($type)) { $type = self::SQL_ORDER_ASC; } /** * @internal * ORDER BY Clause cannot be "prepared", at least not as a question * mark placeholder, because for some reason PDO::prepare() will * try to wrap the values (field and ordenation type) into quotes, * invalidating this specific part of SQL Statement */ self::$parts[self::SQL_ORDER_BY][$field] = $type; } return $this; }
/** * Compare given value in a [List|of|Possible|Values] * * @param string $value * Value to compare * * @param string $list * List to compare given value * * @return string|NULL * * - If given parameter lacks the Separator Token OR * if it IS present in a List of Possible Values, it will be returned "as is" * * - Otherwise we'll try to find the proper Default Value. * If we succeed, this value will be returned. Otherwise NULL will */ private function compareWithList($value, $list) { // Mal-formed List? if (strpos($list, self::SEPARATOR_TOKEN) === FALSE) { // Let's use what we received return $value; } else { // Parameter is in the List? // Removing Default Value Definition of given list in order to split correctly $l = preg_replace(sprintf('/%s/', self::DEFAULT_VALUE_REGEXP), '', $list); if (ArrayUtils::in($value, explode(self::SEPARATOR_TOKEN, $l))) { return $value; } // No? Let's try to find the Default Value return $this->getDefaultValue($list); } }
/** * Parse one or more Routes, recursively * * @param string|array $routes * Route(s) to be parsed * * @param string $controller * Controller to whom belongs the Route(s) * * @param string $method * Method to whom belongs the Route(s) * * @throws Next\Tools\RoutesGenerator\RoutesGeneratorException * Route has less than 2 Components (a Request Method and a Route) * * @throws Next\Tools\RoutesGenerator\RoutesGeneratorException * Routes defined as single slash (usually for homepages) DO have * arguments (hierarchy concept) * * @throws Next\Tools\RoutesGenerator\RoutesGeneratorException * There is another Route with exactly the same definition, including * the Request Method */ private function parseRoutes($routes, $controller, $method, $basepath) { if (is_array($routes)) { foreach ($routes as $route) { $this->parseRoutes($route, $controller, $method, $basepath); } } else { // Listing Route Components $components = explode(',', $routes); if (count($components) < 2) { throw RoutesGeneratorException::invalidRouteStructure(array($routes, basename($controller), $method)); } // Shifting Request Method $requestMethod = trim(array_shift($components)); // ... and URI Route $URI = trim(array_shift($components)); // If we still have some components, all them will be treated as Route Params $params = array(); if (count($components) > 0) { $params = array_map('trim', $components); } // Parsing, fixing and complementing them /** * @internal * If defined URI is NOT a single slash, no trailing slash for it * But add a RegExp Border instead */ if ($URI != '/') { // Prepending Routes Basepath if present if (empty($basepath)) { $URI = sprintf('%s\\b', trim($URI, '/')); } else { $URI = sprintf('%s/%s\\b', trim($basepath, '/'), trim($URI, '/')); } /** * @internal * If we have a well designed structure, let's add RegExp Delim Captures Token too * * Routes pointing to a single slash do not have this token due hierarchical logic * These kind of Routes cannot even have any params, except the one reserved for Localization */ $URI .= self::DELIM_CAPTURE_TOKEN; } else { // Let's ensure single slash Routes have no params if (!empty($params)) { throw RoutesGeneratorException::malformedRoute(array($URI, basename($controller), $method)); } } // Adding an Always Optional Parameter for Localization $params[] = self::LOCALE_PARAM; // Let's parse Required and Optional Params $required = $this->parseParams($params, self::REQUIRED_PARAMS_TOKEN); $optional = $this->parseParams($params, self::OPTIONAL_PARAMS_TOKEN); // Searching for Duplicates $offset = ArrayUtils::search($this->results, $URI, 'route'); // We found one... if ($offset != -1) { // ... let's compare with the Request Method if ($this->results[$offset]['requestMethod'] == $requestMethod) { // Yeah! We have a Duplicate throw RoutesGeneratorException::duplicatedRoute(array($requestMethod, $URI, basename($controller), $method)); } } // Preparing Parsed Route to be recorded $this->results[] = array('requestMethod' => $requestMethod, 'route' => $URI, 'params' => array('required' => $required, 'optional' => $optional), 'class' => $controller, 'method' => $method); } }
/** * Invoke an extended resource from a caller context * * @param Next\Components\Object $caller * Caller Object * * @param string $method * Callable resource name * * @param array $args * Calling Arguments * * @return mixed|boolean * Return what extended method returns. * * If invoking process fail, false will returned. * * @throws Next\Components\Debug\Exception * Called resource is not known as an extended method */ public function call(Object $caller, $method, array $args = array()) { $caller = $caller->getClass()->getName(); if (array_key_exists($caller, $this->callables)) { $offset = ArrayUtils::search($this->callables[$caller], $method); if ($offset != -1) { try { $reflector = new \ReflectionMethod($this->callables[$caller][$offset][0], $method); return $reflector->invokeArgs($this->callables[$caller][$offset][0], $args); } catch (\ReflectionException $e) { return FALSE; } } } // Unknown Method throw \Next\Components\Debug\Exception::wrongUse('Method <strong>%s</strong> could not be matched against any methods in extended Context', array($method)); }
/** * Get Context Options * * @param string|optional $option * Desired Context Option * * @param string|optional $wrapper * Optional Context Option Wrapper * * @return array|boolean * * If <strong>$option</strong> is NOT null and we can't find a match * option FALSE is returned. Otherwise the desired option value * will. * * If <strong>$option</strong> argument IS null, all the options defined will be * returned * * <strong>$wrapper</strong> argument, if set, can restrict the search and thus avoid a * value to be found */ public function getOptions($option = NULL, $wrapper = NULL) { if (!is_null($option)) { // Looking for the array key where the option COULD be $key = ArrayUtils::search($this->options, $option, $wrapper); // If it exists, let's return it if ($key != -1 && isset($this->options[$key][$option])) { return $this->options[$key][$option]; } return FALSE; } return $this->options; }
/** * Build the Class Map * * @param string $format * Output Format from Available Formats * * @param mixed|optional $options * List of Options to affect Database Drivers. Acceptable values are: * * <p> * * <ul> * * <li>Associative and multidimensional array</li> * * <li> * * An {@link http://php.net/manual/en/reserved.classes.php stdClass Object} * * </li> * * <li>A well formed Next\Components\Parameter Object</li> * * </ul> * * </p> * * <p>There are no Common Options defined so far.</p> * * <p> * All the arguments taken in consideration are defined in * and by factored classes * </p> * * @see Next\Components\Parameter * * @return mixed|void * If chosen format allow the result to be returned, it will * accordingly to the its rules * * Otherwise, nothing is returned * * @throws Next\Tools\ClassMapper\ClassMapperException * Invalid or unsupported Mapping Format */ public function build($format, $options = NULL) { if (!in_array((string) $format, $this->available)) { throw ClassMapperException::unknown(); } // Building Definitions $map = new \stdClass(); $iterator =& $this; iterator_apply($iterator, function () use($iterator, $map) { $file = $iterator->current(); $namespace = ''; if (!empty($file->namespace)) { $namespace = sprintf('%s\\', $file->namespace); } $classname = $namespace . $file->classname; $map->{$classname} = $file->getRealPath(); return TRUE; }); // Building Output Format Classname $class = sprintf('Next\\Tools\\ClassMapper\\%s', (string) $format); // Instantiating Object $instance = new $class($options); // Building! return $instance->build(ArrayUtils::map($map)); }