/** * Get the singleton instance. * * @param string $identifier The identifier (string), * @param static [$instance] New value for the identifier. * * @return static */ public static function instance($identifier = 'default') { if ($identifier instanceof static) { // Given identfier already is an instance? return $identifier; } if (array_key_exists($identifier, static::$instances) === false) { if ($identifier === 'default') { $instance = static::defaultInstance(); static::$instances['default'] = $instance; return $instance; } throw new InfoException(static::class . '::instances["' . $identifier . '"] is not configured', 'Available instances: ' . \Sledgehammer\quoted_human_implode(' or ', array_keys(static::$instances))); } $connection = static::$instances[$identifier]; if ($identifier instanceof static) { return $identifier; } if (is_string($connection)) { // A reference to another instance? return static::instance($connection); } if (is_array($connection) || get_class($connection) === 'Closure') { // A callback which creates the instance? static::$instances[$identifier] = call_user_func($connection); return static::instance($identifier); } return $connection; }
/** * Controleer of de php extenties geinstalleerd zijn. */ public function test_missing_extentions() { if (!function_exists('token_get_all')) { $this->fail('PHP extention "tokenizer" is required for this UnitTest'); return; } $whitelist = array(); if ($this->onlyClassesFolder) { // Alleen de classes mappen van de modules inlezen $modules = Framework::getModules(); foreach ($modules as $module) { $this->checkFolder($module['path'] . 'classes/'); } } else { // check all php files within $path $this->checkFolder(\Sledgehammer\PATH); } foreach ($this->missingExtensions as $extension => $definition) { $files = $this->extensionUsedIn[$extension]; foreach ($files as $i => $filename) { if (isset($whitelist[$extension]) && in_array($filename, $whitelist[$extension])) { unset($files[$i]); } } if (count($files) > 0) { $this->fail('Missing php extension "' . $extension . '". Function or class "' . $definition . '" is used in ' . \Sledgehammer\quoted_human_implode(' and', $files)); } } $this->assertTrue(true, 'All required extenstion are installed'); }
/** * Convert raw backend data into an object instance. * * @param mixed $data * @param ModelConfig $config * @param string|null $index (optional) speedoptim: Prevents resolving the index again. * @param bool $reload true: Overwrite properties in the instance. * * @return stdClass */ protected function convertToInstance($data, $config, $index = null, $reload = false) { if ($index === null) { $index = $this->resolveIndex($data, $config); } elseif (empty($this->objects[$config->name][$index])) { throw new Exception('Invalid index: "' . $index . '" for ' . $config->name); } if ($reload) { $instance = $this->objects[$config->name][$index]['instance']; if ($instance === null) { throw new Exception('No instance loaded'); } } elseif ($this->objects[$config->name][$index]['instance'] !== null) { throw new Exception('Instance already loaded, use reload parameter to reload'); } else { // new instance $class = $config->class; $instance = new $class(); } // Validate the properties in the class. if ($this->validated[$config->name] === false) { // No validated? $properties = get_object_vars($instance); $paths = array_merge($config->properties, $config->ignoreProperties, array_keys($config->belongsTo), array_keys($config->hasMany)); foreach ($paths as $path) { $tokens = PropertyPath::parse($path); if (in_array($tokens[0][0], array(PropertyPath::TYPE_ANY, PropertyPath::TYPE_ELEMENT))) { unset($properties[$tokens[0][1]]); } } if (count($properties) !== 0) { $causes = array('1. The column is missing in the backend/database.', '2. The relation/foreign key is missing in the backend/database.', '3. The column has diffent name than the property. Set the ModelConfig->properties[columname] = propertyname.', '4. The property should be ignored by the repository. Add the property to the ModelConfig->ignoreProperties.', '5. The relation couldn\'t be detected. Add an entry to ModelConfig->hasMany or ModelConfig->belongsTo.'); throw new InfoException('Unexpected property: ' . \Sledgehammer\quoted_human_implode(' and ', array_keys($properties)) . ' in ' . $config->class . ' class for "' . $config->name . '"', '<b>Possible causes:</b><br />' . implode('<br />', $causes)); } $this->validated[$config->name] = true; } // Map the data onto the instance foreach ($config->properties as $sourcePath => $targetPath) { $value = PropertyPath::get($sourcePath, $data); if (isset($config->readFilters[$sourcePath])) { $value = \Sledgehammer\filter($value, $config->readFilters[$sourcePath]); } PropertyPath::set($targetPath, $value, $instance); } foreach ($config->belongsTo as $property => $relation) { if (isset($relation['convert'])) { $value = $this->convert($relation['model'], PropertyPath::get($relation['convert'], $data)); PropertyPath::set($property, $value, $instance); } else { $belongsToId = $data[$relation['reference']]; if ($belongsToId === null) { PropertyPath::set($property, null, $instance); } else { if (empty($relation['model'])) { // No model given? throw new Exception('Invalid config: ' . $config->name . '->belongsTo[' . $property . '][model] not set'); } if ($relation['useIndex']) { $belongsToIndex = $this->resolveIndex($belongsToId); $belongsToInstance = @$this->objects[$relation['model']][$belongsToIndex]['instance']; } else { $belongsToInstance = null; } if ($belongsToInstance !== null) { $instance->{$property} = $belongsToInstance; } else { $fields = array($relation['id'] => $belongsToId); $instance->{$property} = new BelongsToPlaceholder($this->ref() . '/' . $config->name . '/' . $property, $instance, $fields); } } } } foreach ($config->hasMany as $property => $relation) { if (isset($relation['convert'])) { $collection = new RepositoryCollection(PropertyPath::get($relation['convert'], $data), $relation['model'], $this->ref()); PropertyPath::set($property, $collection, $instance); } else { $instance->{$property} = new HasManyPlaceholder($this->ref() . '/' . $config->name . '/' . $property, $instance); } } $this->_triggerEvent($instance, 'load', $instance, ['repository' => $this->ref(), 'model' => $config->name], $this); return $instance; }
/** * Download and install a PEAR package. * * @throws Exceptions on failure * * @param string $package * @param string $version * @param array $options array( * 'version' = Install a specific version * 'target' => alternative target directory * 'channel' => specifiy the channel * ) */ public function install($package, $options = []) { $version = \Sledgehammer\array_value($options, 'version') ?: 'stable'; if (isset($options['channel'])) { $channel = $options['channel']; $this->addChannel($channel); if (empty($this->channels[$channel]['packages'][$package])) { if (isset($this->channels[$channel]['packages'])) { foreach ($this->channels[$channel]['packages'] as $name => $info) { if (strcasecmp($name, $package) === 0) { return $this->install($name, $options); } } } throw new InfoException('Package "' . $package . '" not found in channel: ' . $channel, \Sledgehammer\quoted_human_implode(' and ', array_keys($this->channels[$channel]['packages']))); } $packageLocation =& $this->channels[$channel]['packages'][$package]; } else { if (count($this->channels) === 0) { $this->addChannel('pear.php.net'); } if (empty($this->packages[$package])) { foreach ($this->packages as $name => $channel) { if (strcasecmp($name, $package) === 0) { return $this->install($name, $options); } } throw new InfoException('Package "' . $package . '" not found in channels: ' . \Sledgehammer\quoted_human_implode(' and ', array_keys($this->channels)), 'Available packages: ' . \Sledgehammer\quoted_human_implode(' and ', array_keys($this->packages))); } $packageLocation =& $this->channels[$this->packages[$package]]['packages'][$package]; } $release = $this->findRelease($packageLocation, $version); if (\Sledgehammer\array_value($packageLocation, 'installed') === $version) { return; } $this->trigger('installing', $this, $package, $version); $tmpFolder = \Sledgehammer\TMP_DIR . 'PearInstaller/'; $folderName = $package . '-' . $version; $tarFile = $tmpFolder . $folderName . '/package.tar'; \Sledgehammer\mkdirs(dirname($tarFile)); if (file_exists($tarFile) === false) { // Is this package already in the tmp folder Curl::download($release->g . '.tar', $tarFile); } chdir(dirname($tarFile)); system('tar xf ' . escapeshellarg($tarFile), $exit); if ($exit !== 0) { throw new Exception('Unable to untar "' . $tarFile . '"'); } if (file_exists(dirname($tarFile) . '/package2.xml')) { $info = simplexml_load_file(dirname($tarFile) . '/package2.xml'); } else { $info = simplexml_load_file(dirname($tarFile) . '/package.xml'); } // Install dependencies first foreach ($info->dependencies->required->package as $dependancy) { if ($dependancy->conflicts) { // \Sledgehammer\notice('Dependancy "'.$dependancy->name.'" for "'.$package.'" <conflicts />'); continue; } $this->install((string) $dependancy->name, array('channel' => (string) $dependancy->channel)); } $renames = []; foreach ($info->phprelease as $release) { if ($release->count() > 0) { foreach ($release->filelist->install as $move) { $renames[(string) $move['name']] = (string) $move['as']; } } } $files = $this->extractFiles($info->contents->dir, '', '/', $renames); foreach ($files as $file) { if (isset($this->targets[$file['role']])) { $dir = $this->targets[$file['role']]; if (in_array($file['role'], array('doc', 'www'))) { if (\Sledgehammer\text($file['to'])->startsWith($package) == false) { $dir = $this->makePath($dir, $package); } } $target = $this->makePath($dir, $file['to']); if (\Sledgehammer\mkdirs(dirname($target)) == false || is_writable(dirname($target)) == false) { throw new Exception('Target "' . $target . '" is not writable'); } $source = $this->makePath($tmpFolder . $folderName . '/' . $folderName, $file['from']); if (isset($file['tasks'])) { $contents = file_get_contents($source); foreach ($file['tasks'] as $task) { $value = null; if ($task['type'] === 'package-info') { if ($task['to'] == 'version') { $value = $version; } elseif ($task['to'] == 'state') { $value = (string) $info->stability->release; } } elseif ($task['type'] == 'pear-config') { if (substr($task['to'], -4) === '_dir') { $role = substr($task['to'], 0, -4); if (isset($this->targets[$role])) { $value = $this->targets[$role]; // @todo calculate relative paths \Sledgehammer\notice('Harcoding path "' . $value . '" into "' . $file['to'] . '"', $file); } } elseif ($task['to'] == 'php_bin') { $value = trim(`which php`); \Sledgehammer\notice('Harcoding path "' . $value . '" into "' . $file['to'] . '"', $file); } } if ($task['task'] === 'replace') { if ($value != '') { $contents = str_replace($task['from'], $value, $contents); } else { \Sledgehammer\notice($task['type'] . ' "' . $task['to'] . '" not yet supported'); } } else { \Sledgehammer\notice('task "' . $task['task'] . '" not implemented'); } } file_put_contents($target, $contents); } else { copy($source, $target); } } } \Sledgehammer\rmdir_recursive($tmpFolder . $folderName . '/' . $folderName); $packageLocation['installed'] = $version; $this->trigger('installed', $this, $package, $version); }
/** * Read meta records. * * @param string [$key] The name of the meta property, when ommitted getMeta() returns all meta fields in a assoc array. * @param mixed [$default] Default returnvalue, when ommitted getMeta() will throw an exception if the property doesn't exist. * * @return mixed */ public function getMeta($key = null, $default = null) { if ($this->meta instanceof HasManyPlaceholder || $this->meta instanceof Collection) { $meta = $this->meta; } else { throw new Exception('implement support'); } if ($key === null) { $data = []; foreach ($meta as $row) { if (array_key_exists($row->key, $data)) { if (array_value($data, $row->key, 0) === '__MULTIRECORD__') { $data[$row->key][] = $row->value; } else { $data[$row->key] = ['__MULTIRECORD__', $data[$row->key], $row->value]; } } else { $data[$row->key] = $row->value; } } return $data; } $value = $meta->where(['key' => $key]); if (count($value) == 1) { foreach ($value as $metaField) { return $metaField->value; } } elseif (count($value) == 0) { if (func_num_args() > 1) { return $default; } throw new InfoException('Meta field: "' . $key . '" doesn\'t exist in ' . str_replace(__NAMESPACE__ . '\\Model\\', '', static::class) . ' ' . $this->id, 'Existing fields: ' . \Sledgehammer\quoted_human_implode(' or ', array_keys($meta->selectKey('key')->toArray()))); } $data = ['__MULTIRECORD__']; foreach ($value as $row) { $data[] = $row->value; } return $data; }
/** * Returns the cached value when valid cache entry was found. otherwise retrieves the value via the $closure, stores it in the cache and returns it. * * @param string|int|array $options A string or int is interpreted as a 'expires' option. * array( * 'max_age' => int|string // The entry must be newer than the $maxAge. Example: "-5min", "2012-01-01" * 'expires' => int|string, // A string is parsed via strtotime(). Examples: '+5min' or '2020-01-01' int's larger than 3600 (1 hour) are interpreted as unix timestamp expire date. And int's smaller or equal to 3600 are interpreted used as ttl. * 'forever' => bool, // Default false (When true no ) * 'lock' => (bool) // Default true, Prevents a cache stampede (http://en.wikipedia.org/wiki/Cache_stampede) * ) * @param callable $closure The method to retrieve/calculate the value. * * @return mixed */ public function value($options, $closure) { // Convert option to an array if (is_array($options) === false) { $options = array('expires' => $options); } // Merge default options $default = array('expires' => false, 'forever' => false, 'max_age' => false, 'lock' => true); $options = array_merge($default, $options); if (count($options) !== count($default)) { \Sledgehammer\notice('Option: ' . \Sledgehammer\quoted_human_implode(' and ', array_keys(array_diff_key($options, $default))) . ' is invalid'); } if ($options['expires'] === false && $options['forever'] === false && $options['max_age'] === false) { throw new InfoException('Invalid options: "expires", "max_age" or "forever" must be set', $options); } if ($options['forever'] && $options['expires'] !== false) { throw new InfoException('Invalid options: "expires" and "forever" can\'t both be set', $options); } // Read value from cache (without lock) $hit = $this->read($value, $options['max_age']); if ($hit) { return $value; } // Miss, obtain lock and try again. if ($options['lock']) { $this->lock(); // Read value from cache try { $hit = $this->read($value, $options['max_age']); } catch (Exception $e) { // Reading cache failed $this->release(); throw $e; } if ($hit) { $this->release(); return $value; } } // Miss, obtain value. try { $value = call_user_func($closure); } catch (Exception $e) { if ($options['lock']) { $this->release(); } throw $e; } // Store value $this->write($value, $options['expires']); if ($options['lock']) { $this->release(); } return $value; }
/** * Remove a callback from an event. * * @param string $event * @param string $identifier */ public function off($event, $identifier) { if ($this->hasEvent($event) === false) { \Sledgehammer\warning('Event: "' . $event . '" not registered', 'Available events: ' . \Sledgehammer\quoted_human_implode(', ', array_keys($this->events))); return false; } if (empty($this->events[$event][$identifier])) { \Sledgehammer\warning('Identifier: "' . $identifier . '" not found in listeners for event: "' . $event . '"', 'Available identifiers: ' . \Sledgehammer\quoted_human_implode(', ', array_keys($this->events[$event]))); return false; } unset($this->events[$event][$identifier]); return true; }
/** * Import the definition in a file. * * @param string $filename * @param array $settings * * @return array definitions */ public function importFile($filename, $settings = []) { $setttings = $this->mergeSettings($settings); $previousError = error_get_last(); if (filesize($filename) > $settings['filesize_limit']) { $this->hint('File ' . $filename . ' too big, skipping...', array('allowed size' => $settings['filesize_limit'], 'actual size' => filesize($filename))); return; } $tokens = token_get_all(file_get_contents($filename)); $error = error_get_last(); if ($error !== $previousError) { \Sledgehammer\notice($error['message'] . ' in "' . $filename . '"'); } $definitions = []; $namespace = ''; $state = 'DETECT'; foreach ($tokens as $token) { if ($token[0] == T_WHITESPACE) { continue; } switch ($state) { case 'DETECT': switch ($token[0]) { case T_NAMESPACE: $state = 'NAMESPACE'; $namespace = ''; break; case T_CLASS: $state = 'CLASS'; break; case T_INTERFACE: $state = 'INTERFACE'; break; case T_TRAIT: $state = 'INTERFACE'; break; case T_DOUBLE_COLON: $state = 'SKIP_ONE'; break; } break; case 'NAMESPACE': if (in_array($token[0], array(T_STRING, T_NS_SEPARATOR))) { $namespace .= $token[1]; break; } if (in_array($token, array(';', '{'))) { $state = 'DETECT'; break; } $this->unexpectedToken($token); $state = 'DETECT'; break; case 'CLASS': if ($token[0] == T_STRING) { if ($settings['matching_filename'] && substr(basename($filename), 0, -4) != $token[1]) { \Sledgehammer\notice('Filename doesn\'t match classname "' . $token[1] . '" in "' . $filename . '"', array('settings' => $settings)); } if ($namespace == '') { $definition = $token[1]; } else { $definition = $namespace . '\\' . $token[1]; } $definitions[] = $definition; break; } if ($token[0] == T_EXTENDS) { $state = 'DETECT'; break; } elseif ($settings['mandatory_superclass'] && !in_array($definition, array('Sledgehammer\\Core\\Object'))) { \Sledgehammer\notice('Class: "' . $definition . '" has no superclass, expection "class X extends Y"'); } if ($token == '{' || $token[0] == T_IMPLEMENTS) { $state = 'DETECT'; break; } $this->unexpectedToken($token); $state = 'DETECT'; break; case 'INTERFACE': if ($token[0] == T_STRING) { if ($settings['matching_filename'] && substr(basename($filename), 0, -4) != $token[1]) { \Sledgehammer\notice('Filename doesn\'t match interface-name "' . $token[1] . '" in "' . $filename . '"', array('settings' => $settings)); } if ($namespace == '') { $definition = $token[1]; } else { $definition = $namespace . '\\' . $token[1]; } $definitions[] = $definition; $state = 'DETECT'; break; } $this->unexpectedToken($token); $state = 'DETECT'; break; case 'SKIP_ONE': $state = 'DETECT'; break; default: throw new Exception('Unexpected state: "' . $state . '"'); } } if ($settings['detect_accidental_output'] && $token[0] == T_INLINE_HTML) { \Sledgehammer\notice('Invalid end of file. (html)output detected in "' . basename($filename) . '"'); } /* elseif ($token[0] == T_CLOSE_TAG && $token[1] != '?>') { \Sledgehammer\notice('Invalid end of file, accidental newline detected in "'.basename($filename).'"'); // newline directly after the close tag doesn't cause problems } */ if (count($definitions) > 1) { if ($settings['one_definition_per_file']) { \Sledgehammer\notice('Multiple definitions found in ' . $filename, $definitions); } } elseif ($settings['mandatory_definition'] && count($definitions) === 0 && basename($filename) !== strtolower(basename($filename))) { \Sledgehammer\notice('No classes or interfaces found in ' . $filename); } $filename = $this->relativePath($filename); foreach ($definitions as $definition) { if (isset($this->definitions[$definition]) && $this->definitions[$definition] != $filename) { if (empty($this->ambiguous[$definition])) { $this->ambiguous[$definition] = array($this->definitions[$definition]); } unset($this->definitions[$definition]); // Ignore both definitions to prevent autoloading the wrong one. } if (isset($this->ambiguous[$definition])) { $this->ambiguous[$definition][] = $filename; if ($settings['notice_ambiguous']) { \Sledgehammer\notice('"' . $definition . '" is ambiguous, it\'s found in multiple files: ' . \Sledgehammer\quoted_human_implode(' and ', $this->ambiguous[$definition]), array('settings' => $settings)); } } else { $this->definitions[$definition] = $filename; } } }