method.
+ * @param string component name
+ * @return IComponent the created component (optionally)
+ */
+ protected function createComponent($name)
+ {
+ $ucname = ucfirst($name);
+ $method = 'createComponent' . $ucname;
+ if ($ucname !== $name && method_exists($this, $method) && $this->getReflection()->getMethod($method)->getName() === $method) {
+ $component = $this->$method($name);
+ if (!$component instanceof IComponent && !isset($this->components[$name])) {
+ $class = get_class($this);
+ throw new Nette\UnexpectedValueException("Method $class::$method() did not return or create the desired component.");
+ }
+ return $component;
+ }
+ }
+
+
+
+ /**
+ * Iterates over a components.
+ * @param bool recursive?
+ * @param string class types filter
+ * @return \ArrayIterator
+ */
+ final public function getComponents($deep = FALSE, $filterType = NULL)
+ {
+ $iterator = new RecursiveComponentIterator($this->components);
+ if ($deep) {
+ $deep = $deep > 0 ? \RecursiveIteratorIterator::SELF_FIRST : \RecursiveIteratorIterator::CHILD_FIRST;
+ $iterator = new \RecursiveIteratorIterator($iterator, $deep);
+ }
+ if ($filterType) {
+ $iterator = new Nette\Iterators\InstanceFilter($iterator, $filterType);
+ }
+ return $iterator;
+ }
+
+
+
+ /**
+ * Descendant can override this method to disallow insert a child by throwing an Nette\InvalidStateException.
+ * @return void
+ * @throws Nette\InvalidStateException
+ */
+ protected function validateChildComponent(IComponent $child)
+ {
+ }
+
+
+
+ /********************* cloneable, serializable ****************d*g**/
+
+
+
+ /**
+ * Object cloning.
+ */
+ public function __clone()
+ {
+ if ($this->components) {
+ $oldMyself = reset($this->components)->getParent();
+ $oldMyself->cloning = $this;
+ foreach ($this->components as $name => $component) {
+ $this->components[$name] = clone $component;
+ }
+ $oldMyself->cloning = NULL;
+ }
+ parent::__clone();
+ }
+
+
+
+ /**
+ * Is container cloning now?
+ * @return NULL|IComponent
+ * @internal
+ */
+ public function _isCloning()
+ {
+ return $this->cloning;
+ }
+
+}
diff --git a/libs/Nette/ComponentModel/IComponent.php b/libs/Nette/ComponentModel/IComponent.php
index bb994bf..68f5ed4 100644
--- a/libs/Nette/ComponentModel/IComponent.php
+++ b/libs/Nette/ComponentModel/IComponent.php
@@ -1,47 +1,47 @@
-current() instanceof IContainer;
- }
-
-
-
- /**
- * The sub-iterator for the current element.
- * @return \RecursiveIterator
- */
- public function getChildren()
- {
- return $this->current()->getComponents();
- }
-
-
-
- /**
- * Returns the count of elements.
- * @return int
- */
- public function count()
- {
- return iterator_count($this);
- }
-
-}
+current() instanceof IContainer;
+ }
+
+
+
+ /**
+ * The sub-iterator for the current element.
+ * @return \RecursiveIterator
+ */
+ public function getChildren()
+ {
+ return $this->current()->getComponents();
+ }
+
+
+
+ /**
+ * Returns the count of elements.
+ * @return int
+ */
+ public function count()
+ {
+ return iterator_count($this);
+ }
+
+}
diff --git a/libs/Nette/Config/Adapters/IniAdapter.php b/libs/Nette/Config/Adapters/IniAdapter.php
new file mode 100644
index 0000000..d8a820b
--- /dev/null
+++ b/libs/Nette/Config/Adapters/IniAdapter.php
@@ -0,0 +1,151 @@
+getMessage(), 0, $e);
+ }
+
+ $data = array();
+ foreach ($ini as $secName => $secData) {
+ if (is_array($secData)) { // is section?
+ if (substr($secName, -1) === self::RAW_SECTION) {
+ $secName = substr($secName, 0, -1);
+ } else { // process key nesting separator (key1.key2.key3)
+ $tmp = array();
+ foreach ($secData as $key => $val) {
+ $cursor = & $tmp;
+ $key = str_replace(self::ESCAPED_KEY_SEPARATOR, "\xFF", $key);
+ foreach (explode(self::KEY_SEPARATOR, $key) as $part) {
+ $part = str_replace("\xFF", self::KEY_SEPARATOR, $part);
+ if (!isset($cursor[$part]) || is_array($cursor[$part])) {
+ $cursor = & $cursor[$part];
+ } else {
+ throw new Nette\InvalidStateException("Invalid key '$key' in section [$secName] in file '$file'.");
+ }
+ }
+ $cursor = $val;
+ }
+ $secData = $tmp;
+ }
+
+ $parts = explode(self::INHERITING_SEPARATOR, $secName);
+ if (count($parts) > 1) {
+ $secName = trim($parts[0]);
+ $secData[Helpers::EXTENDS_KEY] = trim($parts[1]);
+ }
+ }
+
+ $cursor = & $data; // nesting separator in section name
+ foreach (explode(self::KEY_SEPARATOR, $secName) as $part) {
+ if (!isset($cursor[$part]) || is_array($cursor[$part])) {
+ $cursor = & $cursor[$part];
+ } else {
+ throw new Nette\InvalidStateException("Invalid section [$secName] in file '$file'.");
+ }
+ }
+
+ if (is_array($secData) && is_array($cursor)) {
+ $secData = Helpers::merge($secData, $cursor);
+ }
+
+ $cursor = $secData;
+ }
+
+ return $data;
+ }
+
+
+
+ /**
+ * Generates configuration in INI format.
+ * @param array
+ * @return string
+ */
+ public function dump(array $data)
+ {
+ $output = array();
+ foreach ($data as $name => $secData) {
+ if (!is_array($secData)) {
+ $output = array();
+ self::build($data, $output, '');
+ break;
+ }
+ if ($parent = Helpers::takeParent($secData)) {
+ $output[] = "[$name " . self::INHERITING_SEPARATOR . " $parent]";
+ } else {
+ $output[] = "[$name]";
+ }
+ self::build($secData, $output, '');
+ $output[] = '';
+ }
+ return "; generated by Nette\n\n" . implode(PHP_EOL, $output);
+ }
+
+
+
+ /**
+ * Recursive builds INI list.
+ * @return void
+ */
+ private static function build($input, & $output, $prefix)
+ {
+ foreach ($input as $key => $val) {
+ $key = str_replace(self::KEY_SEPARATOR, self::ESCAPED_KEY_SEPARATOR, $key);
+ if (is_array($val)) {
+ self::build($val, $output, $prefix . $key . self::KEY_SEPARATOR);
+
+ } elseif (is_bool($val)) {
+ $output[] = "$prefix$key = " . ($val ? 'true' : 'false');
+
+ } elseif (is_numeric($val)) {
+ $output[] = "$prefix$key = $val";
+
+ } elseif (is_string($val)) {
+ $output[] = "$prefix$key = \"$val\"";
+
+ } else {
+ throw new Nette\InvalidArgumentException("The '$prefix$key' item must be scalar or array, " . gettype($val) ." given.");
+ }
+ }
+ }
+
+}
diff --git a/libs/Nette/Config/Adapters/NeonAdapter.php b/libs/Nette/Config/Adapters/NeonAdapter.php
new file mode 100644
index 0000000..df23d1b
--- /dev/null
+++ b/libs/Nette/Config/Adapters/NeonAdapter.php
@@ -0,0 +1,93 @@
+process((array) Neon::decode(file_get_contents($file)));
+ }
+
+
+
+ private function process(array $arr)
+ {
+ $res = array();
+ foreach ($arr as $key => $val) {
+ if (substr($key, -1) === self::PREVENT_MERGING) {
+ if (!is_array($val) && $val !== NULL) {
+ throw new Nette\InvalidStateException("Replacing operator is available only for arrays, item '$key' is not array.");
+ }
+ $key = substr($key, 0, -1);
+ $val[Helpers::EXTENDS_KEY] = Helpers::OVERWRITE;
+
+ } elseif (preg_match('#^(\S+)\s+' . self::INHERITING_SEPARATOR . '\s+(\S+)$#', $key, $matches)) {
+ if (!is_array($val) && $val !== NULL) {
+ throw new Nette\InvalidStateException("Inheritance operator is available only for arrays, item '$key' is not array.");
+ }
+ list(, $key, $val[Helpers::EXTENDS_KEY]) = $matches;
+ if (isset($res[$key])) {
+ throw new Nette\InvalidStateException("Duplicated key '$key'.");
+ }
+ }
+
+ if (is_array($val)) {
+ $val = $this->process($val);
+ } elseif ($val instanceof Nette\Utils\NeonEntity) {
+ $val = (object) array('value' => $val->value, 'attributes' => $this->process($val->attributes));
+ }
+ $res[$key] = $val;
+ }
+ return $res;
+ }
+
+
+
+ /**
+ * Generates configuration in NEON format.
+ * @param array
+ * @return string
+ */
+ public function dump(array $data)
+ {
+ $tmp = array();
+ foreach ($data as $name => $secData) {
+ if ($parent = Helpers::takeParent($secData)) {
+ $name .= ' ' . self::INHERITING_SEPARATOR . ' ' . $parent;
+ }
+ $tmp[$name] = $secData;
+ }
+ return "# generated by Nette\n\n" . Neon::encode($tmp, Neon::BLOCK);
+ }
+
+}
diff --git a/libs/Nette/Config/Adapters/PhpAdapter.php b/libs/Nette/Config/Adapters/PhpAdapter.php
new file mode 100644
index 0000000..a6c5cc8
--- /dev/null
+++ b/libs/Nette/Config/Adapters/PhpAdapter.php
@@ -0,0 +1,48 @@
+ 1, 'factories' => 1, 'parameters' => 1);
+
+
+
+ /**
+ * Add custom configurator extension.
+ * @return Compiler provides a fluent interface
+ */
+ public function addExtension($name, CompilerExtension $extension)
+ {
+ if (isset(self::$reserved[$name])) {
+ throw new Nette\InvalidArgumentException("Name '$name' is reserved.");
+ }
+ $this->extensions[$name] = $extension->setCompiler($this, $name);
+ return $this;
+ }
+
+
+
+ /**
+ * @return array
+ */
+ public function getExtensions()
+ {
+ return $this->extensions;
+ }
+
+
+
+ /**
+ * @return Nette\DI\ContainerBuilder
+ */
+ public function getContainerBuilder()
+ {
+ return $this->container;
+ }
+
+
+
+ /**
+ * Returns configuration without expanded parameters.
+ * @return array
+ */
+ public function getConfig()
+ {
+ return $this->config;
+ }
+
+
+
+ /**
+ * @return string
+ */
+ public function compile(array $config, $className, $parentName)
+ {
+ $this->config = $config;
+ $this->container = new Nette\DI\ContainerBuilder;
+ $this->processParameters();
+ $this->processExtensions();
+ $this->processServices();
+ return $this->generateCode($className, $parentName);
+ }
+
+
+
+ public function processParameters()
+ {
+ if (isset($this->config['parameters'])) {
+ $this->container->parameters = $this->config['parameters'];
+ }
+ }
+
+
+
+ public function processExtensions()
+ {
+ for ($i = 0; $slice = array_slice($this->extensions, $i, 1); $i++) {
+ reset($slice)->loadConfiguration();
+ }
+
+ if ($extra = array_diff_key($this->config, self::$reserved, $this->extensions)) {
+ $extra = implode("', '", array_keys($extra));
+ throw new Nette\InvalidStateException("Found sections '$extra' in configuration, but corresponding extensions are missing.");
+ }
+ }
+
+
+
+ public function processServices()
+ {
+ $this->parseServices($this->container, $this->config);
+
+ foreach ($this->extensions as $name => $extension) {
+ $this->container->addDefinition($name)
+ ->setClass('Nette\DI\NestedAccessor', array('@container', $name))
+ ->setAutowired(FALSE);
+
+ if (isset($this->config[$name])) {
+ $this->parseServices($this->container, $this->config[$name], $name);
+ }
+ }
+
+ foreach ($this->container->getDefinitions() as $name => $def) {
+ $factory = $name . 'Factory';
+ if (!$def->shared && !$def->internal && !$this->container->hasDefinition($factory)) {
+ $this->container->addDefinition($factory)
+ ->setClass('Nette\Callback', array('@container', Nette\DI\Container::getMethodName($name, FALSE)))
+ ->setAutowired(FALSE)
+ ->tags = $def->tags;
+ }
+ }
+ }
+
+
+
+ public function generateCode($className, $parentName)
+ {
+ foreach ($this->extensions as $extension) {
+ $extension->beforeCompile();
+ $this->container->addDependency(Nette\Reflection\ClassType::from($extension)->getFileName());
+ }
+
+ $classes[] = $class = $this->container->generateClass($parentName);
+ $class->setName($className)
+ ->addMethod('initialize');
+
+ foreach ($this->extensions as $extension) {
+ $extension->afterCompile($class);
+ }
+
+ $defs = $this->container->getDefinitions();
+ ksort($defs);
+ $list = array_keys($defs);
+ foreach (array_reverse($defs, TRUE) as $name => $def) {
+ if ($def->class === 'Nette\DI\NestedAccessor' && ($found = preg_grep('#^'.$name.'\.#i', $list))) {
+ $list = array_diff($list, $found);
+ $def->class = $className . '_' . preg_replace('#\W+#', '_', $name);
+ $class->documents = preg_replace("#\S+(?= \\$$name$)#", $def->class, $class->documents);
+ $classes[] = $accessor = new Nette\Utils\PhpGenerator\ClassType($def->class);
+ foreach ($found as $item) {
+ if ($defs[$item]->internal) {
+ continue;
+ }
+ $short = substr($item, strlen($name) + 1);
+ $accessor->addDocument($defs[$item]->shared
+ ? "@property {$defs[$item]->class} \$$short"
+ : "@method {$defs[$item]->class} create" . ucfirst("$short()"));
+ }
+ }
+ }
+
+ return implode("\n\n\n", $classes);
+ }
+
+
+
+ /********************* tools ****************d*g**/
+
+
+
+ /**
+ * Parses section 'services' from configuration file.
+ * @return void
+ */
+ public static function parseServices(Nette\DI\ContainerBuilder $container, array $config, $namespace = NULL)
+ {
+ $services = isset($config['services']) ? $config['services'] : array();
+ $factories = isset($config['factories']) ? $config['factories'] : array();
+ if ($tmp = array_intersect_key($services, $factories)) {
+ $tmp = implode("', '", array_keys($tmp));
+ throw new Nette\DI\ServiceCreationException("It is not allowed to use services and factories with the same names: '$tmp'.");
+ }
+
+ $all = $services + $factories;
+ uasort($all, function($a, $b) {
+ return strcmp(Helpers::isInheriting($a), Helpers::isInheriting($b));
+ });
+
+ foreach ($all as $name => $def) {
+ $shared = array_key_exists($name, $services);
+ $name = ($namespace ? $namespace . '.' : '') . $name;
+
+ if (($parent = Helpers::takeParent($def)) && $parent !== $name) {
+ $container->removeDefinition($name);
+ $definition = $container->addDefinition($name);
+ if ($parent !== Helpers::OVERWRITE) {
+ foreach ($container->getDefinition($parent) as $k => $v) {
+ $definition->$k = unserialize(serialize($v)); // deep clone
+ }
+ }
+ } elseif ($container->hasDefinition($name)) {
+ $definition = $container->getDefinition($name);
+ if ($definition->shared !== $shared) {
+ throw new Nette\DI\ServiceCreationException("It is not allowed to use service and factory with the same name '$name'.");
+ }
+ } else {
+ $definition = $container->addDefinition($name);
+ }
+ try {
+ static::parseService($definition, $def, $shared);
+ } catch (\Exception $e) {
+ throw new Nette\DI\ServiceCreationException("Service '$name': " . $e->getMessage(), NULL, $e);
+ }
+ }
+ }
+
+
+
+ /**
+ * Parses single service from configuration file.
+ * @return void
+ */
+ public static function parseService(Nette\DI\ServiceDefinition $definition, $config, $shared = TRUE)
+ {
+ if ($config === NULL) {
+ return;
+ } elseif (!is_array($config)) {
+ $config = array('class' => NULL, 'factory' => $config);
+ }
+
+ $known = $shared
+ ? array('class', 'factory', 'arguments', 'setup', 'autowired', 'run', 'tags')
+ : array('class', 'factory', 'arguments', 'setup', 'autowired', 'tags', 'internal', 'parameters');
+
+ if ($error = array_diff(array_keys($config), $known)) {
+ throw new Nette\InvalidStateException("Unknown key '" . implode("', '", $error) . "' in definition of service.");
+ }
+
+ $arguments = array();
+ if (array_key_exists('arguments', $config)) {
+ Validators::assertField($config, 'arguments', 'array');
+ $arguments = self::filterArguments($config['arguments']);
+ $definition->setArguments($arguments);
+ }
+
+ if (array_key_exists('class', $config) || array_key_exists('factory', $config)) {
+ $definition->class = NULL;
+ $definition->factory = NULL;
+ }
+
+ if (array_key_exists('class', $config)) {
+ Validators::assertField($config, 'class', 'string|stdClass|null');
+ if ($config['class'] instanceof \stdClass) {
+ $definition->setClass($config['class']->value, self::filterArguments($config['class']->attributes));
+ } else {
+ $definition->setClass($config['class'], $arguments);
+ }
+ }
+
+ if (array_key_exists('factory', $config)) {
+ Validators::assertField($config, 'factory', 'callable|stdClass|null');
+ if ($config['factory'] instanceof \stdClass) {
+ $definition->setFactory($config['factory']->value, self::filterArguments($config['factory']->attributes));
+ } else {
+ $definition->setFactory($config['factory'], $arguments);
+ }
+ }
+
+ if (isset($config['setup'])) {
+ if (Helpers::takeParent($config['setup'])) {
+ $definition->setup = array();
+ }
+ Validators::assertField($config, 'setup', 'list');
+ foreach ($config['setup'] as $id => $setup) {
+ Validators::assert($setup, 'callable|stdClass', "setup item #$id");
+ if ($setup instanceof \stdClass) {
+ Validators::assert($setup->value, 'callable', "setup item #$id");
+ $definition->addSetup($setup->value, self::filterArguments($setup->attributes));
+ } else {
+ $definition->addSetup($setup);
+ }
+ }
+ }
+
+ $definition->setShared($shared);
+ if (isset($config['parameters'])) {
+ Validators::assertField($config, 'parameters', 'array');
+ $definition->setParameters($config['parameters']);
+ }
+
+ if (isset($config['autowired'])) {
+ Validators::assertField($config, 'autowired', 'bool');
+ $definition->setAutowired($config['autowired']);
+ }
+
+ if (isset($config['internal'])) {
+ Validators::assertField($config, 'internal', 'bool');
+ $definition->setInternal($config['internal']);
+ }
+
+ if (isset($config['run'])) {
+ $config['tags']['run'] = (bool) $config['run'];
+ }
+
+ if (isset($config['tags'])) {
+ Validators::assertField($config, 'tags', 'array');
+ if (Helpers::takeParent($config['tags'])) {
+ $definition->tags = array();
+ }
+ foreach ($config['tags'] as $tag => $attrs) {
+ if (is_int($tag) && is_string($attrs)) {
+ $definition->addTag($attrs);
+ } else {
+ $definition->addTag($tag, $attrs);
+ }
+ }
+ }
+ }
+
+
+
+ /**
+ * Removes ... and replaces entities with Nette\DI\Statement.
+ * @return array
+ */
+ public static function filterArguments(array $args)
+ {
+ foreach ($args as $k => $v) {
+ if ($v === '...') {
+ unset($args[$k]);
+ } elseif ($v instanceof \stdClass && isset($v->value, $v->attributes)) {
+ $args[$k] = new Nette\DI\Statement($v->value, self::filterArguments($v->attributes));
+ }
+ }
+ return $args;
+ }
+
+}
diff --git a/libs/Nette/Config/CompilerExtension.php b/libs/Nette/Config/CompilerExtension.php
new file mode 100644
index 0000000..5c66e8e
--- /dev/null
+++ b/libs/Nette/Config/CompilerExtension.php
@@ -0,0 +1,130 @@
+compiler = $compiler;
+ $this->name = $name;
+ return $this;
+ }
+
+
+
+ /**
+ * Returns extension configuration.
+ * @param array default values.
+ * @param bool perform %parameters% expansion?
+ * @return array
+ */
+ public function getConfig(array $defaults = NULL, $expand = TRUE)
+ {
+ $config = $this->compiler->getConfig();
+ $config = isset($config[$this->name]) ? $config[$this->name] : array();
+ unset($config['services'], $config['factories']);
+ $config = Helpers::merge($config, $defaults);
+ return $expand ? $this->compiler->getContainerBuilder()->expand($config) : $config;
+ }
+
+
+
+ /**
+ * @return Nette\DI\ContainerBuilder
+ */
+ public function getContainerBuilder()
+ {
+ return $this->compiler->getContainerBuilder();
+ }
+
+
+
+ /**
+ * Reads configuration from file.
+ * @param string file name
+ * @return array
+ */
+ public function loadFromFile($file)
+ {
+ $loader = new Loader;
+ $res = $loader->load($file);
+ $container = $this->compiler->getContainerBuilder();
+ foreach ($loader->getDependencies() as $file) {
+ $container->addDependency($file);
+ }
+ return $res;
+ }
+
+
+
+ /**
+ * Prepend extension name to identifier or service name.
+ * @param string
+ * @return string
+ */
+ public function prefix($id)
+ {
+ return substr_replace($id, $this->name . '.', substr($id, 0, 1) === '@' ? 1 : 0, 0);
+ }
+
+
+
+ /**
+ * Processes configuration data. Intended to be overridden by descendant.
+ * @return void
+ */
+ public function loadConfiguration()
+ {
+ }
+
+
+
+ /**
+ * Adjusts DI container before is compiled to PHP class. Intended to be overridden by descendant.
+ * @return void
+ */
+ public function beforeCompile()
+ {
+ }
+
+
+
+ /**
+ * Adjusts DI container compiled to PHP class. Intended to be overridden by descendant.
+ * @return void
+ */
+ public function afterCompile(Nette\Utils\PhpGenerator\ClassType $class)
+ {
+ }
+
+}
diff --git a/libs/Nette/Config/Config.php b/libs/Nette/Config/Config.php
deleted file mode 100644
index 5ae22b6..0000000
--- a/libs/Nette/Config/Config.php
+++ /dev/null
@@ -1,104 +0,0 @@
- 'Nette\Config\IniAdapter',
- 'neon' => 'Nette\Config\NeonAdapter',
- );
-
-
-
- /**
- * Static class - cannot be instantiated.
- */
- final public function __construct()
- {
- throw new Nette\StaticClassException;
- }
-
-
-
- /**
- * Registers adapter for given file extension.
- * @param string file extension
- * @param string class name (IConfigAdapter)
- * @return void
- */
- public static function registerExtension($extension, $class)
- {
- if (!class_exists($class)) {
- throw new Nette\InvalidArgumentException("Class '$class' was not found.");
- }
-
- if (!Nette\Reflection\ClassType::from($class)->implementsInterface('Nette\Config\IAdapter')) {
- throw new Nette\InvalidArgumentException("Configuration adapter '$class' is not Nette\\Config\\IAdapter implementor.");
- }
-
- self::$extensions[strtolower($extension)] = $class;
- }
-
-
-
- /**
- * Creates new configuration object from file.
- * @param string file name
- * @param string section to load
- * @return array
- */
- public static function fromFile($file, $section = NULL)
- {
- $extension = strtolower(pathinfo($file, PATHINFO_EXTENSION));
- if (!isset(self::$extensions[$extension])) {
- throw new Nette\InvalidArgumentException("Unknown file extension '$file'.");
- }
-
- $data = call_user_func(array(self::$extensions[$extension], 'load'), $file, $section);
- if ($section) {
- if (!isset($data[$section]) || !is_array($data[$section])) {
- throw new Nette\InvalidStateException("There is not section [$section] in file '$file'.");
- }
- $data = $data[$section];
- }
- return $data;
- }
-
-
-
- /**
- * Save configuration to file.
- * @param mixed
- * @param string file
- * @return void
- */
- public static function save($config, $file)
- {
- $extension = strtolower(pathinfo($file, PATHINFO_EXTENSION));
- if (!isset(self::$extensions[$extension])) {
- throw new Nette\InvalidArgumentException("Unknown file extension '$file'.");
- }
- return call_user_func(array(self::$extensions[$extension], 'save'), $config, $file);
- }
-
-}
diff --git a/libs/Nette/Config/Configurator.php b/libs/Nette/Config/Configurator.php
new file mode 100644
index 0000000..d3b2e9b
--- /dev/null
+++ b/libs/Nette/Config/Configurator.php
@@ -0,0 +1,342 @@
+parameters = $this->getDefaultParameters();
+ }
+
+
+
+ /**
+ * Set parameter %debugMode%.
+ * @param bool|string|array
+ * @return Configurator provides a fluent interface
+ */
+ public function setDebugMode($value = TRUE)
+ {
+ $this->parameters['debugMode'] = is_bool($value) ? $value : self::detectDebugMode($value);
+ $this->parameters['productionMode'] = !$this->parameters['debugMode']; // compatibility
+ return $this;
+ }
+
+
+
+ /**
+ * @return bool
+ */
+ public function isDebugMode()
+ {
+ return !$this->parameters['productionMode'];
+ }
+
+
+
+ /**
+ * Sets path to temporary directory.
+ * @return Configurator provides a fluent interface
+ */
+ public function setTempDirectory($path)
+ {
+ $this->parameters['tempDir'] = $path;
+ if (($cacheDir = $this->getCacheDirectory()) && !is_dir($cacheDir)) {
+ mkdir($cacheDir, 0777);
+ }
+ return $this;
+ }
+
+
+
+ /**
+ * Adds new parameters. The %params% will be expanded.
+ * @return Configurator provides a fluent interface
+ */
+ public function addParameters(array $params)
+ {
+ $this->parameters = Helpers::merge($params, $this->parameters);
+ return $this;
+ }
+
+
+
+ /**
+ * @return array
+ */
+ protected function getDefaultParameters()
+ {
+ $trace = /*5.2*PHP_VERSION_ID < 50205 ? debug_backtrace() : */debug_backtrace(FALSE);
+ $debugMode = static::detectDebugMode();
+ return array(
+ 'appDir' => isset($trace[1]['file']) ? dirname($trace[1]['file']) : NULL,
+ 'wwwDir' => isset($_SERVER['SCRIPT_FILENAME']) ? dirname($_SERVER['SCRIPT_FILENAME']) : NULL,
+ 'debugMode' => $debugMode,
+ 'productionMode' => !$debugMode,
+ 'environment' => $debugMode ? self::DEVELOPMENT : self::PRODUCTION,
+ 'consoleMode' => PHP_SAPI === 'cli',
+ 'container' => array(
+ 'class' => 'SystemContainer',
+ 'parent' => 'Nette\DI\Container',
+ )
+ );
+ }
+
+
+
+ /**
+ * @param string error log directory
+ * @param string administrator email
+ * @return void
+ */
+ public function enableDebugger($logDirectory = NULL, $email = NULL)
+ {
+ Nette\Diagnostics\Debugger::$strictMode = TRUE;
+ Nette\Diagnostics\Debugger::enable($this->parameters['productionMode'], $logDirectory, $email);
+ }
+
+
+
+ /**
+ * @return Nette\Loaders\RobotLoader
+ */
+ public function createRobotLoader()
+ {
+ if (!($cacheDir = $this->getCacheDirectory())) {
+ throw new Nette\InvalidStateException("Set path to temporary directory using setTempDirectory().");
+ }
+ $loader = new Nette\Loaders\RobotLoader;
+ $loader->setCacheStorage(new Nette\Caching\Storages\FileStorage($cacheDir));
+ $loader->autoRebuild = !$this->parameters['productionMode'];
+ return $loader;
+ }
+
+
+
+ /**
+ * Adds configuration file.
+ * @return Configurator provides a fluent interface
+ */
+ public function addConfig($file, $section = self::AUTO)
+ {
+ $this->files[] = array($file, $section === self::AUTO ? $this->parameters['environment'] : $section);
+ return $this;
+ }
+
+
+
+ /** @deprecated */
+ public function loadConfig($file, $section = NULL)
+ {
+ trigger_error(__METHOD__ . '() is deprecated; use addConfig(file, [section])->createContainer() instead.', E_USER_WARNING);
+ return $this->addConfig($file, $section)->createContainer();
+ }
+
+
+
+ /**
+ * Returns system DI container.
+ * @return \SystemContainer
+ */
+ public function createContainer()
+ {
+ if ($cacheDir = $this->getCacheDirectory()) {
+ $cache = new Cache(new Nette\Caching\Storages\PhpFileStorage($cacheDir), 'Nette.Configurator');
+ $cacheKey = array($this->parameters, $this->files);
+ $cached = $cache->load($cacheKey);
+ if (!$cached) {
+ $code = $this->buildContainer($dependencies);
+ $cache->save($cacheKey, $code, array(
+ Cache::FILES => $dependencies,
+ ));
+ $cached = $cache->load($cacheKey);
+ }
+ Nette\Utils\LimitedScope::load($cached['file'], TRUE);
+
+ } elseif ($this->files) {
+ throw new Nette\InvalidStateException("Set path to temporary directory using setTempDirectory().");
+
+ } else {
+ Nette\Utils\LimitedScope::evaluate($this->buildContainer()); // back compatibility with Environment
+ }
+
+ $container = new $this->parameters['container']['class'];
+ $container->initialize();
+ Nette\Environment::setContext($container); // back compatibility
+ return $container;
+ }
+
+
+
+ /**
+ * Build system container class.
+ * @return string
+ */
+ protected function buildContainer(& $dependencies = NULL)
+ {
+ $loader = $this->createLoader();
+ $config = array();
+ $code = "files as $tmp) {
+ list($file, $section) = $tmp;
+ $config = Helpers::merge($loader->load($file, $section), $config);
+ $code .= "// source: $file $section\n";
+ }
+ $code .= "\n";
+
+ $this->checkCompatibility($config);
+
+ if (!isset($config['parameters'])) {
+ $config['parameters'] = array();
+ }
+ $config['parameters'] = Helpers::merge($config['parameters'], $this->parameters);
+
+ $compiler = $this->createCompiler();
+ $this->onCompile($this, $compiler);
+
+ $code .= $compiler->compile(
+ $config,
+ $this->parameters['container']['class'],
+ $config['parameters']['container']['parent']
+ );
+ $dependencies = array_merge($loader->getDependencies(), $this->isDebugMode() ? $compiler->getContainerBuilder()->getDependencies() : array());
+ return $code;
+ }
+
+
+
+ protected function checkCompatibility(array $config)
+ {
+ foreach (array('service' => 'services', 'variable' => 'parameters', 'variables' => 'parameters', 'mode' => 'parameters', 'const' => 'constants') as $old => $new) {
+ if (isset($config[$old])) {
+ throw new Nette\DeprecatedException("Section '$old' in configuration file is deprecated; use '$new' instead.");
+ }
+ }
+ if (isset($config['services'])) {
+ foreach ($config['services'] as $key => $def) {
+ foreach (array('option' => 'arguments', 'methods' => 'setup') as $old => $new) {
+ if (is_array($def) && isset($def[$old])) {
+ throw new Nette\DeprecatedException("Section '$old' in service definition is deprecated; refactor it into '$new'.");
+ }
+ }
+ }
+ }
+ }
+
+
+
+ /**
+ * @return Compiler
+ */
+ protected function createCompiler()
+ {
+ $compiler = new Compiler;
+ $compiler->addExtension('php', new Extensions\PhpExtension)
+ ->addExtension('constants', new Extensions\ConstantsExtension)
+ ->addExtension('nette', new Extensions\NetteExtension);
+ return $compiler;
+ }
+
+
+
+ /**
+ * @return Loader
+ */
+ protected function createLoader()
+ {
+ return new Loader;
+ }
+
+
+
+ protected function getCacheDirectory()
+ {
+ return empty($this->parameters['tempDir']) ? NULL : $this->parameters['tempDir'] . '/cache';
+ }
+
+
+
+ /********************* tools ****************d*g**/
+
+
+
+ /**
+ * Detects debug mode by IP address.
+ * @param string|array IP addresses or computer names whitelist detection
+ * @return bool
+ */
+ public static function detectDebugMode($list = NULL)
+ {
+ $list = is_string($list) ? preg_split('#[,\s]+#', $list) : (array) $list;
+ if (!isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
+ $list[] = '127.0.0.1';
+ $list[] = '::1';
+ }
+ return in_array(isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : php_uname('n'), $list, TRUE);
+ }
+
+
+
+ /** @deprecated */
+ public function setProductionMode($value = TRUE)
+ {
+ return $this->setDebugMode(is_bool($value) ? !$value : $value);
+ }
+
+
+
+ /** @deprecated */
+ public function isProductionMode()
+ {
+ return !$this->isDebugMode();
+ }
+
+
+
+ /** @deprecated */
+ public static function detectProductionMode($list = NULL)
+ {
+ return !static::detectDebugMode($list);
+ }
+
+}
diff --git a/libs/Nette/Config/Extensions/ConstantsExtension.php b/libs/Nette/Config/Extensions/ConstantsExtension.php
new file mode 100644
index 0000000..fe954d0
--- /dev/null
+++ b/libs/Nette/Config/Extensions/ConstantsExtension.php
@@ -0,0 +1,34 @@
+getConfig() as $name => $value) {
+ $class->methods['initialize']->addBody('define(?, ?);', array($name, $value));
+ }
+ }
+
+}
diff --git a/libs/Nette/Config/Extensions/NetteExtension.php b/libs/Nette/Config/Extensions/NetteExtension.php
new file mode 100644
index 0000000..d26a5fc
--- /dev/null
+++ b/libs/Nette/Config/Extensions/NetteExtension.php
@@ -0,0 +1,371 @@
+ TRUE,
+ 'session' => array(
+ 'iAmUsingBadHost' => NULL,
+ 'autoStart' => 'smart', // true|false|smart
+ 'expiration' => NULL,
+ ),
+ 'application' => array(
+ 'debugger' => TRUE,
+ 'errorPresenter' => NULL,
+ 'catchExceptions' => '%productionMode%',
+ ),
+ 'routing' => array(
+ 'debugger' => TRUE,
+ 'routes' => array(), // of [mask => action]
+ ),
+ 'security' => array(
+ 'debugger' => TRUE,
+ 'frames' => 'SAMEORIGIN', // X-Frame-Options
+ 'users' => array(), // of [user => password]
+ 'roles' => array(), // of [role => parents]
+ 'resources' => array(), // of [resource => parents]
+ ),
+ 'mailer' => array(
+ 'smtp' => FALSE,
+ ),
+ 'database' => array(), // of [name => dsn, user, password, debugger, explain, autowired, reflection]
+ 'forms' => array(
+ 'messages' => array(),
+ ),
+ 'container' => array(
+ 'debugger' => FALSE,
+ ),
+ 'debugger' => array(
+ 'email' => NULL,
+ 'editor' => NULL,
+ 'browser' => NULL,
+ 'strictMode' => NULL,
+ 'bar' => array(), // of class name
+ 'blueScreen' => array(), // of callback
+ ),
+ );
+
+ public $databaseDefaults = array(
+ 'dsn' => NULL,
+ 'user' => NULL,
+ 'password' => NULL,
+ 'options' => NULL,
+ 'debugger' => TRUE,
+ 'explain' => TRUE,
+ 'reflection' => 'Nette\Database\Reflection\DiscoveredReflection',
+ );
+
+
+
+ public function loadConfiguration()
+ {
+ $container = $this->getContainerBuilder();
+ $config = $this->getConfig($this->defaults);
+
+
+ // cache
+ $container->addDefinition($this->prefix('cacheJournal'))
+ ->setClass('Nette\Caching\Storages\FileJournal', array('%tempDir%'));
+
+ $container->addDefinition('cacheStorage') // no namespace for back compatibility
+ ->setClass('Nette\Caching\Storages\FileStorage', array('%tempDir%/cache'));
+
+ $container->addDefinition($this->prefix('templateCacheStorage'))
+ ->setClass('Nette\Caching\Storages\PhpFileStorage', array('%tempDir%/cache'))
+ ->setAutowired(FALSE);
+
+ $container->addDefinition($this->prefix('cache'))
+ ->setClass('Nette\Caching\Cache', array(1 => '%namespace%'))
+ ->setParameters(array('namespace' => NULL));
+
+
+ // http
+ $container->addDefinition($this->prefix('httpRequestFactory'))
+ ->setClass('Nette\Http\RequestFactory')
+ ->addSetup('setEncoding', array('UTF-8'))
+ ->setInternal(TRUE);
+
+ $container->addDefinition('httpRequest') // no namespace for back compatibility
+ ->setClass('Nette\Http\Request')
+ ->setFactory('@Nette\Http\RequestFactory::createHttpRequest');
+
+ $container->addDefinition('httpResponse') // no namespace for back compatibility
+ ->setClass('Nette\Http\Response');
+
+ $container->addDefinition($this->prefix('httpContext'))
+ ->setClass('Nette\Http\Context');
+
+
+ // session
+ $session = $container->addDefinition('session') // no namespace for back compatibility
+ ->setClass('Nette\Http\Session');
+
+ if (isset($config['session']['expiration'])) {
+ $session->addSetup('setExpiration', array($config['session']['expiration']));
+ }
+ if (isset($config['session']['iAmUsingBadHost'])) {
+ $session->addSetup('Nette\Framework::$iAmUsingBadHost = ?;', array((bool) $config['session']['iAmUsingBadHost']));
+ }
+ unset($config['session']['expiration'], $config['session']['autoStart'], $config['session']['iAmUsingBadHost']);
+ if (!empty($config['session'])) {
+ $session->addSetup('setOptions', array($config['session']));
+ }
+
+
+ // security
+ $container->addDefinition($this->prefix('userStorage'))
+ ->setClass('Nette\Http\UserStorage');
+
+ $user = $container->addDefinition('user') // no namespace for back compatibility
+ ->setClass('Nette\Security\User');
+
+ if (!$container->parameters['productionMode'] && $config['security']['debugger']) {
+ $user->addSetup('Nette\Diagnostics\Debugger::$bar->addPanel(?)', array(
+ new Nette\DI\Statement('Nette\Security\Diagnostics\UserPanel')
+ ));
+ }
+
+ if ($config['security']['users']) {
+ $container->addDefinition($this->prefix('authenticator'))
+ ->setClass('Nette\Security\SimpleAuthenticator', array($config['security']['users']));
+ }
+
+ if ($config['security']['roles'] || $config['security']['resources']) {
+ $authorizator = $container->addDefinition($this->prefix('authorizator'))
+ ->setClass('Nette\Security\Permission');
+ foreach ($config['security']['roles'] as $role => $parents) {
+ $authorizator->addSetup('addRole', array($role, $parents));
+ }
+ foreach ($config['security']['resources'] as $resource => $parents) {
+ $authorizator->addSetup('addResource', array($resource, $parents));
+ }
+ }
+
+
+ // application
+ $application = $container->addDefinition('application') // no namespace for back compatibility
+ ->setClass('Nette\Application\Application')
+ ->addSetup('$catchExceptions', $config['application']['catchExceptions'])
+ ->addSetup('$errorPresenter', $config['application']['errorPresenter']);
+
+ if ($config['application']['debugger']) {
+ $application->addSetup('Nette\Application\Diagnostics\RoutingPanel::initializePanel');
+ }
+
+ $container->addDefinition($this->prefix('presenterFactory'))
+ ->setClass('Nette\Application\PresenterFactory', array(
+ isset($container->parameters['appDir']) ? $container->parameters['appDir'] : NULL
+ ));
+
+
+ // routing
+ $router = $container->addDefinition('router') // no namespace for back compatibility
+ ->setClass('Nette\Application\Routers\RouteList');
+
+ foreach ($config['routing']['routes'] as $mask => $action) {
+ $router->addSetup('$service[] = new Nette\Application\Routers\Route(?, ?);', array($mask, $action));
+ }
+
+ if (!$container->parameters['productionMode'] && $config['routing']['debugger']) {
+ $application->addSetup('Nette\Diagnostics\Debugger::$bar->addPanel(?)', array(
+ new Nette\DI\Statement('Nette\Application\Diagnostics\RoutingPanel')
+ ));
+ }
+
+
+ // mailer
+ if (empty($config['mailer']['smtp'])) {
+ $container->addDefinition($this->prefix('mailer'))
+ ->setClass('Nette\Mail\SendmailMailer');
+ } else {
+ $container->addDefinition($this->prefix('mailer'))
+ ->setClass('Nette\Mail\SmtpMailer', array($config['mailer']));
+ }
+
+ $container->addDefinition($this->prefix('mail'))
+ ->setClass('Nette\Mail\Message')
+ ->addSetup('setMailer')
+ ->setShared(FALSE);
+
+
+ // forms
+ $container->addDefinition($this->prefix('basicForm'))
+ ->setClass('Nette\Forms\Form')
+ ->setShared(FALSE);
+
+
+ // templating
+ $latte = $container->addDefinition($this->prefix('latte'))
+ ->setClass('Nette\Latte\Engine')
+ ->setShared(FALSE);
+
+ if (empty($config['xhtml'])) {
+ $latte->addSetup('$service->getCompiler()->defaultContentType = ?', Nette\Latte\Compiler::CONTENT_HTML);
+ }
+
+ $container->addDefinition($this->prefix('template'))
+ ->setClass('Nette\Templating\FileTemplate')
+ ->addSetup('registerFilter', array($latte))
+ ->addSetup('registerHelperLoader', array('Nette\Templating\Helpers::loader'))
+ ->setShared(FALSE);
+
+
+ // database
+ $container->addDefinition($this->prefix('database'))
+ ->setClass('Nette\DI\NestedAccessor', array('@container', $this->prefix('database')));
+
+ if (isset($config['database']['dsn'])) {
+ $config['database'] = array('default' => $config['database']);
+ }
+
+ $autowired = TRUE;
+ foreach ((array) $config['database'] as $name => $info) {
+ if (!is_array($info)) {
+ continue;
+ }
+ $info += $this->databaseDefaults + array('autowired' => $autowired);
+ $autowired = FALSE;
+
+ foreach ((array) $info['options'] as $key => $value) {
+ unset($info['options'][$key]);
+ $info['options'][constant($key)] = $value;
+ }
+
+ $connection = $container->addDefinition($this->prefix("database.$name"))
+ ->setClass('Nette\Database\Connection', array($info['dsn'], $info['user'], $info['password'], $info['options']))
+ ->setAutowired($info['autowired'])
+ ->addSetup('setCacheStorage')
+ ->addSetup('Nette\Diagnostics\Debugger::$blueScreen->addPanel(?)', array(
+ 'Nette\Database\Diagnostics\ConnectionPanel::renderException'
+ ));
+
+ if ($info['reflection']) {
+ $connection->addSetup('setDatabaseReflection', is_string($info['reflection'])
+ ? array(new Nette\DI\Statement(preg_match('#^[a-z]+$#', $info['reflection']) ? 'Nette\Database\Reflection\\' . ucfirst($info['reflection']) . 'Reflection' : $info['reflection']))
+ : Nette\Config\Compiler::filterArguments(array($info['reflection']))
+ );
+ }
+
+ if (!$container->parameters['productionMode'] && $info['debugger']) {
+ $panel = $container->addDefinition($this->prefix("database.{$name}ConnectionPanel"))
+ ->setClass('Nette\Database\Diagnostics\ConnectionPanel')
+ ->setAutowired(FALSE)
+ ->addSetup('$explain', !empty($info['explain']))
+ ->addSetup('Nette\Diagnostics\Debugger::$bar->addPanel(?)', array('@self'));
+
+ $connection->addSetup('$service->onQuery[] = ?', array(array($panel, 'logQuery')));
+ }
+ }
+ }
+
+
+
+ public function afterCompile(Nette\Utils\PhpGenerator\ClassType $class)
+ {
+ $initialize = $class->methods['initialize'];
+ $container = $this->getContainerBuilder();
+ $config = $this->getConfig($this->defaults);
+
+ // debugger
+ foreach (array('email', 'editor', 'browser', 'strictMode', 'maxLen', 'maxDepth') as $key) {
+ if (isset($config['debugger'][$key])) {
+ $initialize->addBody('Nette\Diagnostics\Debugger::$? = ?;', array($key, $config['debugger'][$key]));
+ }
+ }
+
+ if (!$container->parameters['productionMode']) {
+ if ($config['container']['debugger']) {
+ $config['debugger']['bar'][] = 'Nette\DI\Diagnostics\ContainerPanel';
+ }
+
+ foreach ((array) $config['debugger']['bar'] as $item) {
+ $initialize->addBody($container->formatPhp(
+ 'Nette\Diagnostics\Debugger::$bar->addPanel(?);',
+ Nette\Config\Compiler::filterArguments(array(is_string($item) ? new Nette\DI\Statement($item) : $item))
+ ));
+ }
+
+ foreach ((array) $config['debugger']['blueScreen'] as $item) {
+ $initialize->addBody($container->formatPhp(
+ 'Nette\Diagnostics\Debugger::$blueScreen->addPanel(?);',
+ Nette\Config\Compiler::filterArguments(array($item))
+ ));
+ }
+ }
+
+ if (!empty($container->parameters['tempDir'])) {
+ $initialize->addBody($this->checkTempDir($container->expand('%tempDir%/cache')));
+ }
+
+ foreach ((array) $config['forms']['messages'] as $name => $text) {
+ $initialize->addBody('Nette\Forms\Rules::$defaultMessages[Nette\Forms\Form::?] = ?;', array($name, $text));
+ }
+
+ if ($config['session']['autoStart'] === 'smart') {
+ $initialize->addBody('$this->session->exists() && $this->session->start();');
+ } elseif ($config['session']['autoStart']) {
+ $initialize->addBody('$this->session->start();');
+ }
+
+ if (empty($config['xhtml'])) {
+ $initialize->addBody('Nette\Utils\Html::$xhtml = ?;', array((bool) $config['xhtml']));
+ }
+
+ if (isset($config['security']['frames']) && $config['security']['frames'] !== TRUE) {
+ $frames = $config['security']['frames'];
+ if ($frames === FALSE) {
+ $frames = 'DENY';
+ } elseif (preg_match('#^https?:#', $frames)) {
+ $frames = "ALLOW-FROM $frames";
+ }
+ $initialize->addBody('header(?);', array("X-Frame-Options: $frames"));
+ }
+
+ foreach ($container->findByTag('run') as $name => $on) {
+ if ($on) {
+ $initialize->addBody('$this->getService(?);', array($name));
+ }
+ }
+ }
+
+
+
+ private function checkTempDir($dir)
+ {
+ // checks whether directory is writable
+ $uniq = uniqid('_', TRUE);
+ if (!@mkdir("$dir/$uniq", 0777)) { // @ - is escalated to exception
+ throw new Nette\InvalidStateException("Unable to write to directory '$dir'. Make this directory writable.");
+ }
+
+ // tests subdirectory mode
+ $useDirs = @file_put_contents("$dir/$uniq/_", '') !== FALSE; // @ - error is expected
+ @unlink("$dir/$uniq/_");
+ @rmdir("$dir/$uniq"); // @ - directory may not already exist
+
+ return 'Nette\Caching\Storages\FileStorage::$useDirectories = ' . ($useDirs ? 'TRUE' : 'FALSE') . ";\n";
+ }
+
+}
diff --git a/libs/Nette/Config/Extensions/PhpExtension.php b/libs/Nette/Config/Extensions/PhpExtension.php
new file mode 100644
index 0000000..12580b9
--- /dev/null
+++ b/libs/Nette/Config/Extensions/PhpExtension.php
@@ -0,0 +1,55 @@
+methods['initialize'];
+ foreach ($this->getConfig() as $name => $value) {
+ if (!is_scalar($value)) {
+ throw new Nette\InvalidStateException("Configuration value for directive '$name' is not scalar.");
+
+ } elseif ($name === 'include_path') {
+ $initialize->addBody('set_include_path(?);', array(str_replace(';', PATH_SEPARATOR, $value)));
+
+ } elseif ($name === 'ignore_user_abort') {
+ $initialize->addBody('ignore_user_abort(?);', array($value));
+
+ } elseif ($name === 'max_execution_time') {
+ $initialize->addBody('set_time_limit(?);', array($value));
+
+ } elseif ($name === 'date.timezone') {
+ $initialize->addBody('date_default_timezone_set(?);', array($value));
+
+ } elseif (function_exists('ini_set')) {
+ $initialize->addBody('ini_set(?, ?);', array($name, $value));
+
+ } elseif (ini_get($name) != $value) { // intentionally ==
+ throw new Nette\NotSupportedException('Required function ini_set() is disabled.');
+ }
+ }
+ }
+
+}
diff --git a/libs/Nette/Config/Helpers.php b/libs/Nette/Config/Helpers.php
new file mode 100644
index 0000000..2744059
--- /dev/null
+++ b/libs/Nette/Config/Helpers.php
@@ -0,0 +1,95 @@
+ $val) {
+ if (is_int($key)) {
+ $right[] = $val;
+ } else {
+ if (is_array($val) && isset($val[self::EXTENDS_KEY])) {
+ if ($val[self::EXTENDS_KEY] === self::OVERWRITE) {
+ unset($val[self::EXTENDS_KEY]);
+ }
+ } elseif (isset($right[$key])) {
+ $val = static::merge($val, $right[$key]);
+ }
+ $right[$key] = $val;
+ }
+ }
+ return $right;
+
+ } elseif ($left === NULL && is_array($right)) {
+ return $right;
+
+ } else {
+ return $left;
+ }
+ }
+
+
+
+ /**
+ * Finds out and removes information about the parent.
+ * @return mixed
+ */
+ public static function takeParent(& $data)
+ {
+ if (is_array($data) && isset($data[self::EXTENDS_KEY])) {
+ $parent = $data[self::EXTENDS_KEY];
+ unset($data[self::EXTENDS_KEY]);
+ return $parent;
+ }
+ }
+
+
+
+ /**
+ * @return bool
+ */
+ public static function isOverwriting(& $data)
+ {
+ return is_array($data) && isset($data[self::EXTENDS_KEY]) && $data[self::EXTENDS_KEY] === self::OVERWRITE;
+ }
+
+
+
+ /**
+ * @return bool
+ */
+ public static function isInheriting(& $data)
+ {
+ return is_array($data) && isset($data[self::EXTENDS_KEY]) && $data[self::EXTENDS_KEY] !== self::OVERWRITE;
+ }
+
+}
diff --git a/libs/Nette/Config/IAdapter.php b/libs/Nette/Config/IAdapter.php
index 3b1edf1..0242ca5 100644
--- a/libs/Nette/Config/IAdapter.php
+++ b/libs/Nette/Config/IAdapter.php
@@ -1,41 +1,40 @@
- key2> key3) */
- public static $keySeparator = '.';
-
- /** @var string section inheriting separator (section < parent) */
- public static $sectionSeparator = ' < ';
-
- /** @var string raw section marker */
- public static $rawSection = '!';
-
-
-
- /**
- * Static class - cannot be instantiated.
- */
- final public function __construct()
- {
- throw new Nette\StaticClassException;
- }
-
-
-
- /**
- * Reads configuration from INI file.
- * @param string file name
- * @return array
- * @throws Nette\InvalidStateException
- */
- public static function load($file)
- {
- if (!is_file($file) || !is_readable($file)) {
- throw new Nette\FileNotFoundException("File '$file' is missing or is not readable.");
- }
-
- Nette\Diagnostics\Debugger::tryError();
- $ini = parse_ini_file($file, TRUE);
- if (Nette\Diagnostics\Debugger::catchError($e)) {
- throw new Nette\InvalidStateException('parse_ini_file(): ' . $e->getMessage(), 0, $e);
- }
-
- $separator = trim(self::$sectionSeparator);
- $data = array();
- foreach ($ini as $secName => $secData) {
- // is section?
- if (is_array($secData)) {
- if (substr($secName, -1) === self::$rawSection) {
- $secName = substr($secName, 0, -1);
-
- } elseif (self::$keySeparator) {
- // process key separators (key1> key2> key3)
- $tmp = array();
- foreach ($secData as $key => $val) {
- $cursor = & $tmp;
- foreach (explode(self::$keySeparator, $key) as $part) {
- if (!isset($cursor[$part]) || is_array($cursor[$part])) {
- $cursor = & $cursor[$part];
- } else {
- throw new Nette\InvalidStateException("Invalid key '$key' in section [$secName] in file '$file'.");
- }
- }
- $cursor = $val;
- }
- $secData = $tmp;
- }
-
- // process extends sections like [staging < production] (with special support for separator ':')
- $parts = $separator ? explode($separator, strtr($secName, ':', $separator)) : array($secName);
- if (count($parts) > 1) {
- $parent = trim($parts[1]);
- if (!isset($data[$parent]) || !is_array($data[$parent])) {
- throw new Nette\InvalidStateException("Missing parent section [$parent] in file '$file'.");
- }
- $secData = Nette\Utils\Arrays::mergeTree($secData, $data[$parent]);
- $secName = trim($parts[0]);
- if ($secName === '') {
- throw new Nette\InvalidStateException("Invalid empty section name in file '$file'.");
- }
- }
- }
-
- if (self::$keySeparator) {
- $cursor = & $data;
- foreach (explode(self::$keySeparator, $secName) as $part) {
- if (!isset($cursor[$part]) || is_array($cursor[$part])) {
- $cursor = & $cursor[$part];
- } else {
- throw new Nette\InvalidStateException("Invalid section [$secName] in file '$file'.");
- }
- }
- } else {
- $cursor = & $data[$secName];
- }
-
- if (is_array($secData) && is_array($cursor)) {
- $secData = Nette\Utils\Arrays::mergeTree($secData, $cursor);
- }
-
- $cursor = $secData;
- }
-
- return $data;
- }
-
-
-
- /**
- * Write INI file.
- * @param mixed
- * @param string file
- * @return void
- */
- public static function save($config, $file)
- {
- $output = array();
- $output[] = '; generated by Nette';// at ' . @strftime('%c');
- $output[] = '';
-
- foreach ($config as $secName => $secData) {
- if (!(is_array($secData) || $secData instanceof \Traversable)) {
- throw new Nette\InvalidStateException("Invalid section '$secName'.");
- }
-
- $output[] = "[$secName]";
- self::build($secData, $output, '');
- $output[] = '';
- }
-
- if (!file_put_contents($file, implode(PHP_EOL, $output))) {
- throw new Nette\IOException("Cannot write file '$file'.");
- }
- }
-
-
-
- /**
- * Recursive builds INI list.
- * @param array|\Traversable
- * @param array
- * @param string
- * @return void
- */
- private static function build($input, & $output, $prefix)
- {
- foreach ($input as $key => $val) {
- if (is_array($val) || $val instanceof \Traversable) {
- self::build($val, $output, $prefix . $key . self::$keySeparator);
-
- } elseif (is_bool($val)) {
- $output[] = "$prefix$key = " . ($val ? 'true' : 'false');
-
- } elseif (is_numeric($val)) {
- $output[] = "$prefix$key = $val";
-
- } elseif (is_string($val)) {
- $output[] = "$prefix$key = \"$val\"";
-
- } else {
- throw new Nette\InvalidArgumentException("The '$prefix$key' item must be scalar or array, " . gettype($val) ." given.");
- }
- }
- }
-
-}
diff --git a/libs/Nette/Config/Loader.php b/libs/Nette/Config/Loader.php
new file mode 100644
index 0000000..77c5802
--- /dev/null
+++ b/libs/Nette/Config/Loader.php
@@ -0,0 +1,139 @@
+ 'Nette\Config\Adapters\PhpAdapter',
+ 'ini' => 'Nette\Config\Adapters\IniAdapter',
+ 'neon' => 'Nette\Config\Adapters\NeonAdapter',
+ );
+
+ private $dependencies = array();
+
+
+
+ /**
+ * Reads configuration from file.
+ * @param string file name
+ * @param string optional section to load
+ * @return array
+ */
+ public function load($file, $section = NULL)
+ {
+ if (!is_file($file) || !is_readable($file)) {
+ throw new Nette\FileNotFoundException("File '$file' is missing or is not readable.");
+ }
+ $this->dependencies[] = $file = realpath($file);
+ $data = $this->getAdapter($file)->load($file);
+
+ if ($section) {
+ if (isset($data[self::INCLUDES_KEY])) {
+ throw new Nette\InvalidStateException("Section 'includes' must be placed under some top section in file '$file'.");
+ }
+ $data = $this->getSection($data, $section, $file);
+ }
+
+ // include child files
+ $merged = array();
+ if (isset($data[self::INCLUDES_KEY])) {
+ Validators::assert($data[self::INCLUDES_KEY], 'list', "section 'includes' in file '$file'");
+ foreach ($data[self::INCLUDES_KEY] as $include) {
+ $merged = Helpers::merge($this->load(dirname($file) . '/' . $include), $merged);
+ }
+ }
+ unset($data[self::INCLUDES_KEY]);
+
+ return Helpers::merge($data, $merged);
+ }
+
+
+
+ /**
+ * Save configuration to file.
+ * @param array
+ * @param string file
+ * @return void
+ */
+ public function save($data, $file)
+ {
+ if (file_put_contents($file, $this->getAdapter($file)->dump($data)) === FALSE) {
+ throw new Nette\IOException("Cannot write file '$file'.");
+ }
+ }
+
+
+
+ /**
+ * Returns configuration files.
+ * @return array
+ */
+ public function getDependencies()
+ {
+ return array_unique($this->dependencies);
+ }
+
+
+
+ /**
+ * Registers adapter for given file extension.
+ * @param string file extension
+ * @param string|Nette\Config\IAdapter
+ * @return Loader provides a fluent interface
+ */
+ public function addAdapter($extension, $adapter)
+ {
+ $this->adapters[strtolower($extension)] = $adapter;
+ return $this;
+ }
+
+
+
+ /** @return IAdapter */
+ private function getAdapter($file)
+ {
+ $extension = strtolower(pathinfo($file, PATHINFO_EXTENSION));
+ if (!isset($this->adapters[$extension])) {
+ throw new Nette\InvalidArgumentException("Unknown file extension '$file'.");
+ }
+ return is_object($this->adapters[$extension]) ? $this->adapters[$extension] : new $this->adapters[$extension];
+ }
+
+
+
+ private function getSection(array $data, $key, $file)
+ {
+ Validators::assertField($data, $key, 'array|null', "section '%' in file '$file'");
+ $item = $data[$key];
+ if ($parent = Helpers::takeParent($item)) {
+ $item = Helpers::merge($item, $this->getSection($data, $parent, $file));
+ }
+ return $item;
+ }
+
+}
diff --git a/libs/Nette/Config/NeonAdapter.php b/libs/Nette/Config/NeonAdapter.php
deleted file mode 100644
index d1e2295..0000000
--- a/libs/Nette/Config/NeonAdapter.php
+++ /dev/null
@@ -1,98 +0,0 @@
- $secData) {
- if ($secData === NULL) { // empty section
- $secData = array();
- }
-
- if (is_array($secData)) {
- // process extends sections like [staging < production]
- $parts = $separator ? explode($separator, $secName) : array($secName);
- if (count($parts) > 1) {
- $parent = trim($parts[1]);
- if (!isset($data[$parent]) || !is_array($data[$parent])) {
- throw new Nette\InvalidStateException("Missing parent section '$parent' in file '$file'.");
- }
- $secData = Nette\Utils\Arrays::mergeTree($secData, $data[$parent]);
- $secName = trim($parts[0]);
- if ($secName === '') {
- throw new Nette\InvalidStateException("Invalid empty section name in file '$file'.");
- }
- }
- }
-
- $data[$secName] = $secData;
- }
-
- return $data;
- }
-
-
-
- /**
- * Write NEON file.
- * @param mixed
- * @param string file
- * @return void
- */
- public static function save($config, $file)
- {
- if (!file_put_contents($file, "# generated by Nette\n\n" . Neon::encode($config, Neon::BLOCK))) {
- throw new Nette\IOException("Cannot write file '$file'.");
- }
- }
-
-}
diff --git a/libs/Nette/DI/Container.php b/libs/Nette/DI/Container.php
index 5e5111f..553e490 100644
--- a/libs/Nette/DI/Container.php
+++ b/libs/Nette/DI/Container.php
@@ -1,322 +1,371 @@
-updating();
- if (!is_string($name) || $name === '') {
- throw new Nette\InvalidArgumentException("Service name must be a non-empty string, " . gettype($name) . " given.");
- }
-
- if (isset($this->registry[$name]) || method_exists($this, "createService$name")) {
- throw new Nette\InvalidStateException("Service '$name' has already been registered.");
- }
-
- if (is_string($tags)) {
- $tags = array(self::TAG_TYPEHINT => array($tags));
- } elseif (is_array($tags)) {
- foreach ($tags as $id => $attrs) {
- if (is_int($id) && is_string($attrs)) {
- $tags[$attrs] = array();
- unset($tags[$id]);
- } elseif (!is_array($attrs)) {
- $tags[$id] = (array) $attrs;
- }
- }
- }
-
- if (is_string($service) && strpos($service, ':') === FALSE) { // class name
- if (!isset($tags[self::TAG_TYPEHINT][0])) {
- $tags[self::TAG_TYPEHINT][0] = $service;
- }
- $service = new ServiceBuilder($service);
- }
-
- if ($service instanceof IServiceBuilder) {
- $factory = array($service, 'createService');
-
- } elseif (is_object($service) && !$service instanceof \Closure && !$service instanceof Nette\Callback) {
- $this->registry[$name] = $service;
- $this->tags[$name] = $tags;
- return $this;
-
- } else {
- $factory = $service;
- }
-
- $this->factories[$name] = array(callback($factory));
- $this->tags[$name] = $tags;
- $this->registry[$name] = & $this->factories[$name][1]; // forces cloning using reference
- return $service;
- }
-
-
-
- /**
- * Removes the specified service type from the container.
- * @return void
- */
- public function removeService($name)
- {
- $this->updating();
- unset($this->registry[$name], $this->factories[$name]);
- }
-
-
-
- /**
- * Gets the service object by name.
- * @param string
- * @return object
- */
- public function getService($name)
- {
- if (isset($this->registry[$name])) {
- return $this->registry[$name];
-
- } elseif (isset($this->creating[$name])) {
- throw new Nette\InvalidStateException("Circular reference detected for services: "
- . implode(', ', array_keys($this->creating)) . ".");
- }
-
- if (isset($this->factories[$name])) {
- list($factory) = $this->factories[$name];
- if (!$factory->isCallable()) {
- throw new Nette\InvalidStateException("Unable to create service '$name', factory '$factory' is not callable.");
- }
-
- $this->creating[$name] = TRUE;
- try {
- $service = $factory($this);
- } catch (\Exception $e) {}
-
- } elseif (method_exists($this, $factory = 'createService' . ucfirst($name))) { // static method
- $this->creating[$name] = TRUE;
- try {
- $service = $this->$factory();
- } catch (\Exception $e) {}
-
- } else {
- throw new MissingServiceException("Service '$name' not found.");
- }
-
- unset($this->creating[$name]);
-
- if (isset($e)) {
- throw $e;
-
- } elseif (!is_object($service)) {
- throw new Nette\UnexpectedValueException("Unable to create service '$name', value returned by factory '$factory' is not object.");
-
- } elseif (isset($this->tags[$name][self::TAG_TYPEHINT][0]) && !$service instanceof $this->tags[$name][self::TAG_TYPEHINT][0]) {
- throw new Nette\UnexpectedValueException("Unable to create service '$name', value returned by factory '$factory' is not '{$this->tags[$name][self::TAG_TYPEHINT][0]}' type.");
- }
-
- unset($this->factories[$name]);
- return $this->registry[$name] = $service;
- }
-
-
-
- /**
- * Gets the service object of the specified type.
- * @param string
- * @return object
- */
- public function getServiceByType($type)
- {
- foreach ($this->registry as $name => $service) {
- if (isset($this->tags[$name][self::TAG_TYPEHINT][0]) ? !strcasecmp($this->tags[$name][self::TAG_TYPEHINT][0], $type) : $service instanceof $type) {
- $found[] = $name;
- }
- }
- if (!isset($found)) {
- throw new MissingServiceException("Service matching '$type' type not found.");
-
- } elseif (count($found) > 1) {
- throw new AmbiguousServiceException("Found more than one service ('" . implode("', '", $found) . "') matching '$type' type.");
- }
- return $this->getService($found[0]);
- }
-
-
-
- /**
- * Gets the service objects of the specified tag.
- * @param string
- * @return array of [service name => tag attributes]
- */
- public function getServiceNamesByTag($tag)
- {
- $found = array();
- foreach ($this->registry as $name => $service) {
- if (isset($this->tags[$name][$tag])) {
- $found[$name] = $this->tags[$name][$tag];
- }
- }
- return $found;
- }
-
-
-
- /**
- * Exists the service?
- * @param string service name
- * @return bool
- */
- public function hasService($name)
- {
- return isset($this->registry[$name])
- || isset($this->factories[$name])
- || method_exists($this, "createService$name");
- }
-
-
-
- /**
- * Checks the service type.
- * @param string
- * @param string
- * @return bool
- */
- public function checkServiceType($name, $type)
- {
- return isset($this->tags[$name][self::TAG_TYPEHINT][0])
- ? !strcasecmp($this->tags[$name][self::TAG_TYPEHINT][0], $type)
- : (isset($this->registry[$name]) && $this->registry[$name] instanceof $type);
- }
-
-
-
- /********************* tools ****************d*g**/
-
-
-
- /**
- * Expands %placeholders% in string.
- * @param string
- * @return string
- * @throws Nette\InvalidStateException
- */
- public function expand($s)
- {
- if (is_string($s) && strpos($s, '%') !== FALSE) {
- $that = $this;
- return @preg_replace_callback('#%([a-z0-9._-]*)%#i', function ($m) use ($that) { // intentionally @ due PHP bug #39257
- list(, $param) = $m;
- if ($param === '') {
- return '%';
- } elseif (!isset($that->params[$param])) {
- throw new Nette\InvalidArgumentException("Missing parameter '$param'.");
- } elseif (!is_scalar($val = $that->params[$param])) {
- throw new Nette\InvalidStateException("Parameter '$param' is not scalar.");
- }
- return $val;
- }, $s);
- }
- return $s;
- }
-
-
-
- /********************* shortcuts ****************d*g**/
-
-
-
- /**
- * Gets the service object, shortcut for getService().
- * @param string
- * @return object
- */
- public function &__get($name)
- {
- if (!isset($this->registry[$name])) {
- $this->getService($name);
- }
- return $this->registry[$name];
- }
-
-
-
- /**
- * Adds the service, shortcut for addService().
- * @param string
- * @param object
- * @return void
- */
- public function __set($name, $value)
- {
- $this->addService($name, $value);
- }
-
-
-
- /**
- * Exists the service?
- * @param string
- * @return bool
- */
- public function __isset($name)
- {
- return $this->hasService($name);
- }
-
-
-
- /**
- * Removes the service, shortcut for removeService().
- * @return void
- */
- public function __unset($name)
- {
- $this->removeService($name);
- }
-
-}
+parameters = $params + $this->parameters;
+ $this->params = &$this->parameters;
+ }
+
+
+
+ /**
+ * @return array
+ */
+ public function getParameters()
+ {
+ return $this->parameters;
+ }
+
+
+
+ /**
+ * Adds the service or service factory to the container.
+ * @param string
+ * @param mixed object, class name or callable
+ * @param array service meta information
+ * @return Container provides a fluent interface
+ */
+ public function addService($name, $service, array $meta = NULL)
+ {
+ $this->updating();
+ if (!is_string($name) || $name === '') {
+ throw new Nette\InvalidArgumentException("Service name must be a non-empty string, " . gettype($name) . " given.");
+ }
+
+ if (isset($this->registry[$name])) {
+ throw new Nette\InvalidStateException("Service '$name' has already been registered.");
+ }
+
+ if (is_object($service) && !$service instanceof \Closure && !$service instanceof Nette\Callback) {
+ $this->registry[$name] = $service;
+ $this->meta[$name] = $meta;
+ return $this;
+
+ } elseif (!is_string($service) || strpos($service, ':') !== FALSE/*5.2* || $service[0] === "\0"*/) { // callable
+ $service = new Nette\Callback($service);
+ }
+
+ $this->factories[$name] = array($service);
+ $this->registry[$name] = & $this->factories[$name][1]; // forces cloning using reference
+ $this->meta[$name] = $meta;
+ return $this;
+ }
+
+
+
+ /**
+ * Removes the service from the container.
+ * @param string
+ * @return void
+ */
+ public function removeService($name)
+ {
+ $this->updating();
+ unset($this->registry[$name], $this->factories[$name], $this->meta[$name]);
+ }
+
+
+
+ /**
+ * Gets the service object by name.
+ * @param string
+ * @return object
+ */
+ public function getService($name)
+ {
+ if (isset($this->registry[$name])) {
+ return $this->registry[$name];
+
+ } elseif (isset($this->creating[$name])) {
+ throw new Nette\InvalidStateException("Circular reference detected for services: "
+ . implode(', ', array_keys($this->creating)) . ".");
+ }
+
+ if (isset($this->factories[$name])) {
+ list($factory) = $this->factories[$name];
+ if (is_string($factory)) {
+ if (!class_exists($factory)) {
+ throw new Nette\InvalidStateException("Cannot instantiate service, class '$factory' not found.");
+ }
+ try {
+ $this->creating[$name] = TRUE;
+ $service = new $factory;
+ } catch (\Exception $e) {}
+
+ } elseif (!$factory->isCallable()) {
+ throw new Nette\InvalidStateException("Unable to create service '$name', factory '$factory' is not callable.");
+
+ } else {
+ $this->creating[$name] = TRUE;
+ try {
+ $service = $factory/*5.2*->invoke*/($this);
+ } catch (\Exception $e) {}
+ }
+
+ } elseif (method_exists($this, $factory = Container::getMethodName($name)) && $this->getReflection()->getMethod($factory)->getName() === $factory) {
+ $this->creating[$name] = TRUE;
+ try {
+ $service = $this->$factory();
+ } catch (\Exception $e) {}
+
+ } else {
+ throw new MissingServiceException("Service '$name' not found.");
+ }
+
+ unset($this->creating[$name]);
+
+ if (isset($e)) {
+ throw $e;
+
+ } elseif (!is_object($service)) {
+ throw new Nette\UnexpectedValueException("Unable to create service '$name', value returned by factory '$factory' is not object.");
+ }
+
+ return $this->registry[$name] = $service;
+ }
+
+
+
+ /**
+ * Does the service exist?
+ * @param string service name
+ * @return bool
+ */
+ public function hasService($name)
+ {
+ return isset($this->registry[$name])
+ || isset($this->factories[$name])
+ || method_exists($this, $method = Container::getMethodName($name)) && $this->getReflection()->getMethod($method)->getName() === $method;
+ }
+
+
+
+ /**
+ * Is the service created?
+ * @param string service name
+ * @return bool
+ */
+ public function isCreated($name)
+ {
+ if (!$this->hasService($name)) {
+ throw new MissingServiceException("Service '$name' not found.");
+ }
+ return isset($this->registry[$name]);
+ }
+
+
+
+ /**
+ * Resolves service by type.
+ * @param string class or interface
+ * @param bool throw exception if service doesn't exist?
+ * @return object service or NULL
+ * @throws MissingServiceException
+ */
+ public function getByType($class, $need = TRUE)
+ {
+ $lower = ltrim(strtolower($class), '\\');
+ if (!isset($this->classes[$lower])) {
+ if ($need) {
+ throw new MissingServiceException("Service of type $class not found.");
+ }
+ } elseif ($this->classes[$lower] === FALSE) {
+ throw new MissingServiceException("Multiple services of type $class found.");
+ } else {
+ return $this->getService($this->classes[$lower]);
+ }
+ }
+
+
+
+ /**
+ * Gets the service names of the specified tag.
+ * @param string
+ * @return array of [service name => tag attributes]
+ */
+ public function findByTag($tag)
+ {
+ $found = array();
+ foreach ($this->meta as $name => $meta) {
+ if (isset($meta[self::TAGS][$tag])) {
+ $found[$name] = $meta[self::TAGS][$tag];
+ }
+ }
+ return $found;
+ }
+
+
+
+ /********************* autowiring ****************d*g**/
+
+
+
+ /**
+ * Creates new instance using autowiring.
+ * @param string class
+ * @param array arguments
+ * @return object
+ * @throws Nette\InvalidArgumentException
+ */
+ public function createInstance($class, array $args = array())
+ {
+ $rc = Nette\Reflection\ClassType::from($class);
+ if (!$rc->isInstantiable()) {
+ throw new ServiceCreationException("Class $class is not instantiable.");
+
+ } elseif ($constructor = $rc->getConstructor()) {
+ return $rc->newInstanceArgs(Helpers::autowireArguments($constructor, $args, $this));
+
+ } elseif ($args) {
+ throw new ServiceCreationException("Unable to pass arguments, class $class has no constructor.");
+ }
+ return new $class;
+ }
+
+
+
+ /**
+ * Calls method using autowiring.
+ * @param mixed class, object, function, callable
+ * @param array arguments
+ * @return mixed
+ */
+ public function callMethod($function, array $args = array())
+ {
+ $callback = new Nette\Callback($function);
+ return $callback->invokeArgs(Helpers::autowireArguments($callback->toReflection(), $args, $this));
+ }
+
+
+
+ /********************* shortcuts ****************d*g**/
+
+
+
+ /**
+ * Expands %placeholders%.
+ * @param mixed
+ * @return mixed
+ */
+ public function expand($s)
+ {
+ return Helpers::expand($s, $this->parameters);
+ }
+
+
+
+ /**
+ * Gets the service object, shortcut for getService().
+ * @param string
+ * @return object
+ */
+ public function &__get($name)
+ {
+ if (!isset($this->registry[$name])) {
+ $this->getService($name);
+ }
+ return $this->registry[$name];
+ }
+
+
+
+ /**
+ * Adds the service object.
+ * @param string
+ * @param object
+ * @return void
+ */
+ public function __set($name, $service)
+ {
+ $this->updating();
+ if (!is_string($name) || $name === '') {
+ throw new Nette\InvalidArgumentException("Service name must be a non-empty string, " . gettype($name) . " given.");
+
+ } elseif (isset($this->registry[$name])) {
+ throw new Nette\InvalidStateException("Service '$name' has already been registered.");
+
+ } elseif (!is_object($service)) {
+ throw new Nette\InvalidArgumentException("Service must be a object, " . gettype($service) . " given.");
+ }
+ $this->registry[$name] = $service;
+ }
+
+
+
+ /**
+ * Does the service exist?
+ * @param string
+ * @return bool
+ */
+ public function __isset($name)
+ {
+ return $this->hasService($name);
+ }
+
+
+
+ /**
+ * Removes the service, shortcut for removeService().
+ * @return void
+ */
+ public function __unset($name)
+ {
+ $this->removeService($name);
+ }
+
+
+
+ public static function getMethodName($name, $isService = TRUE)
+ {
+ $uname = ucfirst($name);
+ return ($isService ? 'createService' : 'create') . ($name === $uname ? '__' : '') . str_replace('.', '__', $uname);
+ }
+
+}
diff --git a/libs/Nette/DI/ContainerBuilder.php b/libs/Nette/DI/ContainerBuilder.php
index 36cdc3b..1ea9f65 100644
--- a/libs/Nette/DI/ContainerBuilder.php
+++ b/libs/Nette/DI/ContainerBuilder.php
@@ -1,148 +1,585 @@
- array(
- * class => 'ClassName' or factory => 'Factory::create'
- * arguments => array(...)
- * methods => array(
- * array(methodName, array(...))
- * ...
- * )
- * tags => array(...)
- * )
- */
- public function addDefinitions(IContainer $container, array $definitions)
- {
- foreach ($definitions as $name => $definition) {
- if (!is_array($definition)) {
- $definition = array('class' => $definition);
- }
-
- $arguments = isset($definition['arguments']) ? $definition['arguments'] : array();
- $expander = function(&$val) use ($container) {
- $val = $val[0] === '@' ? $container->getService(substr($val, 1)) : $container->expand($val);
- };
-
- if (isset($definition['class'])) {
- $class = $definition['class'];
- $methods = isset($definition['methods']) ? $definition['methods'] : array();
- $factory = function($container) use ($class, $arguments, $methods, $expander) {
- $class = $container->expand($class);
- if ($arguments) {
- array_walk_recursive($arguments, $expander);
- $service = Nette\Reflection\ClassType::from($class)->newInstanceArgs($arguments);
- } else {
- $service = new $class;
- }
-
- array_walk_recursive($methods, $expander);
- foreach ($methods as $method) {
- call_user_func_array(array($service, $method[0]), isset($method[1]) ? $method[1] : array());
- }
-
- return $service;
- };
-
- } elseif (isset($definition['factory'])) {
- array_unshift($arguments, $definition['factory']);
- $factory = function($container) use ($arguments, $expander) {
- array_walk_recursive($arguments, $expander);
- $factory = $arguments[0]; $arguments[0] = $container;
- return call_user_func_array($factory, $arguments);
- };
- } else {
- throw new Nette\InvalidStateException("Factory method is not specified.");
- }
-
- if (isset($definition['tags'])) {
- $tags = (array) $definition['tags'];
- array_walk_recursive($tags, $expander);
- } else {
- $tags = NULL;
- }
- $container->addService($name, $factory, $tags);
- }
- }
-
-
-
- public function generateCode(array $definitions)
- {
- $code = '';
- foreach ($definitions as $name => $definition) {
- $name = $this->varExport($name);
- if (is_scalar($definition)) {
- $factory = $this->varExport($definition);
- $code .= "\$container->addService($name, $factory);\n\n";
- continue;
- }
-
- $arguments = $this->argsExport(isset($definition['arguments']) ? $definition['arguments'] : array());
-
- if (isset($definition['class'])) {
- $class = $this->argsExport(array($definition['class']));
- $methods = isset($definition['methods']) ? $definition['methods'] : array();
- $factory = "function(\$container) {\n\t\$class = $class; \$service = new \$class($arguments);\n";
- foreach ($methods as $method) {
- $args = isset($method[1]) ? $this->argsExport($method[1]) : '';
- $factory .= "\t\$service->$method[0]($args);\n";
- }
- $factory .= "\treturn \$service;\n}";
-
- } elseif (isset($definition['factory'])) {
- $factory = $this->argsExport(array($definition['factory']));
- $factory = "function(\$container) {\n\treturn call_user_func(\n\t\t$factory,\n\t\t\$container"
- . ($arguments ? ",\n\t\t$arguments" : '') . "\n\t);\n}";
- } else {
- throw new Nette\InvalidStateException("Factory method is not specified.");
- }
-
- $tags = isset($definition['tags']) ? $this->argsExport(array($definition['tags'])) : 'NULL';
- $code .= "\$container->addService($name, $factory, $tags);\n\n";
- }
- return $code;
- }
-
-
-
- private function argsExport($args)
- {
- $args = implode(', ', array_map(array($this, 'varExport'), $args));
- $args = preg_replace("#'@(\w+)'#", '\$container->getService(\'$1\')', $args);
- $args = preg_replace("#('[^']*%[^']*')#", '\$container->expand($1)', $args);
- return $args;
- }
-
-
-
- private function varExport($arg)
- {
- return preg_replace('#\n *#', ' ', var_export($arg, TRUE));
- }
-
-}
+definitions[$name])) {
+ throw new Nette\InvalidStateException("Service '$name' has already been added.");
+ }
+ return $this->definitions[$name] = new ServiceDefinition;
+ }
+
+
+
+ /**
+ * Removes the specified service definition.
+ * @param string
+ * @return void
+ */
+ public function removeDefinition($name)
+ {
+ unset($this->definitions[$name]);
+ }
+
+
+
+ /**
+ * Gets the service definition.
+ * @param string
+ * @return ServiceDefinition
+ */
+ public function getDefinition($name)
+ {
+ if (!isset($this->definitions[$name])) {
+ throw new MissingServiceException("Service '$name' not found.");
+ }
+ return $this->definitions[$name];
+ }
+
+
+
+ /**
+ * Gets all service definitions.
+ * @return array
+ */
+ public function getDefinitions()
+ {
+ return $this->definitions;
+ }
+
+
+
+ /**
+ * Does the service definition exist?
+ * @param string
+ * @return bool
+ */
+ public function hasDefinition($name)
+ {
+ return isset($this->definitions[$name]);
+ }
+
+
+
+ /********************* class resolving ****************d*g**/
+
+
+
+ /**
+ * Resolves service name by type.
+ * @param string class or interface
+ * @return string service name or NULL
+ * @throws ServiceCreationException
+ */
+ public function getByType($class)
+ {
+ $lower = ltrim(strtolower($class), '\\');
+ if (!isset($this->classes[$lower])) {
+ return;
+
+ } elseif (count($this->classes[$lower]) === 1) {
+ return $this->classes[$lower][0];
+
+ } else {
+ throw new ServiceCreationException("Multiple services of type $class found: " . implode(', ', $this->classes[$lower]));
+ }
+ }
+
+
+
+ /**
+ * Gets the service objects of the specified tag.
+ * @param string
+ * @return array of [service name => tag attributes]
+ */
+ public function findByTag($tag)
+ {
+ $found = array();
+ foreach ($this->definitions as $name => $def) {
+ if (isset($def->tags[$tag]) && $def->shared) {
+ $found[$name] = $def->tags[$tag];
+ }
+ }
+ return $found;
+ }
+
+
+
+ /**
+ * Creates a list of arguments using autowiring.
+ * @return array
+ */
+ public function autowireArguments($class, $method, array $arguments)
+ {
+ $rc = Nette\Reflection\ClassType::from($class);
+ if (!$rc->hasMethod($method)) {
+ if (!Nette\Utils\Validators::isList($arguments)) {
+ throw new ServiceCreationException("Unable to pass specified arguments to $class::$method().");
+ }
+ return $arguments;
+ }
+
+ $rm = $rc->getMethod($method);
+ if ($rm->isAbstract() || !$rm->isPublic()) {
+ throw new ServiceCreationException("$rm is not callable.");
+ }
+ $this->addDependency($rm->getFileName());
+ return Helpers::autowireArguments($rm, $arguments, $this);
+ }
+
+
+
+ /**
+ * Generates $dependencies, $classes and expands and normalize class names.
+ * @return array
+ */
+ public function prepareClassList()
+ {
+ // complete class-factory pairs; expand classes
+ foreach ($this->definitions as $name => $def) {
+ if ($def->class === self::CREATED_SERVICE || ($def->factory && $def->factory->entity === self::CREATED_SERVICE)) {
+ $def->class = $name;
+ $def->internal = TRUE;
+ if ($def->factory && $def->factory->entity === self::CREATED_SERVICE) {
+ $def->factory->entity = $def->class;
+ }
+ unset($this->definitions[$name]);
+ $this->definitions['_anonymous_' . str_replace('\\', '_', strtolower(trim($name, '\\')))] = $def;
+ }
+
+ if ($def->class) {
+ $def->class = $this->expand($def->class);
+ if (!$def->factory) {
+ $def->factory = new Statement($def->class);
+ }
+ } elseif (!$def->factory) {
+ throw new ServiceCreationException("Class and factory are missing in service '$name' definition.");
+ }
+ }
+
+ // complete classes
+ $this->classes = FALSE;
+ foreach ($this->definitions as $name => $def) {
+ $this->resolveClass($name);
+ }
+
+ // build auto-wiring list
+ $this->classes = array();
+ foreach ($this->definitions as $name => $def) {
+ if (!$def->class) {
+ continue;
+ }
+ if (!class_exists($def->class) && !interface_exists($def->class)) {
+ throw new Nette\InvalidStateException("Class $def->class has not been found.");
+ }
+ $def->class = Nette\Reflection\ClassType::from($def->class)->getName();
+ if ($def->autowired) {
+ foreach (class_parents($def->class) + class_implements($def->class) + array($def->class) as $parent) {
+ $this->classes[strtolower($parent)][] = $name;
+ }
+ }
+ }
+
+ foreach ($this->classes as $class => $foo) {
+ $this->addDependency(Nette\Reflection\ClassType::from($class)->getFileName());
+ }
+ }
+
+
+
+ private function resolveClass($name, $recursive = array())
+ {
+ if (isset($recursive[$name])) {
+ throw new Nette\InvalidArgumentException('Circular reference detected for services: ' . implode(', ', array_keys($recursive)) . '.');
+ }
+ $recursive[$name] = TRUE;
+
+ $def = $this->definitions[$name];
+ $factory = $this->normalizeEntity($this->expand($def->factory->entity));
+
+ if ($def->class) {
+ return $def->class;
+
+ } elseif (is_array($factory)) { // method calling
+ if ($service = $this->getServiceName($factory[0])) {
+ if (Strings::contains($service, '\\')) { // @\Class
+ throw new ServiceCreationException("Unable resolve class name for service '$name'.");
+ }
+ $factory[0] = $this->resolveClass($service, $recursive);
+ if (!$factory[0]) {
+ return;
+ }
+ }
+ $factory = new Nette\Callback($factory);
+ if (!$factory->isCallable()) {
+ throw new Nette\InvalidStateException("Factory '$factory' is not callable.");
+ }
+ try {
+ $reflection = $factory->toReflection();
+ $def->class = preg_replace('#[|\s].*#', '', $reflection->getAnnotation('return'));
+ if ($def->class && !class_exists($def->class) && $def->class[0] !== '\\' && $reflection instanceof \ReflectionMethod) {
+ /**/$def->class = $reflection->getDeclaringClass()->getNamespaceName() . '\\' . $def->class;/**/
+ }
+ } catch (\ReflectionException $e) {
+ }
+
+ } elseif ($service = $this->getServiceName($factory)) { // alias or factory
+ if (Strings::contains($service, '\\')) { // @\Class
+ /*5.2* $service = ltrim($service, '\\');*/
+ $def->autowired = FALSE;
+ return $def->class = $service;
+ }
+ if ($this->definitions[$service]->shared) {
+ $def->autowired = FALSE;
+ }
+ return $def->class = $this->resolveClass($service, $recursive);
+
+ } else {
+ return $def->class = $factory; // class name
+ }
+ }
+
+
+
+ /**
+ * Adds a file to the list of dependencies.
+ * @return ContainerBuilder provides a fluent interface
+ */
+ public function addDependency($file)
+ {
+ $this->dependencies[$file] = TRUE;
+ return $this;
+ }
+
+
+
+ /**
+ * Returns the list of dependent files.
+ * @return array
+ */
+ public function getDependencies()
+ {
+ unset($this->dependencies[FALSE]);
+ return array_keys($this->dependencies);
+ }
+
+
+
+ /********************* code generator ****************d*g**/
+
+
+
+ /**
+ * Generates PHP class.
+ * @return Nette\Utils\PhpGenerator\ClassType
+ */
+ public function generateClass($parentClass = 'Nette\DI\Container')
+ {
+ unset($this->definitions[self::THIS_CONTAINER]);
+ $this->addDefinition(self::THIS_CONTAINER)->setClass($parentClass);
+
+ $this->prepareClassList();
+
+ $class = new Nette\Utils\PhpGenerator\ClassType('Container');
+ $class->addExtend($parentClass);
+ $class->addMethod('__construct')
+ ->addBody('parent::__construct(?);', array($this->expand($this->parameters)));
+
+ $classes = $class->addProperty('classes', array());
+ foreach ($this->classes as $name => $foo) {
+ try {
+ $classes->value[$name] = $this->getByType($name);
+ } catch (ServiceCreationException $e) {
+ $classes->value[$name] = new PhpLiteral('FALSE, //' . strstr($e->getMessage(), ':'));
+ }
+ }
+
+ $definitions = $this->definitions;
+ ksort($definitions);
+
+ $meta = $class->addProperty('meta', array());
+ foreach ($definitions as $name => $def) {
+ if ($def->shared) {
+ foreach ($this->expand($def->tags) as $tag => $value) {
+ $meta->value[$name][Container::TAGS][$tag] = $value;
+ }
+ }
+ }
+
+ foreach ($definitions as $name => $def) {
+ try {
+ $type = $def->class ?: 'object';
+ $methodName = Container::getMethodName($name, $def->shared);
+ if (!PhpHelpers::isIdentifier($methodName)) {
+ throw new ServiceCreationException('Name contains invalid characters.');
+ }
+ if ($def->shared && !$def->internal && PhpHelpers::isIdentifier($name)) {
+ $class->addDocument("@property $type \$$name");
+ }
+ $method = $class->addMethod($methodName)
+ ->addDocument("@return $type")
+ ->setVisibility($def->shared || $def->internal ? 'protected' : 'public')
+ ->setBody($name === self::THIS_CONTAINER ? 'return $this;' : $this->generateService($name));
+
+ foreach ($this->expand($def->parameters) as $k => $v) {
+ $tmp = explode(' ', is_int($k) ? $v : $k);
+ $param = is_int($k) ? $method->addParameter(end($tmp)) : $method->addParameter(end($tmp), $v);
+ if (isset($tmp[1])) {
+ $param->setTypeHint($tmp[0]);
+ }
+ }
+ } catch (\Exception $e) {
+ throw new ServiceCreationException("Service '$name': " . $e->getMessage(), NULL, $e);
+ }
+ }
+
+ return $class;
+ }
+
+
+
+ /**
+ * Generates body of service method.
+ * @return string
+ */
+ private function generateService($name)
+ {
+ $def = $this->definitions[$name];
+ $parameters = $this->parameters;
+ foreach ($this->expand($def->parameters) as $k => $v) {
+ $v = explode(' ', is_int($k) ? $v : $k);
+ $parameters[end($v)] = new PhpLiteral('$' . end($v));
+ }
+
+ $code = '$service = ' . $this->formatStatement(Helpers::expand($def->factory, $parameters, TRUE)) . ";\n";
+
+ $entity = $this->normalizeEntity($def->factory->entity);
+ if ($def->class && $def->class !== $entity && !$this->getServiceName($entity)) {
+ $code .= PhpHelpers::formatArgs("if (!\$service instanceof $def->class) {\n"
+ . "\tthrow new Nette\\UnexpectedValueException(?);\n}\n",
+ array("Unable to create service '$name', value returned by factory is not $def->class type.")
+ );
+ }
+
+ foreach ((array) $def->setup as $setup) {
+ $setup = Helpers::expand($setup, $parameters, TRUE);
+ if (is_string($setup->entity) && strpbrk($setup->entity, ':@?') === FALSE) { // auto-prepend @self
+ $setup->entity = array("@$name", $setup->entity);
+ }
+ $code .= $this->formatStatement($setup, $name) . ";\n";
+ }
+
+ return $code .= 'return $service;';
+ }
+
+
+
+ /**
+ * Formats PHP code for class instantiating, function calling or property setting in PHP.
+ * @return string
+ * @internal
+ */
+ public function formatStatement(Statement $statement, $self = NULL)
+ {
+ $entity = $this->normalizeEntity($statement->entity);
+ $arguments = $statement->arguments;
+
+ if (is_string($entity) && Strings::contains($entity, '?')) { // PHP literal
+ return $this->formatPhp($entity, $arguments, $self);
+
+ } elseif ($service = $this->getServiceName($entity)) { // factory calling or service retrieving
+ if ($this->definitions[$service]->shared) {
+ if ($arguments) {
+ throw new ServiceCreationException("Unable to call service '$entity'.");
+ }
+ return $this->formatPhp('$this->getService(?)', array($service));
+ }
+ $params = array();
+ foreach ($this->definitions[$service]->parameters as $k => $v) {
+ $params[] = preg_replace('#\w+$#', '\$$0', (is_int($k) ? $v : $k)) . (is_int($k) ? '' : ' = ' . PhpHelpers::dump($v));
+ }
+ $rm = new Nette\Reflection\GlobalFunction(create_function(implode(', ', $params), ''));
+ $arguments = Helpers::autowireArguments($rm, $arguments, $this);
+ return $this->formatPhp('$this->?(?*)', array(Container::getMethodName($service, FALSE), $arguments), $self);
+
+ } elseif ($entity === 'not') { // operator
+ return $this->formatPhp('!?', array($arguments[0]));
+
+ } elseif (is_string($entity)) { // class name
+ if ($constructor = Nette\Reflection\ClassType::from($entity)->getConstructor()) {
+ $this->addDependency($constructor->getFileName());
+ $arguments = Helpers::autowireArguments($constructor, $arguments, $this);
+ } elseif ($arguments) {
+ throw new ServiceCreationException("Unable to pass arguments, class $entity has no constructor.");
+ }
+ return $this->formatPhp("new $entity" . ($arguments ? '(?*)' : ''), array($arguments), $self);
+
+ } elseif (!Validators::isList($entity) || count($entity) !== 2) {
+ throw new Nette\InvalidStateException("Expected class, method or property, " . PhpHelpers::dump($entity) . " given.");
+
+ } elseif ($entity[0] === '') { // globalFunc
+ return $this->formatPhp("$entity[1](?*)", array($arguments), $self);
+
+ } elseif (Strings::contains($entity[1], '$')) { // property setter
+ Validators::assert($arguments, 'list:1', "setup arguments for '" . Nette\Callback::create($entity) . "'");
+ if ($this->getServiceName($entity[0], $self)) {
+ return $this->formatPhp('?->? = ?', array($entity[0], substr($entity[1], 1), $arguments[0]), $self);
+ } else {
+ return $this->formatPhp($entity[0] . '::$? = ?', array(substr($entity[1], 1), $arguments[0]), $self);
+ }
+
+ } elseif ($service = $this->getServiceName($entity[0], $self)) { // service method
+ if ($this->definitions[$service]->class) {
+ $arguments = $this->autowireArguments($this->definitions[$service]->class, $entity[1], $arguments);
+ }
+ return $this->formatPhp('?->?(?*)', array($entity[0], $entity[1], $arguments), $self);
+
+ } else { // static method
+ $arguments = $this->autowireArguments($entity[0], $entity[1], $arguments);
+ return $this->formatPhp("$entity[0]::$entity[1](?*)", array($arguments), $self);
+ }
+ }
+
+
+
+ /**
+ * Formats PHP statement.
+ * @return string
+ */
+ public function formatPhp($statement, $args, $self = NULL)
+ {
+ $that = $this;
+ array_walk_recursive($args, function(&$val) use ($self, $that) {
+ list($val) = $that->normalizeEntity(array($val));
+
+ if ($val instanceof Statement) {
+ $val = new PhpLiteral($that->formatStatement($val, $self));
+
+ } elseif ($val === '@' . ContainerBuilder::THIS_CONTAINER) {
+ $val = new PhpLiteral('$this');
+
+ } elseif ($service = $that->getServiceName($val, $self)) {
+ $val = $service === $self ? '$service' : $that->formatStatement(new Statement($val));
+ $val = new PhpLiteral($val);
+ }
+ });
+ return PhpHelpers::formatArgs($statement, $args);
+ }
+
+
+
+ /**
+ * Expands %placeholders% in strings (recursive).
+ * @param mixed
+ * @return mixed
+ */
+ public function expand($value)
+ {
+ return Helpers::expand($value, $this->parameters, TRUE);
+ }
+
+
+
+ /** @internal */
+ public function normalizeEntity($entity)
+ {
+ if (is_string($entity) && Strings::contains($entity, '::') && !Strings::contains($entity, '?')) { // Class::method -> [Class, method]
+ $entity = explode('::', $entity);
+ }
+
+ if (is_array($entity) && $entity[0] instanceof ServiceDefinition) { // [ServiceDefinition, ...] -> [@serviceName, ...]
+ $tmp = array_keys($this->definitions, $entity[0], TRUE);
+ $entity[0] = "@$tmp[0]";
+
+ } elseif ($entity instanceof ServiceDefinition) { // ServiceDefinition -> @serviceName
+ $tmp = array_keys($this->definitions, $entity, TRUE);
+ $entity = "@$tmp[0]";
+
+ } elseif (is_array($entity) && $entity[0] === $this) { // [$this, ...] -> [@container, ...]
+ $entity[0] = '@' . ContainerBuilder::THIS_CONTAINER;
+ }
+ return $entity; // Class, @service, [Class, member], [@service, member], [, globalFunc]
+ }
+
+
+
+ /**
+ * Converts @service or @\Class -> service name and checks its existence.
+ * @param mixed
+ * @return string of FALSE, if argument is not service name
+ */
+ public function getServiceName($arg, $self = NULL)
+ {
+ if (!is_string($arg) || !preg_match('#^@[\w\\\\.].+$#', $arg)) {
+ return FALSE;
+ }
+ $service = substr($arg, 1);
+ if ($service === self::CREATED_SERVICE) {
+ $service = $self;
+ }
+ if (Strings::contains($service, '\\')) {
+ if ($this->classes === FALSE) { // may be disabled by prepareClassList
+ return $service;
+ }
+ $res = $this->getByType($service);
+ if (!$res) {
+ throw new ServiceCreationException("Reference to missing service of type $service.");
+ }
+ return $res;
+ }
+ if (!isset($this->definitions[$service])) {
+ throw new ServiceCreationException("Reference to missing service '$service'.");
+ }
+ return $service;
+ }
+
+}
diff --git a/libs/Nette/DI/Diagnostics/ContainerPanel.php b/libs/Nette/DI/Diagnostics/ContainerPanel.php
new file mode 100644
index 0000000..8366d44
--- /dev/null
+++ b/libs/Nette/DI/Diagnostics/ContainerPanel.php
@@ -0,0 +1,93 @@
+container = $container;
+ }
+
+
+
+ /**
+ * Renders tab.
+ * @return string
+ */
+ public function getTab()
+ {
+ ob_start();
+ require __DIR__ . '/templates/ContainerPanel.tab.phtml';
+ return ob_get_clean();
+ }
+
+
+
+ /**
+ * Renders panel.
+ * @return string
+ */
+ public function getPanel()
+ {
+ $services = $this->getContainerProperty('factories');
+ $factories = array();
+ foreach (Nette\Reflection\ClassType::from($this->container)->getMethods() as $method) {
+ if (preg_match('#^create(Service)?(.+)$#', $method->getName(), $m)) {
+ $name = str_replace('__', '.', strtolower(substr($m[2], 0, 1)) . substr($m[2], 1));
+ if ($m[1]) {
+ $services[$name] = $method->getAnnotation('return');
+ } elseif ($method->isPublic()) {
+ $a = strrpos(".$name", '.');
+ $factories[substr($name, 0, $a) . 'create' . ucfirst(substr($name, $a))] = $method->getAnnotation('return');
+ }
+ }
+ }
+ ksort($services);
+ ksort($factories);
+ $container = $this->container;
+ $registry = $this->getContainerProperty('registry');
+
+ ob_start();
+ require __DIR__ . '/templates/ContainerPanel.panel.phtml';
+ return ob_get_clean();
+ }
+
+
+
+ private function getContainerProperty($name)
+ {
+ $prop = Nette\Reflection\ClassType::from('Nette\DI\Container')->getProperty($name);
+ $prop->setAccessible(TRUE);
+ return $prop->getValue($this->container);
+ }
+
+}
diff --git a/libs/Nette/DI/Diagnostics/templates/ContainerPanel.panel.phtml b/libs/Nette/DI/Diagnostics/templates/ContainerPanel.panel.phtml
new file mode 100644
index 0000000..c2dd2b9
--- /dev/null
+++ b/libs/Nette/DI/Diagnostics/templates/ContainerPanel.panel.phtml
@@ -0,0 +1,99 @@
+
+
+
+
+
container) ?>
+
+
+
Parameters
+
+
+ container->parameters); ?>
+
+
+
Services
+
+
+
+
+ | Name |
+ Autowired |
+ Service |
+ Meta |
+
+
+
+ $class): ?>
+ classes, $name); ?>
+
+ | ', $name)) ?> |
+ " class=""> |
+
+
+
+
+
+
+
+
+ |
+ meta[$name])) { echo Helpers::clickableDump($container->meta[$name], TRUE); } ?> |
+
+
+
+
+
+
Factories
+
+
+
+
+ | Method |
+ Returns |
+
+
+
+ $class): ?>
+
+ | ', $name)) ?>() |
+ |
+
+
+
+
+
+
diff --git a/libs/Nette/DI/Diagnostics/templates/ContainerPanel.tab.phtml b/libs/Nette/DI/Diagnostics/templates/ContainerPanel.tab.phtml
new file mode 100644
index 0000000..fc4e380
--- /dev/null
+++ b/libs/Nette/DI/Diagnostics/templates/ContainerPanel.tab.phtml
@@ -0,0 +1,9 @@
+
+
diff --git a/libs/Nette/DI/Helpers.php b/libs/Nette/DI/Helpers.php
new file mode 100644
index 0000000..b53af78
--- /dev/null
+++ b/libs/Nette/DI/Helpers.php
@@ -0,0 +1,160 @@
+ $val) {
+ $res[$key] = self::expand($val, $params, $recursive);
+ }
+ return $res;
+
+ } elseif ($var instanceof Statement) {
+ return new Statement(self::expand($var->entity, $params, $recursive), self::expand($var->arguments, $params, $recursive));
+
+ } elseif (!is_string($var)) {
+ return $var;
+ }
+
+ $parts = preg_split('#%([\w.-]*)%#i', $var, -1, PREG_SPLIT_DELIM_CAPTURE);
+ $res = '';
+ foreach ($parts as $n => $part) {
+ if ($n % 2 === 0) {
+ $res .= $part;
+
+ } elseif ($part === '') {
+ $res .= '%';
+
+ } elseif (isset($recursive[$part])) {
+ throw new Nette\InvalidArgumentException('Circular reference detected for variables: ' . implode(', ', array_keys($recursive)) . '.');
+
+ } else {
+ $val = Nette\Utils\Arrays::get($params, explode('.', $part));
+ if ($recursive) {
+ $val = self::expand($val, $params, (is_array($recursive) ? $recursive : array()) + array($part => 1));
+ }
+ if (strlen($part) + 2 === strlen($var)) {
+ return $val;
+ }
+ if (!is_scalar($val)) {
+ throw new Nette\InvalidArgumentException("Unable to concatenate non-scalar parameter '$part' into '$var'.");
+ }
+ $res .= $val;
+ }
+ }
+ return $res;
+ }
+
+
+
+ /**
+ * Expand counterpart.
+ * @param mixed
+ * @return mixed
+ */
+ public static function escape($value)
+ {
+ if (is_array($value)) {
+ array_walk_recursive($value, function(&$val) {
+ $val = is_string($val) ? str_replace('%', '%%', $val) : $val;
+ });
+ } elseif (is_string($value)) {
+ $value = str_replace('%', '%%', $value);
+ }
+ return $value;
+ }
+
+
+
+ /**
+ * Generates list of arguments using autowiring.
+ * @param Nette\Reflection\GlobalFunction|Nette\Reflection\Method
+ * @return array
+ */
+ public static function autowireArguments(\ReflectionFunctionAbstract $method, array $arguments, $container)
+ {
+ $optCount = 0;
+ $num = -1;
+ $res = array();
+
+ foreach ($method->getParameters() as $num => $parameter) {
+ if (array_key_exists($num, $arguments)) {
+ $res[$num] = $arguments[$num];
+ unset($arguments[$num]);
+ $optCount = 0;
+
+ } elseif (array_key_exists($parameter->getName(), $arguments)) {
+ $res[$num] = $arguments[$parameter->getName()];
+ unset($arguments[$parameter->getName()]);
+ $optCount = 0;
+
+ } elseif ($class = $parameter->getClassName()) { // has object type hint
+ $res[$num] = $container->getByType($class, FALSE);
+ if ($res[$num] === NULL) {
+ if ($parameter->allowsNull()) {
+ $optCount++;
+ } else {
+ throw new ServiceCreationException("No service of type {$class} found. Make sure the type hint in $method is written correctly and service of this type is registered.");
+ }
+ } else {
+ if ($container instanceof ContainerBuilder) {
+ $res[$num] = '@' . $res[$num];
+ }
+ $optCount = 0;
+ }
+
+ } elseif ($parameter->isOptional()) {
+ // PDO::__construct has optional parameter without default value (and isArray() and allowsNull() returns FALSE)
+ $res[$num] = $parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() : NULL;
+ $optCount++;
+
+ } else {
+ throw new ServiceCreationException("Parameter $parameter has no type hint, so its value must be specified.");
+ }
+ }
+
+ // extra parameters
+ while (array_key_exists(++$num, $arguments)) {
+ $res[$num] = $arguments[$num];
+ unset($arguments[$num]);
+ $optCount = 0;
+ }
+ if ($arguments) {
+ throw new ServiceCreationException("Unable to pass specified arguments to $method.");
+ }
+
+ return $optCount ? array_slice($res, 0, -$optCount) : $res;
+ }
+
+}
diff --git a/libs/Nette/DI/IContainer.php b/libs/Nette/DI/IContainer.php
index 16b9a35..ddb39d7 100644
--- a/libs/Nette/DI/IContainer.php
+++ b/libs/Nette/DI/IContainer.php
@@ -1,54 +1,52 @@
-container = $container;
+ $this->namespace = $namespace . '.';
+ $this->parameters = & $container->parameters[$namespace];
+ }
+
+
+
+ /**
+ * @return object
+ */
+ public function __call($name, $args)
+ {
+ if (substr($name, 0, 6) === 'create') {
+ return call_user_func_array(array(
+ $this->container,
+ Container::getMethodName($this->namespace . substr($name, 6), FALSE)
+ ), $args);
+ }
+ throw new Nette\NotSupportedException;
+ }
+
+
+
+ /**
+ * @return object
+ */
+ public function &__get($name)
+ {
+ $service = $this->container->getService($this->namespace . $name);
+ return $service;
+ }
+
+
+
+ /**
+ * @return void
+ */
+ public function __set($name, $service)
+ {
+ throw new Nette\NotSupportedException;
+ }
+
+
+
+ /**
+ * @return bool
+ */
+ public function __isset($name)
+ {
+ return $this->container->hasService($this->namespace . $name);
+ }
+
+
+
+ /**
+ * @return void
+ */
+ public function __unset($name)
+ {
+ throw new Nette\NotSupportedException;
+ }
+
+}
diff --git a/libs/Nette/DI/ServiceBuilder.php b/libs/Nette/DI/ServiceBuilder.php
deleted file mode 100644
index cc807b1..0000000
--- a/libs/Nette/DI/ServiceBuilder.php
+++ /dev/null
@@ -1,52 +0,0 @@
-class = $class;
- }
-
-
-
- public function getClass()
- {
- return $this->class;
- }
-
-
-
- public function createService(Nette\DI\IContainer $container)
- {
- if (!class_exists($this->class)) {
- throw new Nette\InvalidStateException("Cannot instantiate service, class '$this->class' not found.");
- }
- return new $this->class;
- }
-
-}
diff --git a/libs/Nette/DI/ServiceDefinition.php b/libs/Nette/DI/ServiceDefinition.php
new file mode 100644
index 0000000..eede295
--- /dev/null
+++ b/libs/Nette/DI/ServiceDefinition.php
@@ -0,0 +1,134 @@
+class = $class;
+ if ($args) {
+ $this->setFactory($class, $args);
+ }
+ return $this;
+ }
+
+
+
+ public function setFactory($factory, array $args = array())
+ {
+ $this->factory = new Statement($factory, $args);
+ return $this;
+ }
+
+
+
+ public function setArguments(array $args = array())
+ {
+ if ($this->factory) {
+ $this->factory->arguments = $args;
+ } else {
+ $this->setClass($this->class, $args);
+ }
+ return $this;
+ }
+
+
+
+ public function addSetup($target, $args = NULL)
+ {
+ if (!is_array($args)) {
+ $args = func_get_args();
+ array_shift($args);
+ }
+ $this->setup[] = new Statement($target, $args);
+ return $this;
+ }
+
+
+
+ public function setParameters(array $params)
+ {
+ $this->shared = $this->autowired = FALSE;
+ $this->parameters = $params;
+ return $this;
+ }
+
+
+
+ public function addTag($tag, $attrs = TRUE)
+ {
+ $this->tags[$tag] = $attrs;
+ return $this;
+ }
+
+
+
+ public function setAutowired($on)
+ {
+ $this->autowired = $on;
+ return $this;
+ }
+
+
+
+ public function setShared($on)
+ {
+ $this->shared = (bool) $on;
+ $this->autowired = $this->shared ? $this->autowired : FALSE;
+ return $this;
+ }
+
+
+
+ public function setInternal($on)
+ {
+ $this->internal = (bool) $on;
+ return $this;
+ }
+
+}
diff --git a/libs/Nette/DI/Statement.php b/libs/Nette/DI/Statement.php
new file mode 100644
index 0000000..c114f9e
--- /dev/null
+++ b/libs/Nette/DI/Statement.php
@@ -0,0 +1,39 @@
+entity = $entity;
+ $this->arguments = $arguments;
+ }
+
+}
diff --git a/libs/Nette/DI/exceptions.php b/libs/Nette/DI/exceptions.php
index 9934e41..bc19f91 100644
--- a/libs/Nette/DI/exceptions.php
+++ b/libs/Nette/DI/exceptions.php
@@ -1,32 +1,32 @@
-setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
- $this->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('Nette\Database\Statement', array($this)));
-
- $class = 'Nette\Database\Drivers\\' . $this->getAttribute(PDO::ATTR_DRIVER_NAME) . 'Driver';
- if (class_exists($class)) {
- $this->driver = new $class($this, (array) $options);
- }
-
- $this->preprocessor = new SqlPreprocessor($this);
-
- $this->databaseReflection = new Reflection\DatabaseReflection; // TODO
-
- Diagnostics\ConnectionPanel::initialize($this);
- }
-
-
-
- /** @return ISupplementalDriver */
- public function getSupplementalDriver()
- {
- return $this->driver;
- }
-
-
-
- /**
- * Generates and executes SQL query.
- * @param string statement
- * @param mixed [parameters, ...]
- * @return Statement
- */
- public function query($statement)
- {
- $args = func_get_args();
- return $this->queryArgs(array_shift($args), $args);
- }
-
-
-
- /**
- * Generates and executes SQL query.
- * @param string statement
- * @param mixed [parameters, ...]
- * @return int number of affected rows
- */
- public function exec($statement)
- {
- $args = func_get_args();
- return $this->queryArgs(array_shift($args), $args)->rowCount();
- }
-
-
-
- /**
- * @param string statement
- * @param array
- * @return Statement
- */
- public function queryArgs($statement, $params)
- {
- foreach ($params as $value) {
- if (is_array($value) || is_object($value)) {
- $need = TRUE; break;
- }
- }
- if (isset($need) || strpos($statement, ':') !== FALSE && $this->preprocessor !== NULL) {
- list($statement, $params) = $this->preprocessor->process($statement, $params);
- }
-
- return $this->prepare($statement)->execute($params);
- }
-
-
-
- /********************* shortcuts ****************d*g**/
-
-
-
- /**
- * Shortcut for query()->fetch()
- * @param string statement
- * @param mixed [parameters, ...]
- * @return Row
- */
- public function fetch($args)
- {
- $args = func_get_args();
- return $this->queryArgs(array_shift($args), $args)->fetch();
- }
-
-
-
- /**
- * Shortcut for query()->fetchColumn()
- * @param string statement
- * @param mixed [parameters, ...]
- * @return mixed
- */
- public function fetchColumn($args)
- {
- $args = func_get_args();
- return $this->queryArgs(array_shift($args), $args)->fetchColumn();
- }
-
-
-
- /**
- * Shortcut for query()->fetchPairs()
- * @param string statement
- * @param mixed [parameters, ...]
- * @return array
- */
- public function fetchPairs($args)
- {
- $args = func_get_args();
- return $this->queryArgs(array_shift($args), $args)->fetchPairs();
- }
-
-
-
- /**
- * Shortcut for query()->fetchAll()
- * @param string statement
- * @param mixed [parameters, ...]
- * @return array
- */
- public function fetchAll($args)
- {
- $args = func_get_args();
- return $this->queryArgs(array_shift($args), $args)->fetchAll();
- }
-
-
-
- /********************* selector ****************d*g**/
-
-
-
- /**
- * Creates selector for table.
- * @param string
- * @return Nette\Database\Table\Selection
- */
- public function table($table)
- {
- return new Table\Selection($table, $this);
- }
-
-
-
- /********************* misc ****************d*g**/
-
-
-
- /**
- * Import SQL dump from file - extreme fast.
- * @param string filename
- * @return int count of commands
- */
- public function loadFile($file)
- {
- @set_time_limit(0); // intentionally @
-
- $handle = @fopen($file, 'r'); // intentionally @
- if (!$handle) {
- throw new Nette\FileNotFoundException("Cannot open file '$file'.");
- }
-
- $count = 0;
- $sql = '';
- while (!feof($handle)) {
- $s = fgets($handle);
- $sql .= $s;
- if (substr(rtrim($s), -1) === ';') {
- parent::exec($sql); // native query without logging
- $sql = '';
- $count++;
- }
- }
- fclose($handle);
- return $count;
- }
-
-
-
- /**
- * Returns syntax highlighted SQL command.
- * @param string
- * @return string
- */
- public static function highlightSql($sql)
- {
- static $keywords1 = 'SELECT|UPDATE|INSERT(?:\s+INTO)?|REPLACE(?:\s+INTO)?|DELETE|FROM|WHERE|HAVING|GROUP\s+BY|ORDER\s+BY|LIMIT|OFFSET|SET|VALUES|LEFT\s+JOIN|INNER\s+JOIN|TRUNCATE';
- static $keywords2 = 'ALL|DISTINCT|DISTINCTROW|AS|USING|ON|AND|OR|IN|IS|NOT|NULL|LIKE|TRUE|FALSE';
-
- // insert new lines
- $sql = " $sql ";
- $sql = preg_replace("#(?<=[\\s,(])($keywords1)(?=[\\s,)])#i", "\n\$1", $sql);
-
- // reduce spaces
- $sql = preg_replace('#[ \t]{2,}#', " ", $sql);
-
- $sql = wordwrap($sql, 100);
- $sql = preg_replace("#([ \t]*\r?\n){2,}#", "\n", $sql);
-
- // syntax highlight
- $sql = htmlSpecialChars($sql);
- $sql = preg_replace_callback("#(/\\*.+?\\*/)|(\\*\\*.+?\\*\\*)|(?<=[\\s,(])($keywords1)(?=[\\s,)])|(?<=[\\s,(=])($keywords2)(?=[\\s,)=])#is", function($matches) {
- if (!empty($matches[1])) // comment
- return '' . $matches[1] . '';
-
- if (!empty($matches[2])) // error
- return '' . $matches[2] . '';
-
- if (!empty($matches[3])) // most important keywords
- return '' . $matches[3] . '';
-
- if (!empty($matches[4])) // other keywords
- return '' . $matches[4] . '';
- }, $sql);
-
- return '' . trim($sql) . "
\n";
- }
-
-
-
- /********************* Nette\Object behaviour ****************d*g**/
-
-
-
- /**
- * @return Nette\Reflection\ClassType
- */
- public static function getReflection()
- {
- return new Nette\Reflection\ClassType(get_called_class());
- }
-
-
-
- public function __call($name, $args)
- {
- return ObjectMixin::call($this, $name, $args);
- }
-
-
-
- public function &__get($name)
- {
- return ObjectMixin::get($this, $name);
- }
-
-
-
- public function __set($name, $value)
- {
- return ObjectMixin::set($this, $name, $value);
- }
-
-
-
- public function __isset($name)
- {
- return ObjectMixin::has($this, $name);
- }
-
-
-
- public function __unset($name)
- {
- ObjectMixin::remove($this, $name);
- }
-
-}
+dsn = $dsn, $username, $password, $options);
+ $this->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+ $this->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('Nette\Database\Statement', array($this)));
+
+ $driverClass = $driverClass ?: 'Nette\Database\Drivers\\' . ucfirst(str_replace('sql', 'Sql', $this->getAttribute(PDO::ATTR_DRIVER_NAME))) . 'Driver';
+ $this->driver = new $driverClass($this, (array) $options);
+ $this->preprocessor = new SqlPreprocessor($this);
+ }
+
+
+
+ public function getDsn()
+ {
+ return $this->dsn;
+ }
+
+
+
+ /** @return ISupplementalDriver */
+ public function getSupplementalDriver()
+ {
+ return $this->driver;
+ }
+
+
+
+ /**
+ * Sets database reflection.
+ * @return Connection provides a fluent interface
+ */
+ public function setDatabaseReflection(IReflection $databaseReflection)
+ {
+ $databaseReflection->setConnection($this);
+ $this->databaseReflection = $databaseReflection;
+ return $this;
+ }
+
+
+
+ /** @return IReflection */
+ public function getDatabaseReflection()
+ {
+ if (!$this->databaseReflection) {
+ $this->setDatabaseReflection(new Reflection\ConventionalReflection);
+ }
+ return $this->databaseReflection;
+ }
+
+
+
+ /**
+ * Sets cache storage engine.
+ * @return Connection provides a fluent interface
+ */
+ public function setCacheStorage(Nette\Caching\IStorage $storage = NULL)
+ {
+ $this->cache = $storage ? new Nette\Caching\Cache($storage, 'Nette.Database.' . md5($this->dsn)) : NULL;
+ return $this;
+ }
+
+
+
+ public function getCache()
+ {
+ return $this->cache;
+ }
+
+
+
+ /**
+ * Generates and executes SQL query.
+ * @param string statement
+ * @param mixed [parameters, ...]
+ * @return Statement
+ */
+ public function query($statement)
+ {
+ $args = func_get_args();
+ return $this->queryArgs(array_shift($args), $args);
+ }
+
+
+
+ /**
+ * Generates and executes SQL query.
+ * @param string statement
+ * @param mixed [parameters, ...]
+ * @return int number of affected rows
+ */
+ public function exec($statement)
+ {
+ $args = func_get_args();
+ return $this->queryArgs(array_shift($args), $args)->rowCount();
+ }
+
+
+
+ /**
+ * @param string statement
+ * @param array
+ * @return Statement
+ */
+ public function queryArgs($statement, $params)
+ {
+ foreach ($params as $value) {
+ if (is_array($value) || is_object($value)) {
+ $need = TRUE; break;
+ }
+ }
+ if (isset($need) && $this->preprocessor !== NULL) {
+ list($statement, $params) = $this->preprocessor->process($statement, $params);
+ }
+
+ return $this->prepare($statement)->execute($params);
+ }
+
+
+
+ /********************* shortcuts ****************d*g**/
+
+
+
+ /**
+ * Shortcut for query()->fetch()
+ * @param string statement
+ * @param mixed [parameters, ...]
+ * @return Row
+ */
+ public function fetch($args)
+ {
+ $args = func_get_args();
+ return $this->queryArgs(array_shift($args), $args)->fetch();
+ }
+
+
+
+ /**
+ * Shortcut for query()->fetchColumn()
+ * @param string statement
+ * @param mixed [parameters, ...]
+ * @return mixed
+ */
+ public function fetchColumn($args)
+ {
+ $args = func_get_args();
+ return $this->queryArgs(array_shift($args), $args)->fetchColumn();
+ }
+
+
+
+ /**
+ * Shortcut for query()->fetchPairs()
+ * @param string statement
+ * @param mixed [parameters, ...]
+ * @return array
+ */
+ public function fetchPairs($args)
+ {
+ $args = func_get_args();
+ return $this->queryArgs(array_shift($args), $args)->fetchPairs();
+ }
+
+
+
+ /**
+ * Shortcut for query()->fetchAll()
+ * @param string statement
+ * @param mixed [parameters, ...]
+ * @return array
+ */
+ public function fetchAll($args)
+ {
+ $args = func_get_args();
+ return $this->queryArgs(array_shift($args), $args)->fetchAll();
+ }
+
+
+
+ /********************* selector ****************d*g**/
+
+
+
+ /**
+ * Creates selector for table.
+ * @param string
+ * @return Nette\Database\Table\Selection
+ */
+ public function table($table)
+ {
+ return new Table\Selection($table, $this);
+ }
+
+
+
+ /********************* Nette\Object behaviour ****************d*g**/
+
+
+
+ /**
+ * @return Nette\Reflection\ClassType
+ */
+ public /**/static/**/ function getReflection()
+ {
+ return new Nette\Reflection\ClassType(/*5.2*$this*//**/get_called_class()/**/);
+ }
+
+
+
+ public function __call($name, $args)
+ {
+ return ObjectMixin::call($this, $name, $args);
+ }
+
+
+
+ public function &__get($name)
+ {
+ return ObjectMixin::get($this, $name);
+ }
+
+
+
+ public function __set($name, $value)
+ {
+ return ObjectMixin::set($this, $name, $value);
+ }
+
+
+
+ public function __isset($name)
+ {
+ return ObjectMixin::has($this, $name);
+ }
+
+
+
+ public function __unset($name)
+ {
+ ObjectMixin::remove($this, $name);
+ }
+
+}
diff --git a/libs/Nette/Database/Diagnostics/ConnectionPanel.php b/libs/Nette/Database/Diagnostics/ConnectionPanel.php
index 6979b5b..316741e 100644
--- a/libs/Nette/Database/Diagnostics/ConnectionPanel.php
+++ b/libs/Nette/Database/Diagnostics/ConnectionPanel.php
@@ -1,162 +1,159 @@
-onQuery[] = callback($panel, 'logQuery');
- Debugger::$bar->addPanel($panel);
- Debugger::$blueScreen->addPanel(callback($panel, 'renderException'), __CLASS__);
- }
- }
-
-
-
- public function logQuery(Nette\Database\Statement $result, array $params = NULL)
- {
- if ($this->disabled) {
- return;
- }
- $source = NULL;
- foreach (debug_backtrace(FALSE) as $row) {
- if (isset($row['file']) && is_file($row['file']) && strpos($row['file'], NETTE_DIR . DIRECTORY_SEPARATOR) !== 0) {
- $source = array($row['file'], (int) $row['line']);
- break;
- }
- }
- $this->totalTime += $result->time;
- $this->queries[] = array($result->queryString, $params, $result->time, $result->rowCount(), $result->getConnection(), $source);
- }
-
-
-
- public function renderException($e)
- {
- if ($e instanceof \PDOException && isset($e->queryString)) {
- return array(
- 'tab' => 'SQL',
- 'panel' => Connection::highlightSql($e->queryString),
- );
- }
- }
-
-
-
- public function getTab()
- {
- return ''
- . '
'
- . count($this->queries) . ' queries'
- . ($this->totalTime ? ' / ' . sprintf('%0.1f', $this->totalTime * 1000) . 'ms' : '')
- . '';
- }
-
-
-
- public function getPanel()
- {
- $this->disabled = TRUE;
- $s = '';
- $h = 'htmlSpecialChars';
- foreach ($this->queries as $i => $query) {
- list($sql, $params, $time, $rows, $connection, $source) = $query;
-
- $explain = NULL; // EXPLAIN is called here to work SELECT FOUND_ROWS()
- if ($this->explain && preg_match('#\s*SELECT\s#iA', $sql)) {
- try {
- $explain = $connection->queryArgs('EXPLAIN ' . $sql, $params)->fetchAll();
- } catch (\PDOException $e) {}
- }
-
- $s .= '' . sprintf('%0.3f', $time * 1000);
- if ($explain) {
- static $counter;
- $counter++;
- $s .= " explain ►";
- }
-
- $s .= ' | ' . Connection::highlightSql(Nette\Utils\Strings::truncate($sql, self::$maxLength));
- if ($explain) {
- $s .= "";
- foreach ($explain[0] as $col => $foo) {
- $s .= "| {$h($col)} | ";
- }
- $s .= " ";
- foreach ($explain as $row) {
- $s .= "";
- foreach ($row as $col) {
- $s .= "| {$h($col)} | ";
- }
- $s .= " ";
- }
- $s .= " ";
- }
- if ($source) {
- $s .= Nette\Diagnostics\Helpers::editorLink($source[0], $source[1])->class('nette-DbConnectionPanel-source');
- }
-
- $s .= ' | ';
- foreach ($params as $param) {
- $s .= Debugger::dump($param, TRUE);
- }
-
- $s .= ' | ' . $rows . ' |
';
- }
-
- return empty($this->queries) ? '' :
- '
- Queries: ' . count($this->queries) . ($this->totalTime ? ', time: ' . sprintf('%0.3f', $this->totalTime * 1000) . ' ms' : '') . '
-
-
- | Time ms | SQL Statement | Params | Rows |
' . $s . '
-
-
';
- }
-
-}
+disabled) {
+ return;
+ }
+ $source = NULL;
+ foreach (/*5.2*PHP_VERSION_ID < 50205 ? debug_backtrace() : */debug_backtrace(FALSE) as $row) {
+ if (isset($row['file']) && is_file($row['file']) && strpos($row['file'], NETTE_DIR . DIRECTORY_SEPARATOR) !== 0) {
+ if (isset($row['function']) && strpos($row['function'], 'call_user_func') === 0) continue;
+ if (isset($row['class']) && is_subclass_of($row['class'], '\\Nette\\Database\\Connection')) continue;
+ $source = array($row['file'], (int) $row['line']);
+ break;
+ }
+ }
+ $this->totalTime += $result->getTime();
+ $this->queries[] = array($result->queryString, $params, $result->getTime(), $result->rowCount(), $result->getConnection(), $source);
+ }
+
+
+
+ public static function renderException($e)
+ {
+ if (!$e instanceof \PDOException) {
+ return;
+ }
+ if (isset($e->queryString)) {
+ $sql = $e->queryString;
+
+ } elseif ($item = Nette\Diagnostics\Helpers::findTrace($e->getTrace(), 'PDO::prepare')) {
+ $sql = $item['args'][0];
+ }
+ return isset($sql) ? array(
+ 'tab' => 'SQL',
+ 'panel' => Helpers::dumpSql($sql),
+ ) : NULL;
+ }
+
+
+
+ public function getTab()
+ {
+ return ''
+ . '
'
+ . count($this->queries) . ' queries'
+ . ($this->totalTime ? ' / ' . sprintf('%0.1f', $this->totalTime * 1000) . 'ms' : '')
+ . '';
+ }
+
+
+
+ public function getPanel()
+ {
+ $this->disabled = TRUE;
+ $s = '';
+ $h = 'htmlSpecialChars';
+ foreach ($this->queries as $i => $query) {
+ list($sql, $params, $time, $rows, $connection, $source) = $query;
+
+ $explain = NULL; // EXPLAIN is called here to work SELECT FOUND_ROWS()
+ if ($this->explain && preg_match('#\s*\(?\s*SELECT\s#iA', $sql)) {
+ try {
+ $cmd = is_string($this->explain) ? $this->explain : 'EXPLAIN';
+ $explain = $connection->queryArgs("$cmd $sql", $params)->fetchAll();
+ } catch (\PDOException $e) {}
+ }
+
+ $s .= '' . sprintf('%0.3f', $time * 1000);
+ if ($explain) {
+ static $counter;
+ $counter++;
+ $s .= " explain ►";
+ }
+
+ $s .= ' | ' . Helpers::dumpSql(self::$maxLength ? Nette\Utils\Strings::truncate($sql, self::$maxLength) : $sql);
+ if ($explain) {
+ $s .= "";
+ foreach ($explain[0] as $col => $foo) {
+ $s .= "| {$h($col)} | ";
+ }
+ $s .= " ";
+ foreach ($explain as $row) {
+ $s .= "";
+ foreach ($row as $col) {
+ $s .= "| {$h($col)} | ";
+ }
+ $s .= " ";
+ }
+ $s .= " ";
+ }
+ if ($source) {
+ $s .= Nette\Diagnostics\Helpers::editorLink($source[0], $source[1])->class('nette-DbConnectionPanel-source');
+ }
+
+ $s .= ' | ';
+ foreach ($params as $param) {
+ $s .= Debugger::dump($param, TRUE);
+ }
+
+ $s .= ' | ' . $rows . ' |
';
+ }
+
+ return empty($this->queries) ? '' :
+ '
+ Queries: ' . count($this->queries) . ($this->totalTime ? ', time: ' . sprintf('%0.3f', $this->totalTime * 1000) . ' ms' : '') . '
+
+
+ | Time ms | SQL Statement | Params | Rows |
' . $s . '
+
+
';
+ }
+
+}
diff --git a/libs/Nette/Database/Drivers/MsSqlDriver.php b/libs/Nette/Database/Drivers/MsSqlDriver.php
index 03967e3..fad898d 100644
--- a/libs/Nette/Database/Drivers/MsSqlDriver.php
+++ b/libs/Nette/Database/Drivers/MsSqlDriver.php
@@ -1,101 +1,162 @@
- TRUE);
-
- /** @var Nette\Database\Connection */
- private $connection;
-
-
-
- public function __construct(Nette\Database\Connection $connection, array $options)
- {
- $this->connection = $connection;
- }
-
-
-
- /********************* SQL ****************d*g**/
-
-
-
- /**
- * Delimites identifier for use in a SQL statement.
- */
- public function delimite($name)
- {
- // @see http://msdn.microsoft.com/en-us/library/ms176027.aspx
- return '[' . str_replace(array('[', ']'), array('[[', ']]'), $name) . ']';
- }
-
-
-
- /**
- * Formats date-time for use in a SQL statement.
- */
- public function formatDateTime(\DateTime $value)
- {
- return $value->format("'Y-m-d H:i:s'");
- }
-
-
-
- /**
- * Encodes string for use in a LIKE statement.
- */
- public function formatLike($value, $pos)
- {
- $value = strtr($value, array("'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]'));
- return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
- }
-
-
-
- /**
- * Injects LIMIT/OFFSET to the SQL query.
- */
- public function applyLimit(&$sql, $limit, $offset)
- {
- // offset support is missing
- if ($limit >= 0) {
- $sql = 'SELECT TOP ' . (int) $limit . ' * FROM (' . $sql . ') t';
- }
-
- if ($offset) {
- throw new Nette\NotImplementedException('Offset is not implemented.');
- }
- }
-
-
-
- /**
- * Normalizes result row.
- */
- public function normalizeRow($row, $statement)
- {
- return $row;
- }
-
-}
+connection = $connection;
+ }
+
+
+
+ /********************* SQL ****************d*g**/
+
+
+
+ /**
+ * Delimites identifier for use in a SQL statement.
+ */
+ public function delimite($name)
+ {
+ // @see http://msdn.microsoft.com/en-us/library/ms176027.aspx
+ return '[' . str_replace(array('[', ']'), array('[[', ']]'), $name) . ']';
+ }
+
+
+
+ /**
+ * Formats boolean for use in a SQL statement.
+ */
+ public function formatBool($value)
+ {
+ return $value ? '1' : '0';
+ }
+
+
+
+ /**
+ * Formats date-time for use in a SQL statement.
+ */
+ public function formatDateTime(\DateTime $value)
+ {
+ return $value->format("'Y-m-d H:i:s'");
+ }
+
+
+
+ /**
+ * Encodes string for use in a LIKE statement.
+ */
+ public function formatLike($value, $pos)
+ {
+ $value = strtr($value, array("'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]'));
+ return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
+ }
+
+
+
+ /**
+ * Injects LIMIT/OFFSET to the SQL query.
+ */
+ public function applyLimit(&$sql, $limit, $offset)
+ {
+ // offset support is missing
+ if ($limit >= 0) {
+ $sql = 'SELECT TOP ' . (int) $limit . ' * FROM (' . $sql . ') t';
+ }
+
+ if ($offset) {
+ throw new Nette\NotImplementedException('Offset is not implemented.');
+ }
+ }
+
+
+
+ /**
+ * Normalizes result row.
+ */
+ public function normalizeRow($row, $statement)
+ {
+ return $row;
+ }
+
+
+
+ /********************* reflection ****************d*g**/
+
+
+
+ /**
+ * Returns list of tables.
+ */
+ public function getTables()
+ {
+ throw new NotImplementedException;
+ }
+
+
+
+ /**
+ * Returns metadata for all columns in a table.
+ */
+ public function getColumns($table)
+ {
+ throw new NotImplementedException;
+ }
+
+
+
+ /**
+ * Returns metadata for all indexes in a table.
+ */
+ public function getIndexes($table)
+ {
+ throw new NotImplementedException;
+ }
+
+
+
+ /**
+ * Returns metadata for all foreign keys in a table.
+ */
+ public function getForeignKeys($table)
+ {
+ throw new NotImplementedException;
+ }
+
+
+
+ /**
+ * @return bool
+ */
+ public function isSupported($item)
+ {
+ return $item === self::SUPPORT_COLUMNS_META;
+ }
+
+}
diff --git a/libs/Nette/Database/Drivers/MySqlDriver.php b/libs/Nette/Database/Drivers/MySqlDriver.php
index 0170546..5441d59 100644
--- a/libs/Nette/Database/Drivers/MySqlDriver.php
+++ b/libs/Nette/Database/Drivers/MySqlDriver.php
@@ -1,111 +1,233 @@
- TRUE);
-
- /** @var Nette\Database\Connection */
- private $connection;
-
-
-
- /**
- * Driver options:
- * - charset => character encoding to set (default is utf8)
- * - sqlmode => see http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html
- */
- public function __construct(Nette\Database\Connection $connection, array $options)
- {
- $this->connection = $connection;
- $charset = isset($options['charset']) ? $options['charset'] : 'utf8';
- if ($charset) {
- $connection->exec("SET NAMES '$charset'");
- }
- if (isset($options['sqlmode'])) {
- $connection->exec("SET sql_mode='$options[sqlmode]'");
- }
- $connection->exec("SET time_zone='" . date('P') . "'");
- }
-
-
-
- /********************* SQL ****************d*g**/
-
-
-
- /**
- * Delimites identifier for use in a SQL statement.
- */
- public function delimite($name)
- {
- // @see http://dev.mysql.com/doc/refman/5.0/en/identifiers.html
- return '`' . str_replace('`', '``', $name) . '`';
- }
-
-
-
- /**
- * Formats date-time for use in a SQL statement.
- */
- public function formatDateTime(\DateTime $value)
- {
- return $value->format("'Y-m-d H:i:s'");
- }
-
-
-
- /**
- * Encodes string for use in a LIKE statement.
- */
- public function formatLike($value, $pos)
- {
- $value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\n\r\\'%_");
- return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
- }
-
-
-
- /**
- * Injects LIMIT/OFFSET to the SQL query.
- */
- public function applyLimit(&$sql, $limit, $offset)
- {
- if ($limit >= 0 || $offset > 0) {
- // see http://dev.mysql.com/doc/refman/5.0/en/select.html
- $sql .= ' LIMIT ' . ($limit < 0 ? '18446744073709551615' : (int) $limit)
- . ($offset > 0 ? ' OFFSET ' . (int) $offset : '');
- }
- }
-
-
-
- /**
- * Normalizes result row.
- */
- public function normalizeRow($row, $statement)
- {
- return $row;
- }
-
-}
+ character encoding to set (default is utf8)
+ * - sqlmode => see http://dev.mysql.com/doc/refman/5.0/en/server-sql-mode.html
+ */
+ public function __construct(Nette\Database\Connection $connection, array $options)
+ {
+ $this->connection = $connection;
+ $charset = isset($options['charset']) ? $options['charset'] : 'utf8';
+ if ($charset) {
+ $connection->exec("SET NAMES '$charset'");
+ }
+ if (isset($options['sqlmode'])) {
+ $connection->exec("SET sql_mode='$options[sqlmode]'");
+ }
+ $connection->exec("SET time_zone='" . date('P') . "'");
+ }
+
+
+
+ /********************* SQL ****************d*g**/
+
+
+
+ /**
+ * Delimites identifier for use in a SQL statement.
+ */
+ public function delimite($name)
+ {
+ // @see http://dev.mysql.com/doc/refman/5.0/en/identifiers.html
+ return '`' . str_replace('`', '``', $name) . '`';
+ }
+
+
+
+ /**
+ * Formats boolean for use in a SQL statement.
+ */
+ public function formatBool($value)
+ {
+ return $value ? '1' : '0';
+ }
+
+
+
+ /**
+ * Formats date-time for use in a SQL statement.
+ */
+ public function formatDateTime(\DateTime $value)
+ {
+ return $value->format("'Y-m-d H:i:s'");
+ }
+
+
+
+ /**
+ * Encodes string for use in a LIKE statement.
+ */
+ public function formatLike($value, $pos)
+ {
+ $value = addcslashes(str_replace('\\', '\\\\', $value), "\x00\n\r\\'%_");
+ return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
+ }
+
+
+
+ /**
+ * Injects LIMIT/OFFSET to the SQL query.
+ */
+ public function applyLimit(&$sql, $limit, $offset)
+ {
+ if ($limit >= 0 || $offset > 0) {
+ // see http://dev.mysql.com/doc/refman/5.0/en/select.html
+ $sql .= ' LIMIT ' . ($limit < 0 ? '18446744073709551615' : (int) $limit)
+ . ($offset > 0 ? ' OFFSET ' . (int) $offset : '');
+ }
+ }
+
+
+
+ /**
+ * Normalizes result row.
+ */
+ public function normalizeRow($row, $statement)
+ {
+ return $row;
+ }
+
+
+
+ /********************* reflection ****************d*g**/
+
+
+
+ /**
+ * Returns list of tables.
+ */
+ public function getTables()
+ {
+ /*$this->connection->query("
+ SELECT TABLE_NAME as name, TABLE_TYPE = 'VIEW' as view
+ FROM INFORMATION_SCHEMA.TABLES
+ WHERE TABLE_SCHEMA = DATABASE()
+ ");*/
+ $tables = array();
+ foreach ($this->connection->query('SHOW FULL TABLES') as $row) {
+ $tables[] = array(
+ 'name' => $row[0],
+ 'view' => isset($row[1]) && $row[1] === 'VIEW',
+ );
+ }
+ return $tables;
+ }
+
+
+
+ /**
+ * Returns metadata for all columns in a table.
+ */
+ public function getColumns($table)
+ {
+ /*$this->connection->query("
+ SELECT *
+ FROM INFORMATION_SCHEMA.COLUMNS
+ WHERE TABLE_NAME = {$this->connection->quote($table)} AND TABLE_SCHEMA = DATABASE()
+ ");*/
+ $columns = array();
+ foreach ($this->connection->query('SHOW FULL COLUMNS FROM ' . $this->delimite($table)) as $row) {
+ $type = explode('(', $row['Type']);
+ $columns[] = array(
+ 'name' => $row['Field'],
+ 'table' => $table,
+ 'nativetype' => strtoupper($type[0]),
+ 'size' => isset($type[1]) ? (int) $type[1] : NULL,
+ 'unsigned' => (bool) strstr($row['Type'], 'unsigned'),
+ 'nullable' => $row['Null'] === 'YES',
+ 'default' => $row['Default'],
+ 'autoincrement' => $row['Extra'] === 'auto_increment',
+ 'primary' => $row['Key'] === 'PRI',
+ 'vendor' => (array) $row,
+ );
+ }
+ return $columns;
+ }
+
+
+
+ /**
+ * Returns metadata for all indexes in a table.
+ */
+ public function getIndexes($table)
+ {
+ /*$this->connection->query("
+ SELECT *
+ FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
+ WHERE TABLE_NAME = {$this->connection->quote($table)} AND TABLE_SCHEMA = DATABASE()
+ AND REFERENCED_COLUMN_NAME IS NULL
+ ");*/
+ $indexes = array();
+ foreach ($this->connection->query('SHOW INDEX FROM ' . $this->delimite($table)) as $row) {
+ $indexes[$row['Key_name']]['name'] = $row['Key_name'];
+ $indexes[$row['Key_name']]['unique'] = !$row['Non_unique'];
+ $indexes[$row['Key_name']]['primary'] = $row['Key_name'] === 'PRIMARY';
+ $indexes[$row['Key_name']]['columns'][$row['Seq_in_index'] - 1] = $row['Column_name'];
+ }
+ return array_values($indexes);
+ }
+
+
+
+ /**
+ * Returns metadata for all foreign keys in a table.
+ */
+ public function getForeignKeys($table)
+ {
+ $keys = array();
+ $query = 'SELECT CONSTRAINT_NAME, COLUMN_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME FROM information_schema.KEY_COLUMN_USAGE '
+ . 'WHERE TABLE_SCHEMA = DATABASE() AND REFERENCED_TABLE_NAME IS NOT NULL AND TABLE_NAME = ' . $this->connection->quote($table);
+
+ foreach ($this->connection->query($query) as $id => $row) {
+ $keys[$id]['name'] = $row['CONSTRAINT_NAME']; // foreign key name
+ $keys[$id]['local'] = $row['COLUMN_NAME']; // local columns
+ $keys[$id]['table'] = $row['REFERENCED_TABLE_NAME']; // referenced table
+ $keys[$id]['foreign'] = $row['REFERENCED_COLUMN_NAME']; // referenced columns
+ }
+
+ return array_values($keys);
+ }
+
+
+
+ /**
+ * @return bool
+ */
+ public function isSupported($item)
+ {
+ return $item === self::SUPPORT_COLUMNS_META || $item == self::SUPPORT_SELECT_UNGROUPED_COLUMNS;
+ }
+
+}
diff --git a/libs/Nette/Database/Drivers/OciDriver.php b/libs/Nette/Database/Drivers/OciDriver.php
index 57ddcdb..81db616 100644
--- a/libs/Nette/Database/Drivers/OciDriver.php
+++ b/libs/Nette/Database/Drivers/OciDriver.php
@@ -1,105 +1,175 @@
- TRUE);
-
- /** @var Nette\Database\Connection */
- private $connection;
-
- /** @var string Datetime format */
- private $fmtDateTime;
-
-
-
- public function __construct(Nette\Database\Connection $connection, array $options)
- {
- $this->connection = $connection;
- $this->fmtDateTime = isset($options['formatDateTime']) ? $options['formatDateTime'] : 'U';
- }
-
-
-
- /********************* SQL ****************d*g**/
-
-
-
- /**
- * Delimites identifier for use in a SQL statement.
- */
- public function delimite($name)
- {
- // @see http://download.oracle.com/docs/cd/B10500_01/server.920/a96540/sql_elements9a.htm
- return '"' . str_replace('"', '""', $name) . '"';
- }
-
-
-
- /**
- * Formats date-time for use in a SQL statement.
- */
- public function formatDateTime(\DateTime $value)
- {
- return $value->format($this->fmtDateTime);
- }
-
-
-
- /**
- * Encodes string for use in a LIKE statement.
- */
- public function formatLike($value, $pos)
- {
- throw new Nette\NotImplementedException;
- }
-
-
-
- /**
- * Injects LIMIT/OFFSET to the SQL query.
- */
- public function applyLimit(&$sql, $limit, $offset)
- {
- if ($offset > 0) {
- // see http://www.oracle.com/technology/oramag/oracle/06-sep/o56asktom.html
- $sql = 'SELECT * FROM (SELECT t.*, ROWNUM AS "__rnum" FROM (' . $sql . ') t '
- . ($limit >= 0 ? 'WHERE ROWNUM <= ' . ((int) $offset + (int) $limit) : '')
- . ') WHERE "__rnum" > '. (int) $offset;
-
- } elseif ($limit >= 0) {
- $sql = 'SELECT * FROM (' . $sql . ') WHERE ROWNUM <= ' . (int) $limit;
- }
- }
-
-
-
- /**
- * Normalizes result row.
- */
- public function normalizeRow($row, $statement)
- {
- return $row;
- }
-
-}
+connection = $connection;
+ $this->fmtDateTime = isset($options['formatDateTime']) ? $options['formatDateTime'] : 'U';
+ }
+
+
+
+ /********************* SQL ****************d*g**/
+
+
+
+ /**
+ * Delimites identifier for use in a SQL statement.
+ */
+ public function delimite($name)
+ {
+ // @see http://download.oracle.com/docs/cd/B10500_01/server.920/a96540/sql_elements9a.htm
+ return '"' . str_replace('"', '""', $name) . '"';
+ }
+
+
+
+ /**
+ * Formats boolean for use in a SQL statement.
+ */
+ public function formatBool($value)
+ {
+ return $value ? '1' : '0';
+ }
+
+
+
+ /**
+ * Formats date-time for use in a SQL statement.
+ */
+ public function formatDateTime(\DateTime $value)
+ {
+ return $value->format($this->fmtDateTime);
+ }
+
+
+
+ /**
+ * Encodes string for use in a LIKE statement.
+ */
+ public function formatLike($value, $pos)
+ {
+ throw new Nette\NotImplementedException;
+ }
+
+
+
+ /**
+ * Injects LIMIT/OFFSET to the SQL query.
+ */
+ public function applyLimit(&$sql, $limit, $offset)
+ {
+ if ($offset > 0) {
+ // see http://www.oracle.com/technology/oramag/oracle/06-sep/o56asktom.html
+ $sql = 'SELECT * FROM (SELECT t.*, ROWNUM AS "__rnum" FROM (' . $sql . ') t '
+ . ($limit >= 0 ? 'WHERE ROWNUM <= ' . ((int) $offset + (int) $limit) : '')
+ . ') WHERE "__rnum" > '. (int) $offset;
+
+ } elseif ($limit >= 0) {
+ $sql = 'SELECT * FROM (' . $sql . ') WHERE ROWNUM <= ' . (int) $limit;
+ }
+ }
+
+
+
+ /**
+ * Normalizes result row.
+ */
+ public function normalizeRow($row, $statement)
+ {
+ return $row;
+ }
+
+
+
+ /********************* reflection ****************d*g**/
+
+
+
+ /**
+ * Returns list of tables.
+ */
+ public function getTables()
+ {
+ $tables = array();
+ foreach ($this->connection->query('SELECT * FROM cat') as $row) {
+ if ($row[1] === 'TABLE' || $row[1] === 'VIEW') {
+ $tables[] = array(
+ 'name' => $row[0],
+ 'view' => $row[1] === 'VIEW',
+ );
+ }
+ }
+ return $tables;
+ }
+
+
+
+ /**
+ * Returns metadata for all columns in a table.
+ */
+ public function getColumns($table)
+ {
+ throw new NotImplementedException;
+ }
+
+
+
+ /**
+ * Returns metadata for all indexes in a table.
+ */
+ public function getIndexes($table)
+ {
+ throw new NotImplementedException;
+ }
+
+
+
+ /**
+ * Returns metadata for all foreign keys in a table.
+ */
+ public function getForeignKeys($table)
+ {
+ throw new NotImplementedException;
+ }
+
+
+
+ /**
+ * @return bool
+ */
+ public function isSupported($item)
+ {
+ return $item === self::SUPPORT_COLUMNS_META || $item === self::SUPPORT_SEQUENCE;
+ }
+
+}
diff --git a/libs/Nette/Database/Drivers/OdbcDriver.php b/libs/Nette/Database/Drivers/OdbcDriver.php
index 64676e6..743c759 100644
--- a/libs/Nette/Database/Drivers/OdbcDriver.php
+++ b/libs/Nette/Database/Drivers/OdbcDriver.php
@@ -1,100 +1,161 @@
- TRUE);
-
- /** @var Nette\Database\Connection */
- private $connection;
-
-
-
- public function __construct(Nette\Database\Connection $connection, array $options)
- {
- $this->connection = $connection;
- }
-
-
-
- /********************* SQL ****************d*g**/
-
-
-
- /**
- * Delimites identifier for use in a SQL statement.
- */
- public function delimite($name)
- {
- return '[' . str_replace(array('[', ']'), array('[[', ']]'), $name) . ']';
- }
-
-
-
- /**
- * Formats date-time for use in a SQL statement.
- */
- public function formatDateTime(\DateTime $value)
- {
- return $value->format("#m/d/Y H:i:s#");
- }
-
-
-
- /**
- * Encodes string for use in a LIKE statement.
- */
- public function formatLike($value, $pos)
- {
- $value = strtr($value, array("'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]'));
- return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
- }
-
-
-
- /**
- * Injects LIMIT/OFFSET to the SQL query.
- */
- public function applyLimit(&$sql, $limit, $offset)
- {
- // offset support is missing
- if ($limit >= 0) {
- $sql = 'SELECT TOP ' . (int) $limit . ' * FROM (' . $sql . ')';
- }
-
- if ($offset) {
- throw new Nette\InvalidArgumentException('Offset is not implemented in driver odbc.');
- }
- }
-
-
-
- /**
- * Normalizes result row.
- */
- public function normalizeRow($row, $statement)
- {
- return $row;
- }
-
-}
+connection = $connection;
+ }
+
+
+
+ /********************* SQL ****************d*g**/
+
+
+
+ /**
+ * Delimites identifier for use in a SQL statement.
+ */
+ public function delimite($name)
+ {
+ return '[' . str_replace(array('[', ']'), array('[[', ']]'), $name) . ']';
+ }
+
+
+
+ /**
+ * Formats boolean for use in a SQL statement.
+ */
+ public function formatBool($value)
+ {
+ return $value ? '1' : '0';
+ }
+
+
+
+ /**
+ * Formats date-time for use in a SQL statement.
+ */
+ public function formatDateTime(\DateTime $value)
+ {
+ return $value->format("#m/d/Y H:i:s#");
+ }
+
+
+
+ /**
+ * Encodes string for use in a LIKE statement.
+ */
+ public function formatLike($value, $pos)
+ {
+ $value = strtr($value, array("'" => "''", '%' => '[%]', '_' => '[_]', '[' => '[[]'));
+ return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
+ }
+
+
+
+ /**
+ * Injects LIMIT/OFFSET to the SQL query.
+ */
+ public function applyLimit(&$sql, $limit, $offset)
+ {
+ // offset support is missing
+ if ($limit >= 0) {
+ $sql = 'SELECT TOP ' . (int) $limit . ' * FROM (' . $sql . ')';
+ }
+
+ if ($offset) {
+ throw new Nette\InvalidArgumentException('Offset is not implemented in driver odbc.');
+ }
+ }
+
+
+
+ /**
+ * Normalizes result row.
+ */
+ public function normalizeRow($row, $statement)
+ {
+ return $row;
+ }
+
+
+
+ /********************* reflection ****************d*g**/
+
+
+
+ /**
+ * Returns list of tables.
+ */
+ public function getTables()
+ {
+ throw new NotImplementedException;
+ }
+
+
+
+ /**
+ * Returns metadata for all columns in a table.
+ */
+ public function getColumns($table)
+ {
+ throw new NotImplementedException;
+ }
+
+
+
+ /**
+ * Returns metadata for all indexes in a table.
+ */
+ public function getIndexes($table)
+ {
+ throw new NotImplementedException;
+ }
+
+
+
+ /**
+ * Returns metadata for all foreign keys in a table.
+ */
+ public function getForeignKeys($table)
+ {
+ throw new NotImplementedException;
+ }
+
+
+
+ /**
+ * @return bool
+ */
+ public function isSupported($item)
+ {
+ return $item === self::SUPPORT_COLUMNS_META;
+ }
+
+}
diff --git a/libs/Nette/Database/Drivers/PgSqlDriver.php b/libs/Nette/Database/Drivers/PgSqlDriver.php
index 207aa71..d7efd91 100644
--- a/libs/Nette/Database/Drivers/PgSqlDriver.php
+++ b/libs/Nette/Database/Drivers/PgSqlDriver.php
@@ -1,97 +1,254 @@
- TRUE);
-
- /** @var Nette\Database\Connection */
- private $connection;
-
-
-
- public function __construct(Nette\Database\Connection $connection, array $options)
- {
- $this->connection = $connection;
- }
-
-
-
- /********************* SQL ****************d*g**/
-
-
-
- /**
- * Delimites identifier for use in a SQL statement.
- */
- public function delimite($name)
- {
- // @see http://www.postgresql.org/docs/8.2/static/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
- return '"' . str_replace('"', '""', $name) . '"';
- }
-
-
-
- /**
- * Formats date-time for use in a SQL statement.
- */
- public function formatDateTime(\DateTime $value)
- {
- return $value->format("'Y-m-d H:i:s'");
- }
-
-
-
- /**
- * Encodes string for use in a LIKE statement.
- */
- public function formatLike($value, $pos)
- {
- throw new Nette\NotImplementedException;
- }
-
-
-
- /**
- * Injects LIMIT/OFFSET to the SQL query.
- */
- public function applyLimit(&$sql, $limit, $offset)
- {
- if ($limit >= 0)
- $sql .= ' LIMIT ' . (int) $limit;
-
- if ($offset > 0)
- $sql .= ' OFFSET ' . (int) $offset;
- }
-
-
-
- /**
- * Normalizes result row.
- */
- public function normalizeRow($row, $statement)
- {
- return $row;
- }
-
-}
+connection = $connection;
+ }
+
+
+
+ /********************* SQL ****************d*g**/
+
+
+
+ /**
+ * Delimites identifier for use in a SQL statement.
+ */
+ public function delimite($name)
+ {
+ // @see http://www.postgresql.org/docs/8.2/static/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
+ return '"' . str_replace('"', '""', $name) . '"';
+ }
+
+
+
+ /**
+ * Formats boolean for use in a SQL statement.
+ */
+ public function formatBool($value)
+ {
+ return $value ? 'TRUE' : 'FALSE';
+ }
+
+
+
+ /**
+ * Formats date-time for use in a SQL statement.
+ */
+ public function formatDateTime(\DateTime $value)
+ {
+ return $value->format("'Y-m-d H:i:s'");
+ }
+
+
+
+ /**
+ * Encodes string for use in a LIKE statement.
+ */
+ public function formatLike($value, $pos)
+ {
+ $value = strtr($value, array("'" => "''", '\\' => '\\\\', '%' => '\\\\%', '_' => '\\\\_'));
+ return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'");
+ }
+
+
+
+ /**
+ * Injects LIMIT/OFFSET to the SQL query.
+ */
+ public function applyLimit(&$sql, $limit, $offset)
+ {
+ if ($limit >= 0)
+ $sql .= ' LIMIT ' . (int) $limit;
+
+ if ($offset > 0)
+ $sql .= ' OFFSET ' . (int) $offset;
+ }
+
+
+
+ /**
+ * Normalizes result row.
+ */
+ public function normalizeRow($row, $statement)
+ {
+ return $row;
+ }
+
+
+
+ /********************* reflection ****************d*g**/
+
+
+
+ /**
+ * Returns list of tables.
+ */
+ public function getTables()
+ {
+ $tables = array();
+ foreach ($this->connection->query("
+ SELECT
+ table_name AS name,
+ table_type = 'VIEW' AS view
+ FROM
+ information_schema.tables
+ WHERE
+ table_schema = current_schema()
+ ") as $row) {
+ $tables[] = (array) $row;
+ }
+
+ return $tables;
+ }
+
+
+
+ /**
+ * Returns metadata for all columns in a table.
+ */
+ public function getColumns($table)
+ {
+ $columns = array();
+ foreach ($this->connection->query("
+ SELECT
+ c.column_name AS name,
+ c.table_name AS table,
+ upper(c.udt_name) AS nativetype,
+ greatest(c.character_maximum_length, c.numeric_precision) AS size,
+ FALSE AS unsigned,
+ c.is_nullable = 'YES' AS nullable,
+ c.column_default AS default,
+ coalesce(tc.constraint_type = 'PRIMARY KEY', FALSE) AND strpos(c.column_default, 'nextval') = 1 AS autoincrement,
+ coalesce(tc.constraint_type = 'PRIMARY KEY', FALSE) AS primary,
+ substring(c.column_default from 'nextval[(]''\"?([^''\"]+)') AS sequence
+ FROM
+ information_schema.columns AS c
+ LEFT JOIN information_schema.constraint_column_usage AS ccu USING(table_catalog, table_schema, table_name, column_name)
+ LEFT JOIN information_schema.table_constraints AS tc USING(constraint_catalog, constraint_schema, constraint_name)
+ WHERE
+ c.table_name = {$this->connection->quote($table)}
+ AND
+ c.table_schema = current_schema()
+ AND
+ (tc.constraint_type IS NULL OR tc.constraint_type = 'PRIMARY KEY')
+ ORDER BY
+ c.ordinal_position
+ ") as $row) {
+ $column = (array) $row;
+ $column['vendor'] = $column;
+ unset($column['sequence']);
+
+ $columns[] = $column;
+ }
+
+ return $columns;
+ }
+
+
+
+ /**
+ * Returns metadata for all indexes in a table.
+ */
+ public function getIndexes($table)
+ {
+ /* There is no information about all indexes in information_schema, so pg catalog must be used */
+ $indexes = array();
+ foreach ($this->connection->query("
+ SELECT
+ c2.relname AS name,
+ indisunique AS unique,
+ indisprimary AS primary,
+ attname AS column
+ FROM
+ pg_class AS c1
+ JOIN pg_namespace ON c1.relnamespace = pg_namespace.oid
+ JOIN pg_index ON c1.oid = indrelid
+ JOIN pg_class AS c2 ON indexrelid = c2.oid
+ LEFT JOIN pg_attribute ON c1.oid = attrelid AND attnum = ANY(indkey)
+ WHERE
+ nspname = current_schema()
+ AND
+ c1.relkind = 'r'
+ AND
+ c1.relname = {$this->connection->quote($table)}
+ ") as $row) {
+ $indexes[$row['name']]['name'] = $row['name'];
+ $indexes[$row['name']]['unique'] = $row['unique'];
+ $indexes[$row['name']]['primary'] = $row['primary'];
+ $indexes[$row['name']]['columns'][] = $row['column'];
+ }
+
+ return array_values($indexes);
+ }
+
+
+
+ /**
+ * Returns metadata for all foreign keys in a table.
+ */
+ public function getForeignKeys($table)
+ {
+ /* Not for multi-column foreign keys */
+ $keys = array();
+ foreach ($this->connection->query("
+ SELECT
+ tc.constraint_name AS name,
+ kcu.column_name AS local,
+ ccu.table_name AS table,
+ ccu.column_name AS foreign
+ FROM
+ information_schema.table_constraints AS tc
+ JOIN information_schema.key_column_usage AS kcu USING(constraint_catalog, constraint_schema, constraint_name)
+ JOIN information_schema.constraint_column_usage AS ccu USING(constraint_catalog, constraint_schema, constraint_name)
+ WHERE
+ constraint_type = 'FOREIGN KEY'
+ AND
+ tc.table_name = {$this->connection->quote($table)}
+ ORDER BY
+ kcu.ordinal_position
+ ") as $row) {
+ $keys[] = (array) $row;
+ }
+
+ return $keys;
+ }
+
+
+
+ /**
+ * @return bool
+ */
+ public function isSupported($item)
+ {
+ return $item === self::SUPPORT_COLUMNS_META || $item === self::SUPPORT_SEQUENCE;
+ }
+
+}
diff --git a/libs/Nette/Database/Drivers/Sqlite2Driver.php b/libs/Nette/Database/Drivers/Sqlite2Driver.php
index 021bcd8..2db3d72 100644
--- a/libs/Nette/Database/Drivers/Sqlite2Driver.php
+++ b/libs/Nette/Database/Drivers/Sqlite2Driver.php
@@ -1,58 +1,68 @@
- $value) {
- unset($row[$key]);
- if ($key[0] === '[' || $key[0] === '"') {
- $key = substr($key, 1, -1);
- }
- $row[$key] = $value;
- }
- return $row;
- }
-
-}
+ $value) {
+ unset($row[$key]);
+ if ($key[0] === '[' || $key[0] === '"') {
+ $key = substr($key, 1, -1);
+ }
+ $row[$key] = $value;
+ }
+ return $row;
+ }
+
+
+
+ /**
+ * Returns metadata for all foreign keys in a table.
+ */
+ public function getForeignKeys($table)
+ {
+ throw new NotSupportedException; // @see http://www.sqlite.org/foreignkeys.html
+ }
+
+}
diff --git a/libs/Nette/Database/Drivers/SqliteDriver.php b/libs/Nette/Database/Drivers/SqliteDriver.php
index fc80762..1ee5d47 100644
--- a/libs/Nette/Database/Drivers/SqliteDriver.php
+++ b/libs/Nette/Database/Drivers/SqliteDriver.php
@@ -1,99 +1,242 @@
- FALSE);
-
- /** @var Nette\Database\Connection */
- private $connection;
-
- /** @var string Datetime format */
- private $fmtDateTime;
-
-
-
- public function __construct(Nette\Database\Connection $connection, array $options)
- {
- $this->connection = $connection;
- $this->fmtDateTime = isset($options['formatDateTime']) ? $options['formatDateTime'] : 'U';
- }
-
-
-
- /********************* SQL ****************d*g**/
-
-
-
- /**
- * Delimites identifier for use in a SQL statement.
- */
- public function delimite($name)
- {
- return '[' . strtr($name, '[]', ' ') . ']';
- }
-
-
-
- /**
- * Formats date-time for use in a SQL statement.
- */
- public function formatDateTime(\DateTime $value)
- {
- return $value->format($this->fmtDateTime);
- }
-
-
-
- /**
- * Encodes string for use in a LIKE statement.
- */
- public function formatLike($value, $pos)
- {
- $value = addcslashes(substr($this->connection->quote($value), 1, -1), '%_\\');
- return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'") . " ESCAPE '\\'";
- }
-
-
-
- /**
- * Injects LIMIT/OFFSET to the SQL query.
- */
- public function applyLimit(&$sql, $limit, $offset)
- {
- if ($limit >= 0 || $offset > 0) {
- $sql .= ' LIMIT ' . $limit . ($offset > 0 ? ' OFFSET ' . (int) $offset : '');
- }
- }
-
-
-
- /**
- * Normalizes result row.
- */
- public function normalizeRow($row, $statement)
- {
- return $row;
- }
-
-}
+connection = $connection;
+ $this->fmtDateTime = isset($options['formatDateTime']) ? $options['formatDateTime'] : 'U';
+ //$connection->exec('PRAGMA foreign_keys = ON');
+ }
+
+
+
+ /********************* SQL ****************d*g**/
+
+
+
+ /**
+ * Delimites identifier for use in a SQL statement.
+ */
+ public function delimite($name)
+ {
+ return '[' . strtr($name, '[]', ' ') . ']';
+ }
+
+
+
+ /**
+ * Formats boolean for use in a SQL statement.
+ */
+ public function formatBool($value)
+ {
+ return $value ? '1' : '0';
+ }
+
+
+
+ /**
+ * Formats date-time for use in a SQL statement.
+ */
+ public function formatDateTime(\DateTime $value)
+ {
+ return $value->format($this->fmtDateTime);
+ }
+
+
+
+ /**
+ * Encodes string for use in a LIKE statement.
+ */
+ public function formatLike($value, $pos)
+ {
+ $value = addcslashes(substr($this->connection->quote($value), 1, -1), '%_\\');
+ return ($pos <= 0 ? "'%" : "'") . $value . ($pos >= 0 ? "%'" : "'") . " ESCAPE '\\'";
+ }
+
+
+
+ /**
+ * Injects LIMIT/OFFSET to the SQL query.
+ */
+ public function applyLimit(&$sql, $limit, $offset)
+ {
+ if ($limit >= 0 || $offset > 0) {
+ $sql .= ' LIMIT ' . $limit . ($offset > 0 ? ' OFFSET ' . (int) $offset : '');
+ }
+ }
+
+
+
+ /**
+ * Normalizes result row.
+ */
+ public function normalizeRow($row, $statement)
+ {
+ return $row;
+ }
+
+
+
+ /********************* reflection ****************d*g**/
+
+
+
+ /**
+ * Returns list of tables.
+ */
+ public function getTables()
+ {
+ return $this->connection->query("
+ SELECT name, type = 'view' as view FROM sqlite_master WHERE type IN ('table', 'view')
+ UNION ALL
+ SELECT name, type = 'view' as view FROM sqlite_temp_master WHERE type IN ('table', 'view')
+ ORDER BY name
+ ")->fetchAll();
+ }
+
+
+
+ /**
+ * Returns metadata for all columns in a table.
+ */
+ public function getColumns($table)
+ {
+ $meta = $this->connection->query("
+ SELECT sql FROM sqlite_master WHERE type = 'table' AND name = {$this->connection->quote($table)}
+ UNION ALL
+ SELECT sql FROM sqlite_temp_master WHERE type = 'table' AND name = {$this->connection->quote($table)}
+ ")->fetch();
+
+ $columns = array();
+ foreach ($this->connection->query("PRAGMA table_info({$this->delimite($table)})") as $row) {
+ $column = $row['name'];
+ $pattern = "/(\"$column\"|\[$column\]|$column)\s+[^,]+\s+PRIMARY\s+KEY\s+AUTOINCREMENT/Ui";
+ $type = explode('(', $row['type']);
+ $columns[] = array(
+ 'name' => $column,
+ 'table' => $table,
+ 'fullname' => "$table.$column",
+ 'nativetype' => strtoupper($type[0]),
+ 'size' => isset($type[1]) ? (int) $type[1] : NULL,
+ 'nullable' => $row['notnull'] == '0',
+ 'default' => $row['dflt_value'],
+ 'autoincrement' => (bool) preg_match($pattern, $meta['sql']),
+ 'primary' => $row['pk'] == '1',
+ 'vendor' => (array) $row,
+ );
+ }
+ return $columns;
+ }
+
+
+
+ /**
+ * Returns metadata for all indexes in a table.
+ */
+ public function getIndexes($table)
+ {
+ $indexes = array();
+ foreach ($this->connection->query("PRAGMA index_list({$this->delimite($table)})") as $row) {
+ $indexes[$row['name']]['name'] = $row['name'];
+ $indexes[$row['name']]['unique'] = (bool) $row['unique'];
+ }
+
+ foreach ($indexes as $index => $values) {
+ $res = $this->connection->query("PRAGMA index_info({$this->delimite($index)})");
+ while ($row = $res->fetch(TRUE)) {
+ $indexes[$index]['columns'][$row['seqno']] = $row['name'];
+ }
+ }
+
+ $columns = $this->getColumns($table);
+ foreach ($indexes as $index => $values) {
+ $column = $indexes[$index]['columns'][0];
+ $primary = FALSE;
+ foreach ($columns as $info) {
+ if ($column == $info['name']) {
+ $primary = $info['primary'];
+ break;
+ }
+ }
+ $indexes[$index]['primary'] = (bool) $primary;
+ }
+ if (!$indexes) { // @see http://www.sqlite.org/lang_createtable.html#rowid
+ foreach ($columns as $column) {
+ if ($column['vendor']['pk']) {
+ $indexes[] = array(
+ 'name' => 'ROWID',
+ 'unique' => TRUE,
+ 'primary' => TRUE,
+ 'columns' => array($column['name']),
+ );
+ break;
+ }
+ }
+ }
+
+ return array_values($indexes);
+ }
+
+
+
+ /**
+ * Returns metadata for all foreign keys in a table.
+ */
+ public function getForeignKeys($table)
+ {
+ $keys = array();
+ foreach ($this->connection->query("PRAGMA foreign_key_list({$this->delimite($table)})") as $row) {
+ $keys[$row['id']]['name'] = $row['id']; // foreign key name
+ $keys[$row['id']]['local'][$row['seq']] = $row['from']; // local columns
+ $keys[$row['id']]['table'] = $row['table']; // referenced table
+ $keys[$row['id']]['foreign'][$row['seq']] = $row['to']; // referenced columns
+ $keys[$row['id']]['onDelete'] = $row['on_delete'];
+ $keys[$row['id']]['onUpdate'] = $row['on_update'];
+
+ if ($keys[$row['id']]['foreign'][0] == NULL) {
+ $keys[$row['id']]['foreign'] = NULL;
+ }
+ }
+ return array_values($keys);
+ }
+
+
+
+ /**
+ * @return bool
+ */
+ public function isSupported($item)
+ {
+ return FALSE;
+ }
+
+}
diff --git a/libs/Nette/Database/Helpers.php b/libs/Nette/Database/Helpers.php
new file mode 100644
index 0000000..646447e
--- /dev/null
+++ b/libs/Nette/Database/Helpers.php
@@ -0,0 +1,173 @@
+ IReflection::FIELD_TEXT, // PostgreSQL arrays
+ 'BYTEA|BLOB|BIN' => IReflection::FIELD_BINARY,
+ 'TEXT|CHAR' => IReflection::FIELD_TEXT,
+ 'YEAR|BYTE|COUNTER|SERIAL|INT|LONG' => IReflection::FIELD_INTEGER,
+ 'CURRENCY|REAL|MONEY|FLOAT|DOUBLE|DECIMAL|NUMERIC|NUMBER' => IReflection::FIELD_FLOAT,
+ '^TIME$' => IReflection::FIELD_TIME,
+ 'TIME' => IReflection::FIELD_DATETIME, // DATETIME, TIMESTAMP
+ 'DATE' => IReflection::FIELD_DATE,
+ 'BOOL|BIT' => IReflection::FIELD_BOOL,
+ );
+
+
+
+ /**
+ * Displays complete result set as HTML table for debug purposes.
+ * @return void
+ */
+ public static function dumpResult(Statement $statement)
+ {
+ echo "\n\n" . htmlSpecialChars($statement->queryString) . "\n";
+ if (!$statement->columnCount()) {
+ echo "\t\n\t\t| Affected rows: | \n\t\t", $statement->rowCount(), " | \n\t
\n
\n";
+ return;
+ }
+ $i = 0;
+ foreach ($statement as $row) {
+ if ($i === 0) {
+ echo "\n\t\n\t\t| #row | \n";
+ foreach ($row as $col => $foo) {
+ echo "\t\t" . htmlSpecialChars($col) . " | \n";
+ }
+ echo "\t
\n\n\n";
+ }
+ echo "\t\n\t\t| ", $i, " | \n";
+ foreach ($row as $col) {
+ //if (is_object($col)) $col = $col->__toString();
+ echo "\t\t", htmlSpecialChars($col), " | \n";
+ }
+ echo "\t
\n";
+ $i++;
+ }
+
+ if ($i === 0) {
+ echo "\t\n\t\t| empty result set | \n\t
\n\n";
+ } else {
+ echo "\n\n";
+ }
+ }
+
+
+
+ /**
+ * Returns syntax highlighted SQL command.
+ * @param string
+ * @return string
+ */
+ public static function dumpSql($sql)
+ {
+ static $keywords1 = 'SELECT|(?:ON\s+DUPLICATE\s+KEY)?UPDATE|INSERT(?:\s+INTO)?|REPLACE(?:\s+INTO)?|DELETE|CALL|UNION|FROM|WHERE|HAVING|GROUP\s+BY|ORDER\s+BY|LIMIT|OFFSET|SET|VALUES|LEFT\s+JOIN|INNER\s+JOIN|TRUNCATE';
+ static $keywords2 = 'ALL|DISTINCT|DISTINCTROW|IGNORE|AS|USING|ON|AND|OR|IN|IS|NOT|NULL|LIKE|RLIKE|REGEXP|TRUE|FALSE';
+
+ // insert new lines
+ $sql = " $sql ";
+ $sql = preg_replace("#(?<=[\\s,(])($keywords1)(?=[\\s,)])#i", "\n\$1", $sql);
+
+ // reduce spaces
+ $sql = preg_replace('#[ \t]{2,}#', " ", $sql);
+
+ $sql = wordwrap($sql, 100);
+ $sql = preg_replace("#([ \t]*\r?\n){2,}#", "\n", $sql);
+
+ // syntax highlight
+ $sql = htmlSpecialChars($sql);
+ $sql = preg_replace_callback("#(/\\*.+?\\*/)|(\\*\\*.+?\\*\\*)|(?<=[\\s,(])($keywords1)(?=[\\s,)])|(?<=[\\s,(=])($keywords2)(?=[\\s,)=])#is", function($matches) {
+ if (!empty($matches[1])) // comment
+ return '' . $matches[1] . '';
+
+ if (!empty($matches[2])) // error
+ return '' . $matches[2] . '';
+
+ if (!empty($matches[3])) // most important keywords
+ return '' . $matches[3] . '';
+
+ if (!empty($matches[4])) // other keywords
+ return '' . $matches[4] . '';
+ }, $sql);
+
+ return '' . trim($sql) . "
\n";
+ }
+
+
+
+ /**
+ * Heuristic type detection.
+ * @param string
+ * @return string
+ * @internal
+ */
+ public static function detectType($type)
+ {
+ static $cache;
+ if (!isset($cache[$type])) {
+ $cache[$type] = 'string';
+ foreach (self::$typePatterns as $s => $val) {
+ if (preg_match("#$s#i", $type)) {
+ return $cache[$type] = $val;
+ }
+ }
+ }
+ return $cache[$type];
+ }
+
+
+
+ /**
+ * Import SQL dump from file - extreme fast.
+ * @return int count of commands
+ */
+ public static function loadFromFile(Connection $connection, $file)
+ {
+ @set_time_limit(0); // intentionally @
+
+ $handle = @fopen($file, 'r'); // intentionally @
+ if (!$handle) {
+ throw new Nette\FileNotFoundException("Cannot open file '$file'.");
+ }
+
+ $count = 0;
+ $sql = '';
+ while (!feof($handle)) {
+ $s = fgets($handle);
+ $sql .= $s;
+ if (substr(rtrim($s), -1) === ';') {
+ $connection->exec($sql); // native query without logging
+ $sql = '';
+ $count++;
+ }
+ }
+ if (trim($sql) !== '') {
+ $connection->exec($sql);
+ $count++;
+ }
+ fclose($handle);
+ return $count;
+ }
+
+}
diff --git a/libs/Nette/Database/IReflection.php b/libs/Nette/Database/IReflection.php
new file mode 100644
index 0000000..454a1cd
--- /dev/null
+++ b/libs/Nette/Database/IReflection.php
@@ -0,0 +1,68 @@
+, %2$s for table name
+ * @param string %1$s stands for key used after ->, %2$s for table name
+ */
+ public function __construct($primary = 'id', $foreign = '%s_id', $table = '%s')
+ {
+ $this->primary = $primary;
+ $this->foreign = $foreign;
+ $this->table = $table;
+ }
+
+
+
+ public function getPrimary($table)
+ {
+ return sprintf($this->primary, $this->getColumnFromTable($table));
+ }
+
+
+
+ public function getHasManyReference($table, $key)
+ {
+ $table = $this->getColumnFromTable($table);
+ return array(
+ sprintf($this->table, $key, $table),
+ sprintf($this->foreign, $table, $key),
+ );
+ }
+
+
+
+ public function getBelongsToReference($table, $key)
+ {
+ $table = $this->getColumnFromTable($table);
+ return array(
+ sprintf($this->table, $key, $table),
+ sprintf($this->foreign, $key, $table),
+ );
+ }
+
+
+
+ public function setConnection(Nette\Database\Connection $connection)
+ {}
+
+
+
+ protected function getColumnFromTable($name)
+ {
+ if ($this->table !== '%s' && preg_match('(^' . str_replace('%s', '(.*)', preg_quote($this->table)) . '$)', $name, $match)) {
+ return $match[1];
+ }
+
+ return $name;
+ }
+
+}
diff --git a/libs/Nette/Database/Reflection/DatabaseReflection.php b/libs/Nette/Database/Reflection/DatabaseReflection.php
deleted file mode 100644
index dd6bef5..0000000
--- a/libs/Nette/Database/Reflection/DatabaseReflection.php
+++ /dev/null
@@ -1,117 +0,0 @@
-, %2$s for table name
- * @param string %1$s stands for key used after ->, %2$s for table name
- */
- public function __construct($primary = 'id', $foreign = '%s_id', $table = '%s')
- {
- $this->primary = $primary;
- $this->foreign = $foreign;
- $this->table = $table;
- }
-
-
-
- public function getPrimary($table)
- {
- return sprintf($this->primary, $table);
- }
-
-
-
- public function getReferencingColumn($name, $table)
- {
- return $this->getReferencedColumn($table, $name);
- }
-
-
-
- public function getReferencedColumn($name, $table)
- {
- if ($this->table !== '%s' && preg_match('(^' . str_replace('%s', '(.*)', preg_quote($this->table)) . '$)', $name, $match)) {
- $name = $match[1];
- }
- return sprintf($this->foreign, $name, $table);
- }
-
-
-
- public function getReferencedTable($name, $table)
- {
- return sprintf($this->table, $name, $table);
- }
-
-
-
- /**
- * Heuristic type detection.
- * @param string
- * @return string
- * @internal
- */
- public static function detectType($type)
- {
- static $types, $patterns = array(
- 'BYTEA|BLOB|BIN' => self::FIELD_BINARY,
- 'TEXT|CHAR' => self::FIELD_TEXT,
- 'YEAR|BYTE|COUNTER|SERIAL|INT|LONG' => self::FIELD_INTEGER,
- 'CURRENCY|REAL|MONEY|FLOAT|DOUBLE|DECIMAL|NUMERIC|NUMBER' => self::FIELD_FLOAT,
- 'TIME|DATE' => self::FIELD_DATETIME,
- 'BOOL|BIT' => self::FIELD_BOOL,
- );
-
- if (!isset($types[$type])) {
- $types[$type] = 'string';
- foreach ($patterns as $s => $val) {
- if (preg_match("#$s#i", $type)) {
- return $types[$type] = $val;
- }
- }
- }
- return $types[$type];
- }
-
-}
diff --git a/libs/Nette/Database/Reflection/DiscoveredReflection.php b/libs/Nette/Database/Reflection/DiscoveredReflection.php
new file mode 100644
index 0000000..0c70d6f
--- /dev/null
+++ b/libs/Nette/Database/Reflection/DiscoveredReflection.php
@@ -0,0 +1,196 @@
+cacheStorage = $storage;
+ }
+
+
+
+ public function setConnection(Nette\Database\Connection $connection)
+ {
+ $this->connection = $connection;
+ if ($this->cacheStorage) {
+ $this->cache = new Nette\Caching\Cache($this->cacheStorage, 'Nette.Database.' . md5($connection->getDsn()));
+ $this->structure = $this->cache->load('structure') ?: $this->structure;
+ }
+ }
+
+
+
+ public function __destruct()
+ {
+ if ($this->cache) {
+ $this->cache->save('structure', $this->structure);
+ }
+ }
+
+
+
+ public function getPrimary($table)
+ {
+ $primary = & $this->structure['primary'][strtolower($table)];
+ if (isset($primary)) {
+ return empty($primary) ? NULL : $primary;
+ }
+
+ $columns = $this->connection->getSupplementalDriver()->getColumns($table);
+ $primaryCount = 0;
+ foreach ($columns as $column) {
+ if ($column['primary']) {
+ $primary = $column['name'];
+ $primaryCount++;
+ }
+ }
+
+ if ($primaryCount !== 1) {
+ $primary = '';
+ return NULL;
+ }
+
+ return $primary;
+ }
+
+
+
+ public function getHasManyReference($table, $key, $refresh = TRUE)
+ {
+ $reference = & $this->structure['hasMany'][strtolower($table)];
+ if (!empty($reference)) {
+ $candidates = $columnCandidates = array();
+ foreach ($reference as $targetPair) {
+ list($targetColumn, $targetTable) = $targetPair;
+ if (stripos($targetTable, $key) === FALSE)
+ continue;
+
+ $candidates[] = array($targetTable, $targetColumn);
+ if (stripos($targetColumn, $table) !== FALSE) {
+ $columnCandidates[] = $candidate = array($targetTable, $targetColumn);
+ if (strtolower($targetTable) === strtolower($key))
+ return $candidate;
+ }
+ }
+
+ if (count($columnCandidates) === 1) {
+ return reset($columnCandidates);
+ } elseif (count($candidates) === 1) {
+ return reset($candidates);
+ }
+
+ foreach ($candidates as $candidate) {
+ list($targetTable, $targetColumn) = $candidate;
+ if (strtolower($targetTable) === strtolower($key))
+ return $candidate;
+ }
+
+ if (!$refresh && !empty($candidates)) {
+ throw new \PDOException('Ambiguous joining column in related call.');
+ }
+ }
+
+ if (!$refresh) {
+ throw new \PDOException("No reference found for \${$table}->related({$key}).");
+ }
+
+ $this->reloadAllForeignKeys();
+ return $this->getHasManyReference($table, $key, FALSE);
+ }
+
+
+
+ public function getBelongsToReference($table, $key, $refresh = TRUE)
+ {
+ $reference = & $this->structure['belongsTo'][strtolower($table)];
+ if (!empty($reference)) {
+ foreach ($reference as $column => $targetTable) {
+ if (stripos($column, $key) !== FALSE) {
+ return array(
+ $targetTable,
+ $column,
+ );
+ }
+ }
+ }
+
+ if (!$refresh) {
+ throw new \PDOException("No reference found for \${$table}->{$key}.");
+ }
+
+ $this->reloadForeignKeys($table);
+ return $this->getBelongsToReference($table, $key, FALSE);
+ }
+
+
+
+ protected function reloadAllForeignKeys()
+ {
+ foreach ($this->connection->getSupplementalDriver()->getTables() as $table) {
+ if ($table['view'] == FALSE) {
+ $this->reloadForeignKeys($table['name']);
+ }
+ }
+
+ foreach (array_keys($this->structure['hasMany']) as $table) {
+ uksort($this->structure['hasMany'][$table], function($a, $b) {
+ return strlen($a) - strlen($b);
+ });
+ }
+ }
+
+
+
+ protected function reloadForeignKeys($table)
+ {
+ foreach ($this->connection->getSupplementalDriver()->getForeignKeys($table) as $row) {
+ $this->structure['belongsTo'][strtolower($table)][$row['local']] = $row['table'];
+ $this->structure['hasMany'][strtolower($row['table'])][$row['local'] . $table] = array($row['local'], $table);
+ }
+
+ if (isset($this->structure['belongsTo'][$table])) {
+ uksort($this->structure['belongsTo'][$table], function($a, $b) {
+ return strlen($a) - strlen($b);
+ });
+ }
+ }
+
+}
diff --git a/libs/Nette/Database/Row.php b/libs/Nette/Database/Row.php
index c61366c..d72482d 100644
--- a/libs/Nette/Database/Row.php
+++ b/libs/Nette/Database/Row.php
@@ -1,47 +1,58 @@
-normalizeRow($this);
- }
-
-
-
- /**
- * Returns a item.
- * @param mixed key or index
- * @return mixed
- */
- public function offsetGet($key)
- {
- if (is_int($key)) {
- $arr = array_values((array) $this);
- return $arr[$key];
- }
- return $this->$key;
- }
-
-}
+normalizeRow($this);
+ }
+
+
+
+ /**
+ * Returns a item.
+ * @param mixed key or index
+ * @return mixed
+ */
+ public function offsetGet($key)
+ {
+ if (is_int($key)) {
+ $arr = array_values((array) $this);
+ return $arr[$key];
+ }
+ return $this->$key;
+ }
+
+
+
+ public function offsetExists($key)
+ {
+ if (is_int($key)) {
+ $arr = array_values((array) $this);
+ return isset($arr[$key]);
+ }
+ return parent::offsetExists($key);
+ }
+
+}
diff --git a/libs/Nette/Database/SqlLiteral.php b/libs/Nette/Database/SqlLiteral.php
index b5202c5..14144f7 100644
--- a/libs/Nette/Database/SqlLiteral.php
+++ b/libs/Nette/Database/SqlLiteral.php
@@ -1,34 +1,44 @@
-value = (string) $value;
- }
-
-}
+value = (string) $value;
+ }
+
+
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->value;
+ }
+
+}
diff --git a/libs/Nette/Database/SqlPreprocessor.php b/libs/Nette/Database/SqlPreprocessor.php
index c65e647..3a21d0d 100644
--- a/libs/Nette/Database/SqlPreprocessor.php
+++ b/libs/Nette/Database/SqlPreprocessor.php
@@ -1,167 +1,162 @@
-connection = $connection;
- $this->driver = $connection->getSupplementalDriver();
- }
-
-
-
- /**
- * @param string
- * @param array
- * @return array of [sql, params]
- */
- public function process($sql, $params)
- {
- $this->params = $params;
- $this->counter = 0;
- $this->remaining = array();
-
- $cmd = strtoupper(substr(ltrim($sql), 0, 6)); // detect array mode
- $this->arrayMode = $cmd === 'INSERT' || $cmd === 'REPLAC' ? 'values' : 'assoc';
-
- /*~
- \'.*?\'|".*?"| ## string
- :[a-zA-Z0-9_]+:| ## :substitution:
- \? ## placeholder
- ~xs*/
- $sql = Nette\Utils\Strings::replace($sql, '~\'.*?\'|".*?"|:[a-zA-Z0-9_]+:|\?~s', array($this, 'callback'));
-
- while ($this->counter < count($params)) {
- $sql .= ' ' . $this->formatValue($params[$this->counter++]);
- }
-
- return array($sql, $this->remaining);
- }
-
-
-
- /** @internal */
- public function callback($m)
- {
- $m = $m[0];
- if ($m[0] === "'" || $m[0] === '"') { // string
- return $m;
-
- } elseif ($m[0] === '?') { // placeholder
- return $this->formatValue($this->params[$this->counter++]);
-
- } elseif ($m[0] === ':') { // substitution
- $s = substr($m, 1, -1);
- return isset($this->connection->substitutions[$s]) ? $this->connection->substitutions[$s] : $m;
- }
- }
-
-
-
- private function formatValue($value)
- {
- if (is_string($value)) {
- if (strlen($value) > 20) {
- $this->remaining[] = $value;
- return '?';
-
- } else {
- return $this->connection->quote($value);
- }
-
- } elseif (is_int($value)) {
- return (string) $value;
-
- } elseif (is_float($value)) {
- return rtrim(rtrim(number_format($value, 10, '.', ''), '0'), '.');
-
- } elseif (is_bool($value)) {
- $this->remaining[] = $value;
- return '?';
-
- } elseif ($value === NULL) {
- return 'NULL';
-
- } elseif (is_array($value) || $value instanceof \Traversable) {
- $vx = $kx = array();
-
- if (isset($value[0])) { // non-associative; value, value, value
- foreach ($value as $v) {
- $vx[] = $this->formatValue($v);
- }
- return implode(', ', $vx);
-
- } elseif ($this->arrayMode === 'values') { // (key, key, ...) VALUES (value, value, ...)
- $this->arrayMode = 'multi';
- foreach ($value as $k => $v) {
- $kx[] = $this->driver->delimite($k);
- $vx[] = $this->formatValue($v);
- }
- return '(' . implode(', ', $kx) . ') VALUES (' . implode(', ', $vx) . ')';
-
- } elseif ($this->arrayMode === 'assoc') { // key=value, key=value, ...
- foreach ($value as $k => $v) {
- $vx[] = $this->driver->delimite($k) . '=' . $this->formatValue($v);
- }
- return implode(', ', $vx);
-
- } elseif ($this->arrayMode === 'multi') { // multiple insert (value, value, ...), ...
- foreach ($value as $k => $v) {
- $vx[] = $this->formatValue($v);
- }
- return ', (' . implode(', ', $vx) . ')';
- }
-
- } elseif ($value instanceof \DateTime) {
- return $this->driver->formatDateTime($value);
-
- } elseif ($value instanceof SqlLiteral) {
- return $value->value;
-
- } else {
- $this->remaining[] = $value;
- return '?';
- }
- }
-
-}
+connection = $connection;
+ $this->driver = $connection->getSupplementalDriver();
+ }
+
+
+
+ /**
+ * @param string
+ * @param array
+ * @return array of [sql, params]
+ */
+ public function process($sql, $params)
+ {
+ $this->params = $params;
+ $this->counter = 0;
+ $this->remaining = array();
+ $this->arrayMode = 'assoc';
+
+ $sql = Nette\Utils\Strings::replace($sql, '~\'.*?\'|".*?"|\?|\b(?:INSERT|REPLACE|UPDATE)\b~si', array($this, 'callback'));
+
+ while ($this->counter < count($params)) {
+ $sql .= ' ' . $this->formatValue($params[$this->counter++]);
+ }
+
+ return array($sql, $this->remaining);
+ }
+
+
+
+ /** @internal */
+ public function callback($m)
+ {
+ $m = $m[0];
+ if ($m[0] === "'" || $m[0] === '"') { // string
+ return $m;
+
+ } elseif ($m === '?') { // placeholder
+ return $this->formatValue($this->params[$this->counter++]);
+
+ } else { // INSERT, REPLACE, UPDATE
+ $this->arrayMode = strtoupper($m) === 'UPDATE' ? 'assoc' : 'values';
+ return $m;
+ }
+ }
+
+
+
+ private function formatValue($value)
+ {
+ if (is_string($value)) {
+ if (strlen($value) > 20) {
+ $this->remaining[] = $value;
+ return '?';
+
+ } else {
+ return $this->connection->quote($value);
+ }
+
+ } elseif (is_int($value)) {
+ return (string) $value;
+
+ } elseif (is_float($value)) {
+ return rtrim(rtrim(number_format($value, 10, '.', ''), '0'), '.');
+
+ } elseif (is_bool($value)) {
+ return $this->driver->formatBool($value);
+
+ } elseif ($value === NULL) {
+ return 'NULL';
+
+ } elseif ($value instanceof Table\ActiveRow) {
+ return $value->getPrimary();
+
+ } elseif (is_array($value) || $value instanceof \Traversable) {
+ $vx = $kx = array();
+
+ if (isset($value[0])) { // non-associative; value, value, value
+ foreach ($value as $v) {
+ $vx[] = $this->formatValue($v);
+ }
+ return implode(', ', $vx);
+
+ } elseif ($this->arrayMode === 'values') { // (key, key, ...) VALUES (value, value, ...)
+ $this->arrayMode = 'multi';
+ foreach ($value as $k => $v) {
+ $kx[] = $this->driver->delimite($k);
+ $vx[] = $this->formatValue($v);
+ }
+ return '(' . implode(', ', $kx) . ') VALUES (' . implode(', ', $vx) . ')';
+
+ } elseif ($this->arrayMode === 'assoc') { // key=value, key=value, ...
+ foreach ($value as $k => $v) {
+ $vx[] = $this->driver->delimite($k) . '=' . $this->formatValue($v);
+ }
+ return implode(', ', $vx);
+
+ } elseif ($this->arrayMode === 'multi') { // multiple insert (value, value, ...), ...
+ foreach ($value as $k => $v) {
+ $vx[] = $this->formatValue($v);
+ }
+ return '(' . implode(', ', $vx) . ')';
+ }
+
+ } elseif ($value instanceof \DateTime) {
+ return $this->driver->formatDateTime($value);
+
+ } elseif ($value instanceof SqlLiteral) {
+ return $value->__toString();
+
+ } else {
+ $this->remaining[] = $value;
+ return '?';
+ }
+ }
+
+}
diff --git a/libs/Nette/Database/Statement.php b/libs/Nette/Database/Statement.php
index 06ccbd2..68c36bf 100644
--- a/libs/Nette/Database/Statement.php
+++ b/libs/Nette/Database/Statement.php
@@ -1,225 +1,221 @@
-connection = $connection;
- $this->setFetchMode(PDO::FETCH_CLASS, 'Nette\Database\Row', array($this));
- }
-
-
-
- /**
- * @return Connection
- */
- public function getConnection()
- {
- return $this->connection;
- }
-
-
-
- /**
- * Executes statement.
- * @param array
- * @return Statement provides a fluent interface
- */
- public function execute($params = array())
- {
- static $types = array('boolean' => PDO::PARAM_BOOL, 'integer' => PDO::PARAM_INT,
- 'resource' => PDO::PARAM_LOB, 'NULL' => PDO::PARAM_NULL);
-
- foreach ($params as $key => $value) {
- $type = gettype($value);
- $this->bindValue(is_int($key) ? $key + 1 : $key, $value, isset($types[$type]) ? $types[$type] : PDO::PARAM_STR);
- }
-
- $time = microtime(TRUE);
- try {
- parent::execute();
- } catch (\PDOException $e) {
- $e->queryString = $this->queryString;
- throw $e;
- }
- $this->time = microtime(TRUE) - $time;
- $this->connection->__call('onQuery', array($this, $params)); // $this->connection->onQuery() in PHP 5.3
-
- return $this;
- }
-
-
-
- /**
- * Fetches into an array where the 1st column is a key and all subsequent columns are values.
- * @return array
- */
- public function fetchPairs()
- {
- return $this->fetchAll(PDO::FETCH_KEY_PAIR); // since PHP 5.2.3
- }
-
-
-
- /**
- * Normalizes result row.
- * @param array
- * @return array
- */
- public function normalizeRow($row)
- {
- if ($this->types === NULL) {
- $this->types = array();
- if ($this->connection->getSupplementalDriver()->supports['meta']) { // workaround for PHP bugs #53782, #54695
- foreach ($row as $key => $foo) {
- $type = $this->getColumnMeta(count($this->types));
- if (isset($type['native_type'])) {
- $this->types[$key] = Reflection\DatabaseReflection::detectType($type['native_type']);
- }
- }
- }
- }
-
- foreach ($this->types as $key => $type) {
- $value = $row[$key];
- if ($value === NULL || $value === FALSE || $type === Reflection\DatabaseReflection::FIELD_TEXT) {
-
- } elseif ($type === Reflection\DatabaseReflection::FIELD_INTEGER) {
- $row[$key] = is_float($tmp = $value * 1) ? $value : $tmp;
-
- } elseif ($type === Reflection\DatabaseReflection::FIELD_FLOAT) {
- $row[$key] = (string) ($tmp = (float) $value) === $value ? $tmp : $value;
-
- } elseif ($type === Reflection\DatabaseReflection::FIELD_BOOL) {
- $row[$key] = ((bool) $value) && $value !== 'f' && $value !== 'F';
- }
- }
-
- return $this->connection->getSupplementalDriver()->normalizeRow($row, $this);
- }
-
-
-
- /********************* misc tools ****************d*g**/
-
-
-
- /**
- * Displays complete result set as HTML table for debug purposes.
- * @return void
- */
- public function dump()
- {
- echo "\n\n" . htmlSpecialChars($this->queryString) . "\n";
- if (!$this->columnCount()) {
- echo "\t\n\t\t| Affected rows: | \n\t\t", $this->rowCount(), " | \n\t
\n
\n";
- return;
- }
- $i = 0;
- foreach ($this as $row) {
- if ($i === 0) {
- echo "\n\t\n\t\t| #row | \n";
- foreach ($row as $col => $foo) {
- echo "\t\t" . htmlSpecialChars($col) . " | \n";
- }
- echo "\t
\n\n\n";
- }
- echo "\t\n\t\t| ", $i, " | \n";
- foreach ($row as $col) {
- //if (is_object($col)) $col = $col->__toString();
- echo "\t\t", htmlSpecialChars($col), " | \n";
- }
- echo "\t
\n";
- $i++;
- }
-
- if ($i === 0) {
- echo "\t\n\t\t| empty result set | \n\t
\n\n";
- } else {
- echo "\n\n";
- }
- }
-
-
-
- /********************* Nette\Object behaviour ****************d*g**/
-
-
-
- /**
- * @return Nette\Reflection\ClassType
- */
- public static function getReflection()
- {
- return new Nette\Reflection\ClassType(get_called_class());
- }
-
-
-
- public function __call($name, $args)
- {
- return ObjectMixin::call($this, $name, $args);
- }
-
-
-
- public function &__get($name)
- {
- return ObjectMixin::get($this, $name);
- }
-
-
-
- public function __set($name, $value)
- {
- return ObjectMixin::set($this, $name, $value);
- }
-
-
-
- public function __isset($name)
- {
- return ObjectMixin::has($this, $name);
- }
-
-
-
- public function __unset($name)
- {
- ObjectMixin::remove($this, $name);
- }
-
-}
+connection = $connection;
+ $this->setFetchMode(PDO::FETCH_CLASS, 'Nette\Database\Row', array($this));
+ }
+
+
+
+ /**
+ * @return Connection
+ */
+ public function getConnection()
+ {
+ return $this->connection;
+ }
+
+
+
+ /**
+ * Executes statement.
+ * @param array
+ * @return Statement provides a fluent interface
+ */
+ public function execute($params = array())
+ {
+ static $types = array('boolean' => PDO::PARAM_BOOL, 'integer' => PDO::PARAM_INT,
+ 'resource' => PDO::PARAM_LOB, 'NULL' => PDO::PARAM_NULL);
+
+ foreach ($params as $key => $value) {
+ $type = gettype($value);
+ $this->bindValue(is_int($key) ? $key + 1 : $key, $value, isset($types[$type]) ? $types[$type] : PDO::PARAM_STR);
+ }
+
+ $time = microtime(TRUE);
+ try {
+ parent::execute();
+ } catch (\PDOException $e) {
+ $e->queryString = $this->queryString;
+ throw $e;
+ }
+ $this->time = microtime(TRUE) - $time;
+ $this->connection->__call('onQuery', array($this, $params)); // $this->connection->onQuery() in PHP 5.3
+
+ return $this;
+ }
+
+
+
+ /**
+ * Fetches into an array where the 1st column is a key and all subsequent columns are values.
+ * @return array
+ */
+ public function fetchPairs()
+ {
+ return $this->fetchAll(PDO::FETCH_KEY_PAIR); // since PHP 5.2.3
+ }
+
+
+
+ /**
+ * Normalizes result row.
+ * @param array
+ * @return array
+ */
+ public function normalizeRow($row)
+ {
+ foreach ($this->detectColumnTypes() as $key => $type) {
+ $value = $row[$key];
+ if ($value === NULL || $value === FALSE || $type === IReflection::FIELD_TEXT) {
+
+ } elseif ($type === IReflection::FIELD_INTEGER) {
+ $row[$key] = is_float($tmp = $value * 1) ? $value : $tmp;
+
+ } elseif ($type === IReflection::FIELD_FLOAT) {
+ $row[$key] = (string) ($tmp = (float) $value) === $value ? $tmp : $value;
+
+ } elseif ($type === IReflection::FIELD_BOOL) {
+ $row[$key] = ((bool) $value) && $value !== 'f' && $value !== 'F';
+
+ } elseif ($type === IReflection::FIELD_DATETIME || $type === IReflection::FIELD_DATE || $type === IReflection::FIELD_TIME) {
+ $row[$key] = new Nette\DateTime($value);
+
+ }
+ }
+
+ return $this->connection->getSupplementalDriver()->normalizeRow($row, $this);
+ }
+
+
+
+ private function detectColumnTypes()
+ {
+ if ($this->types === NULL) {
+ $this->types = array();
+ if ($this->connection->getSupplementalDriver()->isSupported(ISupplementalDriver::SUPPORT_COLUMNS_META)) { // workaround for PHP bugs #53782, #54695
+ $col = 0;
+ while ($meta = $this->getColumnMeta($col++)) {
+ if (isset($meta['native_type'])) {
+ $this->types[$meta['name']] = Helpers::detectType($meta['native_type']);
+ }
+ }
+ }
+ }
+ return $this->types;
+ }
+
+
+
+ /**
+ * @return float
+ */
+ public function getTime()
+ {
+ return $this->time;
+ }
+
+
+
+ /********************* misc tools ****************d*g**/
+
+
+
+ /**
+ * Displays complete result set as HTML table for debug purposes.
+ * @return void
+ */
+ public function dump()
+ {
+ Helpers::dumpResult($this);
+ }
+
+
+
+ /********************* Nette\Object behaviour ****************d*g**/
+
+
+
+ /**
+ * @return Nette\Reflection\ClassType
+ */
+ public /**/static/**/ function getReflection()
+ {
+ return new Nette\Reflection\ClassType(/*5.2*$this*//**/get_called_class()/**/);
+ }
+
+
+
+ public function __call($name, $args)
+ {
+ return ObjectMixin::call($this, $name, $args);
+ }
+
+
+
+ public function &__get($name)
+ {
+ return ObjectMixin::get($this, $name);
+ }
+
+
+
+ public function __set($name, $value)
+ {
+ return ObjectMixin::set($this, $name, $value);
+ }
+
+
+
+ public function __isset($name)
+ {
+ return ObjectMixin::has($this, $name);
+ }
+
+
+
+ public function __unset($name)
+ {
+ ObjectMixin::remove($this, $name);
+ }
+
+}
diff --git a/libs/Nette/Database/Table/ActiveRow.php b/libs/Nette/Database/Table/ActiveRow.php
index 7cbccf0..9285295 100644
--- a/libs/Nette/Database/Table/ActiveRow.php
+++ b/libs/Nette/Database/Table/ActiveRow.php
@@ -1,265 +1,316 @@
-data = $data;
- $this->table = $table;
- }
-
-
-
- /**
- * Returns primary key value.
- * @return string
- */
- public function __toString()
- {
- return (string) $this[$this->table->primary]; // (string) - PostgreSQL returns int
- }
-
-
-
- /**
- * @return array
- */
- public function toArray()
- {
- $this->access(NULL);
- return $this->data;
- }
-
-
-
- /**
- * Returns referenced row.
- * @param string
- * @return ActiveRow or NULL if the row does not exist
- */
- public function ref($name)
- {
- $referenced = $this->table->getReferencedTable($name, $column);
- if (isset($referenced[$this[$column]])) { // referenced row may not exist
- $res = $referenced[$this[$column]];
- return $res;
- }
- }
-
-
-
- /**
- * Returns referencing rows.
- * @param string table name
- * @return GroupedSelection
- */
- public function related($table)
- {
- $referencing = $this->table->getReferencingTable($table);
- $referencing->active = $this[$this->table->primary];
- return $referencing;
- }
-
-
-
- /**
- * Updates row.
- * @param array or NULL for all modified values
- * @return int number of affected rows or FALSE in case of an error
- */
- public function update($data = NULL)
- {
- if ($data === NULL) {
- $data = $this->modified;
- }
- return $this->table->connection->table($this->table->name)
- ->where($this->table->primary, $this[$this->table->primary])
- ->update($data);
- }
-
-
-
- /**
- * Deletes row.
- * @return int number of affected rows or FALSE in case of an error
- */
- public function delete()
- {
- return $this->table->connection->table($this->table->name)
- ->where($this->table->primary, $this[$this->table->primary])
- ->delete();
- }
-
-
-
- /********************* interface IteratorAggregate ****************d*g**/
-
-
-
- public function getIterator()
- {
- $this->access(NULL);
- return new \ArrayIterator($this->data);
- }
-
-
-
- /********************* interface ArrayAccess & magic accessors ****************d*g**/
-
-
-
- /**
- * Stores value in column.
- * @param string column name
- * @return NULL
- */
- public function offsetSet($key, $value)
- {
- $this->__set($key, $value);
- }
-
-
-
- /**
- * Returns value of column.
- * @param string column name
- * @return string
- */
- public function offsetGet($key)
- {
- return $this->__get($key);
- }
-
-
-
- /**
- * Tests if column exists.
- * @param string column name
- * @return bool
- */
- public function offsetExists($key)
- {
- return $this->__isset($key);
- }
-
-
-
- /**
- * Removes column from data.
- * @param string column name
- * @return NULL
- */
- public function offsetUnset($key)
- {
- $this->__unset($key);
- }
-
-
-
- public function __set($key, $value)
- {
- $this->data[$key] = $value;
- $this->modified[$key] = $value;
- }
-
-
-
- public function &__get($key)
- {
- if (array_key_exists($key, $this->data)) {
- $this->access($key);
- return $this->data[$key];
- }
-
- $column = $this->table->connection->databaseReflection->getReferencedColumn($key, $this->table->name);
- if (array_key_exists($column, $this->data)) {
- $value = $this->data[$column];
- $referenced = $this->table->getReferencedTable($key);
- $ret = isset($referenced[$value]) ? $referenced[$value] : NULL; // referenced row may not exist
- return $ret;
- }
-
- $this->access($key);
- if (array_key_exists($key, $this->data)) {
- return $this->data[$key];
-
- } else {
- $this->access($key, TRUE);
-
- $this->access($column);
- if (array_key_exists($column, $this->data)) {
- $value = $this->data[$column];
- $referenced = $this->table->getReferencedTable($key);
- $ret = isset($referenced[$value]) ? $referenced[$value] : NULL; // referenced row may not exist
-
- } else {
- $this->access($column, TRUE);
- trigger_error("Unknown column $key", E_USER_WARNING);
- $ret = NULL;
- }
- return $ret;
- }
- }
-
-
-
- public function __isset($key)
- {
- $this->access($key);
- $return = array_key_exists($key, $this->data);
- if (!$return) {
- $this->access($key, TRUE);
- }
- return $return;
- }
-
-
-
- public function __unset($key)
- {
- unset($this->data[$key]);
- unset($this->modified[$key]);
- }
-
-
-
- public function access($key, $delete = FALSE)
- {
- if ($this->table->connection->cache && $this->table->access($key, $delete)) {
- $this->data = $this->table[$this->data[$this->table->primary]]->data;
- }
- }
-
-}
+data = $data;
+ $this->table = $table;
+ }
+
+
+
+ /**
+ * @internal
+ * @ignore
+ */
+ public function setTable(Selection $table)
+ {
+ $this->table = $table;
+ }
+
+
+
+ /**
+ * @internal
+ * @ignore
+ */
+ public function getTable()
+ {
+ return $this->table;
+ }
+
+
+
+ public function __toString()
+ {
+ try {
+ return (string) $this->getPrimary();
+ } catch (\Exception $e) {
+ trigger_error("Exception in " . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR);
+ }
+ }
+
+
+
+ /**
+ * @return array
+ */
+ public function toArray()
+ {
+ $this->access(NULL);
+ return $this->data;
+ }
+
+
+
+ /**
+ * Returns primary key value.
+ * @return mixed
+ */
+ public function getPrimary()
+ {
+ if (!isset($this->data[$this->table->getPrimary()])) {
+ throw new Nette\NotSupportedException("Table {$this->table->getName()} does not have any primary key.");
+ }
+ return $this[$this->table->getPrimary()];
+ }
+
+
+
+ /**
+ * Returns referenced row.
+ * @param string
+ * @param string
+ * @return ActiveRow or NULL if the row does not exist
+ */
+ public function ref($key, $throughColumn = NULL)
+ {
+ if (!$throughColumn) {
+ list($key, $throughColumn) = $this->table->getConnection()->getDatabaseReflection()->getBelongsToReference($this->table->getName(), $key);
+ }
+
+ return $this->getReference($key, $throughColumn);
+ }
+
+
+
+ /**
+ * Returns referencing rows.
+ * @param string
+ * @param string
+ * @return GroupedSelection
+ */
+ public function related($key, $throughColumn = NULL)
+ {
+ if (strpos($key, '.') !== FALSE) {
+ list($key, $throughColumn) = explode('.', $key);
+ } elseif (!$throughColumn) {
+ list($key, $throughColumn) = $this->table->getConnection()->getDatabaseReflection()->getHasManyReference($this->table->getName(), $key);
+ }
+
+ return $this->table->getReferencingTable($key, $throughColumn, $this[$this->table->getPrimary()]);
+ }
+
+
+
+ /**
+ * Updates row.
+ * @param array or NULL for all modified values
+ * @return int number of affected rows or FALSE in case of an error
+ */
+ public function update($data = NULL)
+ {
+ if ($data === NULL) {
+ $data = $this->modified;
+ }
+ return $this->table->getConnection()->table($this->table->getName())
+ ->where($this->table->getPrimary(), $this[$this->table->getPrimary()])
+ ->update($data);
+ }
+
+
+
+ /**
+ * Deletes row.
+ * @return int number of affected rows or FALSE in case of an error
+ */
+ public function delete()
+ {
+ return $this->table->getConnection()->table($this->table->getName())
+ ->where($this->table->getPrimary(), $this[$this->table->getPrimary()])
+ ->delete();
+ }
+
+
+
+ /********************* interface IteratorAggregate ****************d*g**/
+
+
+
+ public function getIterator()
+ {
+ $this->access(NULL);
+ return new \ArrayIterator($this->data);
+ }
+
+
+
+ /********************* interface ArrayAccess & magic accessors ****************d*g**/
+
+
+
+ /**
+ * Stores value in column.
+ * @param string column name
+ * @param string value
+ * @return void
+ */
+ public function offsetSet($key, $value)
+ {
+ $this->__set($key, $value);
+ }
+
+
+
+ /**
+ * Returns value of column.
+ * @param string column name
+ * @return string
+ */
+ public function offsetGet($key)
+ {
+ return $this->__get($key);
+ }
+
+
+
+ /**
+ * Tests if column exists.
+ * @param string column name
+ * @return bool
+ */
+ public function offsetExists($key)
+ {
+ return $this->__isset($key);
+ }
+
+
+
+ /**
+ * Removes column from data.
+ * @param string column name
+ * @return void
+ */
+ public function offsetUnset($key)
+ {
+ $this->__unset($key);
+ }
+
+
+
+ public function __set($key, $value)
+ {
+ $this->data[$key] = $value;
+ $this->modified[$key] = $value;
+ }
+
+
+
+ public function &__get($key)
+ {
+ $this->access($key);
+ if (array_key_exists($key, $this->data)) {
+ return $this->data[$key];
+ }
+
+ list($table, $column) = $this->table->getConnection()->getDatabaseReflection()->getBelongsToReference($this->table->getName(), $key);
+ $referenced = $this->getReference($table, $column);
+ if ($referenced !== FALSE) {
+ $this->access($key, FALSE);
+ return $referenced;
+ }
+
+ $this->access($key, NULL);
+ throw new Nette\MemberAccessException("Cannot read an undeclared column \"$key\".");
+ }
+
+
+
+ public function __isset($key)
+ {
+ $this->access($key);
+ if (array_key_exists($key, $this->data)) {
+ return isset($this->data[$key]);
+ }
+ $this->access($key, NULL);
+ return FALSE;
+ }
+
+
+
+ public function __unset($key)
+ {
+ unset($this->data[$key]);
+ unset($this->modified[$key]);
+ }
+
+
+
+ /**
+ * @internal
+ */
+ public function access($key, $cache = TRUE)
+ {
+ if ($this->table->getConnection()->getCache() && !isset($this->modified[$key]) && $this->table->access($key, $cache)) {
+ $id = (isset($this->data[$this->table->getPrimary()]) ? $this->data[$this->table->getPrimary()] : $this->data);
+ $this->data = $this->table[$id]->data;
+ }
+ }
+
+
+
+ protected function getReference($table, $column)
+ {
+ if (array_key_exists($column, $this->data)) {
+ $this->access($column);
+
+ $value = $this->data[$column];
+ $value = $value instanceof ActiveRow ? $value->getPrimary() : $value;
+
+ $referenced = $this->table->getReferencedTable($table, $column, !empty($this->modified[$column]));
+ $referenced = isset($referenced[$value]) ? $referenced[$value] : NULL; // referenced row may not exist
+
+ if (!empty($this->modified[$column])) { // cause saving changed column and prevent regenerating referenced table for $column
+ $this->modified[$column] = 0; // 0 fails on empty, pass on isset
+ }
+
+ return $referenced;
+ }
+
+ return FALSE;
+ }
+
+}
diff --git a/libs/Nette/Database/Table/GroupedSelection.php b/libs/Nette/Database/Table/GroupedSelection.php
index b0b18e0..7e11a6c 100644
--- a/libs/Nette/Database/Table/GroupedSelection.php
+++ b/libs/Nette/Database/Table/GroupedSelection.php
@@ -1,179 +1,257 @@
-connection);
- $this->refTable = $refTable;
- $this->through($column);
- }
-
-
-
- /**
- * Specify referencing column.
- * @param string
- * @return GroupedSelection provides a fluent interface
- */
- public function through($column)
- {
- $this->column = $column;
- $this->delimitedColumn = $this->refTable->connection->getSupplementalDriver()->delimite($this->column);
- return $this;
- }
-
-
-
- public function select($columns)
- {
- if (!$this->select) {
- $this->select[] = "$this->delimitedName.$this->delimitedColumn";
- }
- return parent::select($columns);
- }
-
-
-
- public function order($columns)
- {
- if (!$this->order) { // improve index utilization
- $this->order[] = "$this->delimitedName.$this->delimitedColumn"
- . (preg_match('~\\bDESC$~i', $columns) ? ' DESC' : '');
- }
- return parent::order($columns);
- }
-
-
-
- public function aggregation($function)
- {
- $join = $this->createJoins(implode(',', $this->conditions), TRUE) + $this->createJoins($function);
- $column = ($join ? "$this->table." : '') . $this->column;
- $query = "SELECT $function, $this->delimitedColumn FROM $this->delimitedName" . implode($join);
- if ($this->where) {
- $query .= ' WHERE (' . implode(') AND (', $this->where) . ')';
- }
- $query .= " GROUP BY $this->delimitedColumn";
- $aggregation = & $this->refTable->aggregation[$query];
- if ($aggregation === NULL) {
- $aggregation = array();
- foreach ($this->query($query, $this->parameters) as $row) {
- $aggregation[$row[$this->column]] = $row;
- }
- }
-
- foreach ($aggregation[$this->active] as $val) {
- return $val;
- }
- }
-
-
-
- public function insert($data)
- {
- if ($data instanceof \Traversable && !$data instanceof Selection) {
- $data = iterator_to_array($data);
- }
- if (is_array($data)) {
- $data[$this->column] = $this->active;
- }
- return parent::insert($data);
- }
-
-
-
- public function update($data)
- {
- $where = $this->where;
- $this->where[0] = "$this->delimitedColumn = " . $this->connection->quote($this->active);
- $return = parent::update($data);
- $this->where = $where;
- return $return;
- }
-
-
-
- public function delete()
- {
- $where = $this->where;
- $this->where[0] = "$this->delimitedColumn = " . $this->connection->quote($this->active);
- $return = parent::delete();
- $this->where = $where;
- return $return;
- }
-
-
-
- protected function execute()
- {
- if ($this->rows !== NULL) {
- return;
- }
-
- $referencing = & $this->refTable->referencing[$this->getSql()];
- if ($referencing === NULL) {
- $limit = $this->limit;
- $rows = count($this->refTable->rows);
- if ($this->limit && $rows > 1) {
- $this->limit = NULL;
- }
- parent::execute();
- $this->limit = $limit;
- $referencing = array();
- $offset = array();
- foreach ($this->rows as $key => $row) {
- $ref = & $referencing[$row[$this->column]];
- $skip = & $offset[$row[$this->column]];
- if ($limit === NULL || $rows <= 1 || (count($ref) < $limit && $skip >= $this->offset)) {
- $ref[$key] = $row;
- } else {
- unset($this->rows[$key]);
- }
- $skip++;
- unset($ref, $skip);
- }
- }
-
- $this->data = & $referencing[$this->active];
- if ($this->data === NULL) {
- $this->data = array();
- }
- }
-
-}
+connection);
+ $this->refTable = $refTable;
+ $this->column = $column;
+ }
+
+
+
+ /**
+ * Sets active group.
+ * @internal
+ * @param int primary key of grouped rows
+ * @return GroupedSelection
+ */
+ public function setActive($active)
+ {
+ $this->active = $active;
+ return $this;
+ }
+
+
+
+ /** @deprecated */
+ public function through($column)
+ {
+ trigger_error(__METHOD__ . '() is deprecated; use ' . __CLASS__ . '::related("' . $this->name . '", "' . $column . '") instead.', E_USER_WARNING);
+ $this->column = $column;
+ $this->delimitedColumn = $this->refTable->connection->getSupplementalDriver()->delimite($this->column);
+ return $this;
+ }
+
+
+
+ public function select($columns)
+ {
+ if (!$this->sqlBuilder->getSelect()) {
+ $this->sqlBuilder->addSelect("$this->name.$this->column");
+ }
+
+ return parent::select($columns);
+ }
+
+
+
+ public function order($columns)
+ {
+ if (!$this->sqlBuilder->getOrder()) {
+ // improve index utilization
+ $this->sqlBuilder->addOrder("$this->name.$this->column" . (preg_match('~\\bDESC$~i', $columns) ? ' DESC' : ''));
+ }
+
+ return parent::order($columns);
+ }
+
+
+
+ /********************* aggregations ****************d*g**/
+
+
+
+ public function aggregation($function)
+ {
+ $aggregation = & $this->getRefTable($refPath)->aggregation[$refPath . $function . $this->sqlBuilder->buildSelectQuery() . json_encode($this->sqlBuilder->getParameters())];
+
+ if ($aggregation === NULL) {
+ $aggregation = array();
+
+ $selection = $this->createSelectionInstance();
+ $selection->getSqlBuilder()->importConditions($this->getSqlBuilder());
+ $selection->select($function);
+ $selection->select("$this->name.$this->column");
+ $selection->group("$this->name.$this->column");
+
+ foreach ($selection as $row) {
+ $aggregation[$row[$this->column]] = $row;
+ }
+ }
+
+ if (isset($aggregation[$this->active])) {
+ foreach ($aggregation[$this->active] as $val) {
+ return $val;
+ }
+ }
+ }
+
+
+
+ public function count($column = NULL)
+ {
+ $return = parent::count($column);
+ return isset($return) ? $return : 0;
+ }
+
+
+
+ /********************* internal ****************d*g**/
+
+
+
+ protected function execute()
+ {
+ if ($this->rows !== NULL) {
+ return;
+ }
+
+ $hash = md5($this->sqlBuilder->buildSelectQuery() . json_encode($this->sqlBuilder->getParameters()));
+
+ $referencing = & $this->getRefTable($refPath)->referencing[$refPath . $hash];
+ $this->rows = & $referencing['rows'];
+ $this->referenced = & $referencing['refs'];
+ $this->accessed = & $referencing['accessed'];
+ $refData = & $referencing['data'];
+
+ if ($refData === NULL) {
+ $limit = $this->sqlBuilder->getLimit();
+ $rows = count($this->refTable->rows);
+ if ($limit && $rows > 1) {
+ $this->sqlBuilder->setLimit(NULL, NULL);
+ }
+ parent::execute();
+ $this->sqlBuilder->setLimit($limit, NULL);
+ $refData = array();
+ $offset = array();
+ foreach ($this->rows as $key => $row) {
+ $ref = & $refData[$row[$this->column]];
+ $skip = & $offset[$row[$this->column]];
+ if ($limit === NULL || $rows <= 1 || (count($ref) < $limit && $skip >= $this->sqlBuilder->getOffset())) {
+ $ref[$key] = $row;
+ } else {
+ unset($this->rows[$key]);
+ }
+ $skip++;
+ unset($ref, $skip);
+ }
+ }
+
+ $this->data = & $refData[$this->active];
+ if ($this->data === NULL) {
+ $this->data = array();
+ } else {
+ foreach ($this->data as $row) {
+ $row->setTable($this); // injects correct parent GroupedSelection
+ }
+ reset($this->data);
+ }
+ }
+
+
+
+ protected function getRefTable(& $refPath)
+ {
+ $refObj = $this->refTable;
+ $refPath = $this->name . '.';
+ while ($refObj instanceof GroupedSelection) {
+ $refPath .= $refObj->name . '.';
+ $refObj = $refObj->refTable;
+ }
+
+ return $refObj;
+ }
+
+
+
+ /********************* manipulation ****************d*g**/
+
+
+
+ public function insert($data)
+ {
+ if ($data instanceof \Traversable && !$data instanceof Selection) {
+ $data = iterator_to_array($data);
+ }
+
+ if (Nette\Utils\Validators::isList($data)) {
+ foreach (array_keys($data) as $key) {
+ $data[$key][$this->column] = $this->active;
+ }
+ } else {
+ $data[$this->column] = $this->active;
+ }
+
+ return parent::insert($data);
+ }
+
+
+
+ public function update($data)
+ {
+ $builder = $this->sqlBuilder;
+
+ $this->sqlBuilder = new SqlBuilder($this);
+ $this->where($this->column, $this->active);
+ $return = parent::update($data);
+
+ $this->sqlBuilder = $builder;
+ return $return;
+ }
+
+
+
+ public function delete()
+ {
+ $builder = $this->sqlBuilder;
+
+ $this->sqlBuilder = new SqlBuilder($this);
+ $this->where($this->column, $this->active);
+ $return = parent::delete();
+
+ $this->sqlBuilder = $builder;
+ return $return;
+ }
+
+}
diff --git a/libs/Nette/Database/Table/Selection.php b/libs/Nette/Database/Table/Selection.php
index 37d3b1d..edf505e 100644
--- a/libs/Nette/Database/Table/Selection.php
+++ b/libs/Nette/Database/Table/Selection.php
@@ -1,783 +1,827 @@
- TableRow] readed from database */
- protected $rows;
-
- /** @var array of [primary key => TableRow] modifiable */
- protected $data;
-
- /** @var array of column to select */
- protected $select = array();
-
- /** @var array of where conditions */
- protected $where = array();
-
- /** @var array of where conditions for caching */
- protected $conditions = array();
-
- /** @var array of parameters passed to where conditions */
- protected $parameters = array();
-
- /** @var array or columns to order by */
- protected $order = array();
-
- /** @var int number of rows to fetch */
- protected $limit = NULL;
-
- /** @var int first row to fetch */
- protected $offset = NULL;
-
- /** @var string columns to grouping */
- protected $group = '';
-
- /** @var string grouping condition */
- protected $having = '';
-
- /** @var array of referenced TableSelection */
- protected $referenced = array();
-
- /** @var array of [sql => [column => [key => TableRow]]] used by GroupedTableSelection */
- protected $referencing = array();
-
- /** @var array of [sql => [key => TableRow]] used by GroupedTableSelection */
- protected $aggregation = array();
-
- /** @var array of touched columns */
- protected $accessed;
-
- /** @var array of earlier touched columns */
- protected $prevAccessed;
-
- /** @var array of primary key values */
- protected $keys = array();
-
- /** @var string */
- protected $delimitedName;
-
- /** @var string */
- protected $delimitedPrimary;
-
-
-
- /**
- * @param string
- * @param
- */
- public function __construct($table, Nette\Database\Connection $connection)
- {
- $this->name = $table;
- $this->connection = $connection;
- $this->primary = $this->getPrimary($table);
- $this->delimitedName = $connection->getSupplementalDriver()->delimite($this->name);
- $this->delimitedPrimary = $connection->getSupplementalDriver()->delimite($this->primary);
- }
-
-
-
- /**
- * Saves data to cache and empty result.
- */
- public function __destruct()
- {
- if ($this->connection->cache && !$this->select && $this->rows !== NULL) {
- $accessed = $this->accessed;
- if (is_array($accessed)) {
- $accessed = array_filter($accessed);
- }
- $this->connection->cache->save(array(__CLASS__, $this->name, $this->conditions), $accessed);
- }
- $this->rows = NULL;
- }
-
-
-
- /**
- * Returns row specified by primary key.
- * @param mixed
- * @return ActiveRow or NULL if there is no such row
- */
- public function get($key)
- {
- // can also use array_pop($this->where) instead of clone to save memory
- $clone = clone $this;
- $clone->where($this->delimitedPrimary, $key);
- return $clone->fetch();
- }
-
-
-
- /**
- * Adds select clause, more calls appends to the end.
- * @param string for example "column, MD5(column) AS column_md5"
- * @return Selection provides a fluent interface
- */
- public function select($columns)
- {
- $this->__destruct();
- $this->select[] = $this->tryDelimite($columns);
- return $this;
- }
-
-
-
- /**
- * Selects by primary key.
- * @param mixed
- * @return Selection provides a fluent interface
- */
- public function find($key)
- {
- return $this->where($this->delimitedPrimary, $key);
- }
-
-
-
- /**
- * Adds where condition, more calls appends with AND.
- * @param string condition possibly containing ?
- * @param mixed
- * @param mixed ...
- * @return Selection provides a fluent interface
- */
- public function where($condition, $parameters = array())
- {
- if (is_array($condition)) { // where(array('column1' => 1, 'column2 > ?' => 2))
- foreach ($condition as $key => $val) {
- $this->where($key, $val);
- }
- return $this;
- }
-
- $this->__destruct();
-
- $this->conditions[] = $condition = $this->tryDelimite($condition);
-
- $args = func_num_args();
- if ($args !== 2 || strpbrk($condition, '?:')) { // where('column < ? OR column > ?', array(1, 2))
- if ($args !== 2 || !is_array($parameters)) { // where('column < ? OR column > ?', 1, 2)
- $parameters = func_get_args();
- array_shift($parameters);
- }
- $this->parameters = array_merge($this->parameters, $parameters);
-
- } elseif ($parameters === NULL) { // where('column', NULL)
- $condition .= ' IS NULL';
-
- } elseif ($parameters instanceof Selection) { // where('column', $db->$table())
- $clone = clone $parameters;
- if (!$clone->select) {
- $clone->select = array($this->getPrimary($clone->name));
- }
- if ($this->connection->getAttribute(PDO::ATTR_DRIVER_NAME) !== 'mysql') {
- $condition .= " IN ($clone)";
- } else {
- $in = array();
- foreach ($clone as $row) {
- $this->parameters[] = array_values(iterator_to_array($row));
- $in[] = (count($row) === 1 ? '?' : '(?)');
- }
- $condition .= ' IN (' . ($in ? implode(', ', $in) : 'NULL') . ')';
- }
-
- } elseif (!is_array($parameters)) { // where('column', 'x')
- $condition .= ' = ?';
- $this->parameters[] = $parameters;
-
- } else { // where('column', array(1, 2))
- if ($parameters) {
- $condition .= " IN (?)";
- $this->parameters[] = $parameters;
- } else {
- $condition .= " IN (NULL)";
- }
- }
-
- $this->where[] = $condition;
- return $this;
- }
-
-
-
- /**
- * Adds order clause, more calls appends to the end.
- * @param string for example 'column1, column2 DESC'
- * @return Selection provides a fluent interface
- */
- public function order($columns)
- {
- $this->rows = NULL;
- $this->order[] = $this->tryDelimite($columns);
- return $this;
- }
-
-
-
- /**
- * Sets limit clause, more calls rewrite old values.
- * @param int
- * @param int
- * @return Selection provides a fluent interface
- */
- public function limit($limit, $offset = NULL)
- {
- $this->rows = NULL;
- $this->limit = $limit;
- $this->offset = $offset;
- return $this;
- }
-
-
-
- /**
- * Sets group clause, more calls rewrite old values.
- * @param string
- * @param string
- * @return Selection provides a fluent interface
- */
- public function group($columns, $having = '')
- {
- $this->__destruct();
- $this->group = $this->tryDelimite($columns);
- $this->having = $having;
- return $this;
- }
-
-
-
- /**
- * Executes aggregation function.
- * @param string
- * @return string
- */
- public function aggregation($function)
- {
- $join = $this->createJoins(implode(',', $this->conditions), TRUE) + $this->createJoins($function);
- $query = "SELECT $function FROM $this->delimitedName" . implode($join);
- if ($this->where) {
- $query .= ' WHERE (' . implode(') AND (', $this->where) . ')';
- }
- foreach ($this->query($query)->fetch() as $val) {
- return $val;
- }
- }
-
-
-
- /**
- * Counts number of rows.
- * @param string
- * @return int
- */
- public function count($column = '')
- {
- if (!$column) {
- $this->execute();
- return count($this->data);
- }
- return $this->aggregation("COUNT({$this->tryDelimite($column)})");
- }
-
-
-
- /**
- * Returns minimum value from a column.
- * @param string
- * @return int
- */
- public function min($column)
- {
- return $this->aggregation("MIN({$this->tryDelimite($column)})");
- }
-
-
-
- /**
- * Returns maximum value from a column.
- * @param string
- * @return int
- */
- public function max($column)
- {
- return $this->aggregation("MAX({$this->tryDelimite($column)})");
- }
-
-
-
- /**
- * Returns sum of values in a column.
- * @param string
- * @return int
- */
- public function sum($column)
- {
- return $this->aggregation("SUM({$this->tryDelimite($column)})");
- }
-
-
-
- /**
- * Returns SQL query.
- * @return string
- */
- public function getSql()
- {
- $join = $this->createJoins(implode(',', $this->conditions), TRUE)
- + $this->createJoins(implode(',', $this->select) . ",$this->group,$this->having," . implode(',', $this->order));
-
- if ($this->rows === NULL && $this->connection->cache && !is_string($this->prevAccessed)) {
- $this->accessed = $this->prevAccessed = $this->connection->cache->load(array(__CLASS__, $this->name, $this->conditions));
- }
-
- $prefix = $join ? "$this->delimitedName." : '';
- if ($this->select) {
- $cols = implode(', ', $this->select);
-
- } elseif ($this->prevAccessed) {
- $cols = $prefix . implode(', ' . $prefix, array_map(array($this->connection->getSupplementalDriver(), 'delimite'), array_keys($this->prevAccessed)));
-
- } else {
- $cols = $prefix . '*';
- }
-
- return "SELECT{$this->topString()} $cols FROM $this->delimitedName" . implode($join) . $this->whereString();
- }
-
-
-
- protected function createJoins($val, $inner = FALSE)
- {
- $supplementalDriver = $this->connection->getSupplementalDriver();
- $joins = array();
- preg_match_all('~\\b(\\w+)\\.(\\w+)(\\s+IS\\b|\\s*<=>)?~i', $val, $matches, PREG_SET_ORDER);
- foreach ($matches as $match) {
- $name = $match[1];
- if ($name !== $this->name) { // case-sensitive
- $table = $this->connection->databaseReflection->getReferencedTable($name, $this->name);
- $column = $this->connection->databaseReflection->getReferencedColumn($name, $this->name);
- $primary = $this->getPrimary($table);
- $joins[$name] = ' ' . (!isset($joins[$name]) && $inner && !isset($match[3]) ? 'INNER' : 'LEFT')
- . ' JOIN ' . $supplementalDriver->delimite($table)
- . ($table !== $name ? ' AS ' . $supplementalDriver->delimite($name) : '')
- . " ON $this->delimitedName." . $supplementalDriver->delimite($column)
- . ' = ' . $supplementalDriver->delimite($name) . '.' . $supplementalDriver->delimite($primary);
- }
- }
- return $joins;
- }
-
-
-
- /**
- * Executes built query.
- * @return NULL
- */
- protected function execute()
- {
- if ($this->rows !== NULL) {
- return;
- }
-
- try {
- $result = $this->query($this->getSql());
-
- } catch (\PDOException $exception) {
- if (!$this->select && $this->prevAccessed) {
- $this->prevAccessed = '';
- $this->accessed = array();
- $result = $this->query($this->getSql());
- } else {
- throw $exception;
- }
- }
-
- $this->rows = array();
- $result->setFetchMode(PDO::FETCH_ASSOC);
- foreach ($result as $key => $row) {
- $row = $result->normalizeRow($row);
- $this->rows[isset($row[$this->primary]) ? $row[$this->primary] : $key] = new ActiveRow($row, $this);
- }
- $this->data = $this->rows;
-
- if (isset($row[$this->primary]) && !is_string($this->accessed)) {
- $this->accessed[$this->primary] = TRUE;
- }
- }
-
-
-
- protected function whereString()
- {
- $return = '';
- $driver = $this->connection->getAttribute(PDO::ATTR_DRIVER_NAME);
- $where = $this->where;
- if ($this->limit !== NULL && $driver === 'oci') {
- $where[] = ($this->offset ? "rownum > $this->offset AND " : '') . 'rownum <= ' . ($this->limit + $this->offset);
- }
- if ($where) {
- $return .= ' WHERE (' . implode(') AND (', $where) . ')';
- }
- if ($this->group) {
- $return .= " GROUP BY $this->group";
- }
- if ($this->having) {
- $return .= " HAVING $this->having";
- }
- if ($this->order) {
- $return .= ' ORDER BY ' . implode(', ', $this->order);
- }
- if ($this->limit !== NULL && $driver !== 'oci' && $driver !== 'dblib') {
- $return .= " LIMIT $this->limit";
- if ($this->offset !== NULL) {
- $return .= " OFFSET $this->offset";
- }
- }
- return $return;
- }
-
-
-
- protected function topString()
- {
- if ($this->limit !== NULL && $this->connection->getAttribute(PDO::ATTR_DRIVER_NAME) === 'dblib') {
- return " TOP ($this->limit)"; //! offset is not supported
- }
- return '';
- }
-
-
-
- protected function tryDelimite($s)
- {
- return preg_match('#^[a-z_][a-z0-9_.]*$#i', $s) // is identifier?
- ? implode('.', array_map(array($this->connection->getSupplementalDriver(), 'delimite'), explode('.', $s)))
- : $s;
- }
-
-
-
- protected function query($query)
- {
- return $this->connection->queryArgs($query, $this->parameters);
- }
-
-
-
- public function access($key, $delete = FALSE)
- {
- if ($delete) {
- if (is_array($this->accessed)) {
- $this->accessed[$key] = FALSE;
- }
- return FALSE;
- }
-
- if ($key === NULL) {
- $this->accessed = '';
-
- } elseif (!is_string($this->accessed)) {
- $this->accessed[$key] = TRUE;
- }
-
- if (!$this->select && $this->prevAccessed && ($key === NULL || !isset($this->prevAccessed[$key]))) {
- $this->prevAccessed = '';
- $this->rows = NULL;
- return TRUE;
- }
- return FALSE;
- }
-
-
-
- /********************* manipulation ****************d*g**/
-
-
-
- /**
- * Inserts row in a table.
- * @param mixed array($column => $value)|Traversable for single row insert or TableSelection|string for INSERT ... SELECT
- * @return ActiveRow or FALSE in case of an error or number of affected rows for INSERT ... SELECT
- */
- public function insert($data)
- {
- if ($data instanceof Selection) {
- $data = $data->getSql();
-
- } elseif ($data instanceof \Traversable) {
- $data = iterator_to_array($data);
- }
-
- $return = $this->connection->query("INSERT INTO $this->delimitedName", $data);
-
- $this->rows = NULL;
- if (!is_array($data)) {
- return $return->rowCount();
- }
-
- if (!isset($data[$this->primary]) && ($id = $this->connection->lastInsertId())) {
- $data[$this->primary] = $id;
- }
- return new ActiveRow($data, $this);
- }
-
-
-
- /**
- * Updates all rows in result set.
- * @param array ($column => $value)
- * @return int number of affected rows or FALSE in case of an error
- */
- public function update($data)
- {
- if ($data instanceof \Traversable) {
- $data = iterator_to_array($data);
-
- } elseif (!is_array($data)) {
- throw new Nette\InvalidArgumentException;
- }
-
- if (!$data) {
- return 0;
- }
- // joins in UPDATE are supported only in MySQL
- return $this->connection->queryArgs(
- 'UPDATE' . $this->topString() . " $this->delimitedName SET ?" . $this->whereString(),
- array_merge(array($data), $this->parameters)
- )->rowCount();
- }
-
-
-
- /**
- * Deletes all rows in result set.
- * @return int number of affected rows or FALSE in case of an error
- */
- public function delete()
- {
- return $this->query(
- 'DELETE' . $this->topString() . " FROM $this->delimitedName" . $this->whereString()
- )->rowCount();
- }
-
-
-
- /********************* references ****************d*g**/
-
-
-
- /**
- * Returns referenced row.
- * @param string
- * @return ActiveRow or NULL if the row does not exist
- */
- public function getReferencedTable($name, & $column = NULL)
- {
- $column = $this->connection->databaseReflection->getReferencedColumn($name, $this->name);
- $referenced = & $this->referenced[$name];
- if ($referenced === NULL) {
- $keys = array();
- foreach ($this->rows as $row) {
- if ($row[$column] !== NULL) {
- $keys[$row[$column]] = NULL;
- }
- }
- if ($keys) {
- $table = $this->connection->databaseReflection->getReferencedTable($name, $this->name);
- $referenced = new Selection($table, $this->connection);
- $referenced->where($table . '.' . $this->getPrimary($table), array_keys($keys));
- } else {
- $referenced = array();
- }
- }
- return $referenced;
- }
-
-
-
- /**
- * Returns referencing rows.
- * @param string table name
- * @return GroupedSelection
- */
- public function getReferencingTable($table)
- {
- $column = $this->connection->databaseReflection->getReferencingColumn($table, $this->name);
- $referencing = new GroupedSelection($table, $this, $column);
- $referencing->where("$table.$column", array_keys((array) $this->rows)); // (array) - is NULL after insert
- return $referencing;
- }
-
-
-
- private function getPrimary($table)
- {
- return $this->connection->databaseReflection->getPrimary($table);
- }
-
-
-
- /********************* interface Iterator ****************d*g**/
-
-
-
- public function rewind()
- {
- $this->execute();
- $this->keys = array_keys($this->data);
- reset($this->keys);
- }
-
-
-
- /** @return ActiveRow */
- public function current()
- {
- return $this->data[current($this->keys)];
- }
-
-
-
- /**
- * @return string row ID
- */
- public function key()
- {
- return current($this->keys);
- }
-
-
-
- public function next()
- {
- next($this->keys);
- }
-
-
-
- public function valid()
- {
- return current($this->keys) !== FALSE;
- }
-
-
-
- /********************* interface ArrayAccess ****************d*g**/
-
-
-
- /**
- * Mimic row.
- * @param string row ID
- * @param ActiveRow
- * @return NULL
- */
- public function offsetSet($key, $value)
- {
- $this->execute();
- $this->data[$key] = $value;
- }
-
-
-
- /**
- * Returns specified row.
- * @param string row ID
- * @return ActiveRow or NULL if there is no such row
- */
- public function offsetGet($key)
- {
- $this->execute();
- return $this->data[$key];
- }
-
-
-
- /**
- * Tests if row exists.
- * @param string row ID
- * @return bool
- */
- public function offsetExists($key)
- {
- $this->execute();
- return isset($this->data[$key]);
- }
-
-
-
- /**
- * Removes row from result set.
- * @param string row ID
- * @return NULL
- */
- public function offsetUnset($key)
- {
- $this->execute();
- unset($this->data[$key]);
- }
-
-
-
- /**
- * Returns next row of result.
- * @return ActiveRow or FALSE if there is no row
- */
- public function fetch()
- {
- $this->execute();
- $return = current($this->data);
- next($this->data);
- return $return;
- }
-
-
-
- /**
- * Returns all rows as associative array.
- * @param string
- * @param string column name used for an array value or an empty string for the whole row
- * @return array
- */
- public function fetchPairs($key, $value = '')
- {
- $return = array();
- // no $clone->select = array($key, $value) to allow efficient caching with repetitive calls with different parameters
- foreach ($this as $row) {
- $return[$row[$key]] = ($value !== '' ? $row[$value] : $row);
- }
- return $return;
- }
-
-}
+ ActiveRow] format */
+ protected $rows;
+
+ /** @var ActiveRow[] modifiable data in [primary key => ActiveRow] format */
+ protected $data;
+
+ /** @var Selection[] */
+ protected $referenced = array();
+
+ /** @var array of [sqlQuery-hash => grouped data]; used by GroupedSelection */
+ protected $referencing = array();
+
+ /** @var GroupedSelection[] cached array of GroupedSelection prototypes */
+ protected $referencingPrototype = array();
+
+ /** @var array of [conditions => [key => ActiveRow]]; used by GroupedSelection */
+ protected $aggregation = array();
+
+ /** @var array of touched columns */
+ protected $accessed;
+
+ /** @var array of earlier touched columns */
+ protected $prevAccessed;
+
+ /** @var bool should instance observe accessed columns caching */
+ protected $observeCache = FALSE;
+
+ /** @var bool recheck referencing keys */
+ protected $checkReferenced = FALSE;
+
+ /** @var array of primary key values */
+ protected $keys = array();
+
+
+
+ /**
+ * Creates filtered table representation.
+ * @param string database table name
+ * @param Nette\Database\Connection
+ */
+ public function __construct($table, Nette\Database\Connection $connection)
+ {
+ $this->name = $table;
+ $this->connection = $connection;
+ $this->primary = $connection->getDatabaseReflection()->getPrimary($table);
+ $this->sqlBuilder = new SqlBuilder($this);
+ }
+
+
+
+ public function __destruct()
+ {
+ $this->saveCacheState();
+ }
+
+
+
+ public function __clone()
+ {
+ $this->sqlBuilder = clone $this->sqlBuilder;
+ $this->sqlBuilder->setSelection($this);
+ }
+
+
+
+ /**
+ * @return Nette\Database\Connection
+ */
+ public function getConnection()
+ {
+ return $this->connection;
+ }
+
+
+
+ /**
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+
+
+ /**
+ * @return string
+ */
+ public function getPrimary()
+ {
+ return $this->primary;
+ }
+
+
+
+ /**
+ * @return string
+ */
+ public function getPrimarySequence()
+ {
+ if ($this->primarySequence === FALSE) {
+ $this->primarySequence = NULL;
+
+ $driver = $this->connection->getSupplementalDriver();
+ if ($driver->isSupported(ISupplementalDriver::SUPPORT_SEQUENCE)) {
+ foreach ($driver->getColumns($this->name) as $column) {
+ if ($column['name'] === $this->primary) {
+ $this->primarySequence = $column['vendor']['sequence'];
+ break;
+ }
+ }
+ }
+ }
+
+ return $this->primarySequence;
+ }
+
+
+
+ /**
+ * @param string
+ * @return Selection provides a fluent interface
+ */
+ public function setPrimarySequence($sequence)
+ {
+ $this->primarySequence = $sequence;
+ return $this;
+ }
+
+
+
+ /**
+ * @return string
+ */
+ public function getSql()
+ {
+ return $this->sqlBuilder->buildSelectQuery();
+ }
+
+
+
+ /**
+ * Loads cache of previous accessed columns and returns it.
+ * @internal
+ * @return array|false
+ */
+ public function getPreviousAccessed()
+ {
+ $cache = $this->connection->getCache();
+ if ($this->rows === NULL && $cache && !is_string($this->prevAccessed)) {
+ $this->accessed = $this->prevAccessed = $cache->load(array(__CLASS__, $this->name, $this->sqlBuilder->getConditions()));
+ }
+
+ return $this->prevAccessed;
+ }
+
+
+
+ /**
+ * @internal
+ * @return SqlBuilder
+ */
+ public function getSqlBuilder()
+ {
+ return $this->sqlBuilder;
+ }
+
+
+
+ /********************* quick access ****************d*g**/
+
+
+
+ /**
+ * Returns row specified by primary key.
+ * @param mixed primary key
+ * @return ActiveRow or FALSE if there is no such row
+ */
+ public function get($key)
+ {
+ $clone = clone $this;
+ $clone->where($this->primary, $key);
+ return $clone->fetch();
+ }
+
+
+
+ /**
+ * Returns next row of result.
+ * @return ActiveRow or FALSE if there is no row
+ */
+ public function fetch()
+ {
+ $this->execute();
+ $return = current($this->data);
+ next($this->data);
+ return $return;
+ }
+
+
+
+ /**
+ * Returns all rows as associative array.
+ * @param string
+ * @param string column name used for an array value or NULL for the whole row
+ * @return array
+ */
+ public function fetchPairs($key, $value = NULL)
+ {
+ $return = array();
+ foreach ($this as $row) {
+ $return[is_object($row[$key]) ? (string) $row[$key] : $row[$key]] = ($value ? $row[$value] : $row);
+ }
+ return $return;
+ }
+
+
+
+ /********************* sql selectors ****************d*g**/
+
+
+
+ /**
+ * Adds select clause, more calls appends to the end.
+ * @param string for example "column, MD5(column) AS column_md5"
+ * @return Selection provides a fluent interface
+ */
+ public function select($columns)
+ {
+ $this->emptyResultSet();
+ $this->sqlBuilder->addSelect($columns);
+ return $this;
+ }
+
+
+
+ /**
+ * Selects by primary key.
+ * @param mixed
+ * @return Selection provides a fluent interface
+ */
+ public function find($key)
+ {
+ return $this->where($this->primary, $key);
+ }
+
+
+
+ /**
+ * Adds where condition, more calls appends with AND.
+ * @param string condition possibly containing ?
+ * @param mixed
+ * @param mixed ...
+ * @return Selection provides a fluent interface
+ */
+ public function where($condition, $parameters = array())
+ {
+ if (is_array($condition)) { // where(array('column1' => 1, 'column2 > ?' => 2))
+ foreach ($condition as $key => $val) {
+ if (is_int($key)) {
+ $this->where($val); // where('full condition')
+ } else {
+ $this->where($key, $val); // where('column', 1)
+ }
+ }
+ return $this;
+ }
+
+ $args = func_get_args();
+ if (call_user_func_array(array($this->sqlBuilder, 'addWhere'), $args)) {
+ $this->emptyResultSet();
+ }
+
+ return $this;
+ }
+
+
+
+ /**
+ * Adds order clause, more calls appends to the end.
+ * @param string for example 'column1, column2 DESC'
+ * @return Selection provides a fluent interface
+ */
+ public function order($columns)
+ {
+ $this->emptyResultSet();
+ $this->sqlBuilder->addOrder($columns);
+ return $this;
+ }
+
+
+
+ /**
+ * Sets limit clause, more calls rewrite old values.
+ * @param int
+ * @param int
+ * @return Selection provides a fluent interface
+ */
+ public function limit($limit, $offset = NULL)
+ {
+ $this->emptyResultSet();
+ $this->sqlBuilder->setLimit($limit, $offset);
+ return $this;
+ }
+
+
+
+ /**
+ * Sets offset using page number, more calls rewrite old values.
+ * @param int
+ * @param int
+ * @return Selection provides a fluent interface
+ */
+ public function page($page, $itemsPerPage)
+ {
+ return $this->limit($itemsPerPage, ($page - 1) * $itemsPerPage);
+ }
+
+
+
+ /**
+ * Sets group clause, more calls rewrite old values.
+ * @param string
+ * @param string
+ * @return Selection provides a fluent interface
+ */
+ public function group($columns, $having = NULL)
+ {
+ $this->emptyResultSet();
+ $this->sqlBuilder->setGroup($columns, $having);
+ return $this;
+ }
+
+
+
+ /********************* aggregations ****************d*g**/
+
+
+
+ /**
+ * Executes aggregation function.
+ * @param string select call in "FUNCTION(column)" format
+ * @return string
+ */
+ public function aggregation($function)
+ {
+ $selection = $this->createSelectionInstance();
+ $selection->getSqlBuilder()->importConditions($this->getSqlBuilder());
+ $selection->select($function);
+ foreach ($selection->fetch() as $val) {
+ return $val;
+ }
+ }
+
+
+
+ /**
+ * Counts number of rows.
+ * @param string if it is not provided returns count of result rows, otherwise runs new sql counting query
+ * @return int
+ */
+ public function count($column = NULL)
+ {
+ if (!$column) {
+ $this->execute();
+ return count($this->data);
+ }
+ return $this->aggregation("COUNT($column)");
+ }
+
+
+
+ /**
+ * Returns minimum value from a column.
+ * @param string
+ * @return int
+ */
+ public function min($column)
+ {
+ return $this->aggregation("MIN($column)");
+ }
+
+
+
+ /**
+ * Returns maximum value from a column.
+ * @param string
+ * @return int
+ */
+ public function max($column)
+ {
+ return $this->aggregation("MAX($column)");
+ }
+
+
+
+ /**
+ * Returns sum of values in a column.
+ * @param string
+ * @return int
+ */
+ public function sum($column)
+ {
+ return $this->aggregation("SUM($column)");
+ }
+
+
+
+ /********************* internal ****************d*g**/
+
+
+
+ protected function execute()
+ {
+ if ($this->rows !== NULL) {
+ return;
+ }
+
+ $this->observeCache = TRUE;
+
+ try {
+ $result = $this->query($this->sqlBuilder->buildSelectQuery());
+
+ } catch (\PDOException $exception) {
+ if (!$this->sqlBuilder->getSelect() && $this->prevAccessed) {
+ $this->prevAccessed = '';
+ $this->accessed = array();
+ $result = $this->query($this->sqlBuilder->buildSelectQuery());
+ } else {
+ throw $exception;
+ }
+ }
+
+ $this->rows = array();
+ $result->setFetchMode(PDO::FETCH_ASSOC);
+ foreach ($result as $key => $row) {
+ $row = $result->normalizeRow($row);
+ $this->rows[isset($row[$this->primary]) ? $row[$this->primary] : $key] = $this->createRow($row);
+ }
+ $this->data = $this->rows;
+
+ if (isset($row[$this->primary]) && !is_string($this->accessed)) {
+ $this->accessed[$this->primary] = TRUE;
+ }
+ }
+
+
+
+ protected function createRow(array $row)
+ {
+ return new ActiveRow($row, $this);
+ }
+
+
+
+ protected function createSelectionInstance($table = NULL)
+ {
+ return new Selection($table ?: $this->name, $this->connection);
+ }
+
+
+
+ protected function createGroupedSelectionInstance($table, $column)
+ {
+ return new GroupedSelection($this, $table, $column);
+ }
+
+
+
+ protected function query($query)
+ {
+ return $this->connection->queryArgs($query, $this->sqlBuilder->getParameters());
+ }
+
+
+
+ protected function emptyResultSet()
+ {
+ if ($this->rows === NULL) {
+ return;
+ }
+
+ $this->rows = NULL;
+ $this->saveCacheState();
+ }
+
+
+
+ protected function saveCacheState()
+ {
+ if ($this->observeCache && ($cache = $this->connection->getCache()) && !$this->sqlBuilder->getSelect() && $this->accessed != $this->prevAccessed) {
+ $cache->save(array(__CLASS__, $this->name, $this->sqlBuilder->getConditions()), $this->accessed);
+ }
+ }
+
+
+
+ /**
+ * Returns Selection parent for caching.
+ * @return Selection
+ */
+ protected function getRefTable(& $refPath)
+ {
+ return $this;
+ }
+
+
+
+ /**
+ * @internal
+ * @param string column name
+ * @param bool|NULL TRUE - cache, FALSE - don't cache, NULL - remove
+ * @return bool
+ */
+ public function access($key, $cache = TRUE)
+ {
+ if ($cache === NULL) {
+ if (is_array($this->accessed)) {
+ $this->accessed[$key] = FALSE;
+ }
+ return FALSE;
+ }
+
+ if ($key === NULL) {
+ $this->accessed = '';
+
+ } elseif (!is_string($this->accessed)) {
+ $this->accessed[$key] = $cache;
+ }
+
+ if ($cache && !$this->sqlBuilder->getSelect() && $this->prevAccessed && ($key === NULL || !isset($this->prevAccessed[$key]))) {
+ $this->prevAccessed = '';
+ $this->emptyResultSet();
+ return TRUE;
+ }
+
+ return FALSE;
+ }
+
+
+
+ /********************* manipulation ****************d*g**/
+
+
+
+ /**
+ * Inserts row in a table.
+ * @param mixed array($column => $value)|Traversable for single row insert or Selection|string for INSERT ... SELECT
+ * @return ActiveRow or FALSE in case of an error or number of affected rows for INSERT ... SELECT
+ */
+ public function insert($data)
+ {
+ if ($data instanceof Selection) {
+ $data = $data->getSql();
+
+ } elseif ($data instanceof \Traversable) {
+ $data = iterator_to_array($data);
+ }
+
+ $return = $this->connection->query($this->sqlBuilder->buildInsertQuery(), $data);
+ $this->checkReferenced = TRUE;
+
+ if (!is_array($data)) {
+ return $return->rowCount();
+ }
+
+ if (!isset($data[$this->primary]) && ($id = $this->connection->lastInsertId($this->getPrimarySequence()))) {
+ $data[$this->primary] = $id;
+ return $this->rows[$id] = $this->createRow($data);
+
+ } else {
+ return $this->createRow($data);
+
+ }
+ }
+
+
+
+ /**
+ * Updates all rows in result set.
+ * Joins in UPDATE are supported only in MySQL
+ * @param array|\Traversable ($column => $value)
+ * @return int number of affected rows or FALSE in case of an error
+ */
+ public function update($data)
+ {
+ if ($data instanceof \Traversable) {
+ $data = iterator_to_array($data);
+
+ } elseif (!is_array($data)) {
+ throw new Nette\InvalidArgumentException;
+ }
+
+ if (!$data) {
+ return 0;
+ }
+
+ return $this->connection->queryArgs(
+ $this->sqlBuilder->buildUpdateQuery(),
+ array_merge(array($data), $this->sqlBuilder->getParameters())
+ )->rowCount();
+ }
+
+
+
+ /**
+ * Deletes all rows in result set.
+ * @return int number of affected rows or FALSE in case of an error
+ */
+ public function delete()
+ {
+ return $this->query($this->sqlBuilder->buildDeleteQuery())->rowCount();
+ }
+
+
+
+ /********************* references ****************d*g**/
+
+
+
+ /**
+ * Returns referenced row.
+ * @param string
+ * @param string
+ * @param bool checks if rows contains the same primary value relations
+ * @return Selection or array() if the row does not exist
+ */
+ public function getReferencedTable($table, $column, $checkReferenced = FALSE)
+ {
+ $referenced = & $this->getRefTable($refPath)->referenced[$refPath . "$table.$column"];
+ if ($referenced === NULL || $checkReferenced || $this->checkReferenced) {
+ $this->execute();
+ $this->checkReferenced = FALSE;
+ $keys = array();
+ foreach ($this->rows as $row) {
+ if ($row[$column] === NULL)
+ continue;
+
+ $key = $row[$column] instanceof ActiveRow ? $row[$column]->getPrimary() : $row[$column];
+ $keys[$key] = TRUE;
+ }
+
+ if ($referenced !== NULL && array_keys($keys) === array_keys($referenced->rows)) {
+ return $referenced;
+ }
+
+ if ($keys) {
+ $referenced = $this->createSelectionInstance($table);
+ $referenced->where($referenced->primary, array_keys($keys));
+ } else {
+ $referenced = array();
+ }
+ }
+
+ return $referenced;
+ }
+
+
+
+ /**
+ * Returns referencing rows.
+ * @param string
+ * @param string
+ * @param int primary key
+ * @return GroupedSelection
+ */
+ public function getReferencingTable($table, $column, $active = NULL)
+ {
+ $prototype = & $this->getRefTable($refPath)->referencingPrototype[$refPath . "$table.$column"];
+ if (!$prototype) {
+ $prototype = $this->createGroupedSelectionInstance($table, $column);
+ $prototype->where("$table.$column", array_keys((array) $this->rows));
+ }
+
+ $clone = clone $prototype;
+ $clone->setActive($active);
+ return $clone;
+ }
+
+
+
+ /********************* interface Iterator ****************d*g**/
+
+
+
+ public function rewind()
+ {
+ $this->execute();
+ $this->keys = array_keys($this->data);
+ reset($this->keys);
+ }
+
+
+
+ /** @return ActiveRow */
+ public function current()
+ {
+ return $this->data[current($this->keys)];
+ }
+
+
+
+ /**
+ * @return string row ID
+ */
+ public function key()
+ {
+ return current($this->keys);
+ }
+
+
+
+ public function next()
+ {
+ next($this->keys);
+ }
+
+
+
+ public function valid()
+ {
+ return current($this->keys) !== FALSE;
+ }
+
+
+
+ /********************* interface ArrayAccess ****************d*g**/
+
+
+
+ /**
+ * Mimic row.
+ * @param string row ID
+ * @param ActiveRow
+ * @return NULL
+ */
+ public function offsetSet($key, $value)
+ {
+ $this->execute();
+ $this->data[$key] = $value;
+ }
+
+
+
+ /**
+ * Returns specified row.
+ * @param string row ID
+ * @return ActiveRow or NULL if there is no such row
+ */
+ public function offsetGet($key)
+ {
+ $this->execute();
+ return $this->data[$key];
+ }
+
+
+
+ /**
+ * Tests if row exists.
+ * @param string row ID
+ * @return bool
+ */
+ public function offsetExists($key)
+ {
+ $this->execute();
+ return isset($this->data[$key]);
+ }
+
+
+
+ /**
+ * Removes row from result set.
+ * @param string row ID
+ * @return NULL
+ */
+ public function offsetUnset($key)
+ {
+ $this->execute();
+ unset($this->data[$key]);
+ }
+
+}
diff --git a/libs/Nette/Database/Table/SqlBuilder.php b/libs/Nette/Database/Table/SqlBuilder.php
new file mode 100644
index 0000000..2e7e104
--- /dev/null
+++ b/libs/Nette/Database/Table/SqlBuilder.php
@@ -0,0 +1,388 @@
+selection = $selection;
+ $this->connection = $selection->getConnection();
+ $this->delimitedTable = $this->tryDelimite($selection->getName());
+ }
+
+
+
+ public function setSelection(Selection $selection)
+ {
+ $this->selection = $selection;
+ }
+
+
+
+ public function buildInsertQuery()
+ {
+ return "INSERT INTO {$this->delimitedTable}";
+ }
+
+
+
+ public function buildUpdateQuery()
+ {
+ return "UPDATE{$this->buildTopClause()} {$this->delimitedTable} SET ?" . $this->buildConditions();
+ }
+
+
+
+ public function buildDeleteQuery()
+ {
+ return "DELETE{$this->buildTopClause()} FROM {$this->delimitedTable}" . $this->buildConditions();
+ }
+
+
+
+ public function importConditions(SqlBuilder $builder)
+ {
+ $this->where = $builder->where;
+ $this->parameters = $builder->parameters;
+ $this->conditions = $builder->conditions;
+ }
+
+
+
+ /********************* SQL selectors ****************d*g**/
+
+
+
+ public function addSelect($columns)
+ {
+ $this->select[] = $columns;
+ }
+
+
+
+ public function getSelect()
+ {
+ return $this->select;
+ }
+
+
+
+ public function addWhere($condition, $parameters = array())
+ {
+ $args = func_get_args();
+ $hash = md5(json_encode($args));
+ if (isset($this->conditions[$hash])) {
+ return FALSE;
+ }
+
+ $this->conditions[$hash] = $condition;
+ $condition = $this->removeExtraTables($condition);
+ $condition = $this->tryDelimite($condition);
+
+ if (count($args) !== 2 || strpbrk($condition, '?:')) { // where('column < ? OR column > ?', array(1, 2))
+ if (count($args) !== 2 || !is_array($parameters)) { // where('column < ? OR column > ?', 1, 2)
+ $parameters = $args;
+ array_shift($parameters);
+ }
+
+ $this->parameters = array_merge($this->parameters, $parameters);
+
+ } elseif ($parameters === NULL) { // where('column', NULL)
+ $condition .= ' IS NULL';
+
+ } elseif ($parameters instanceof Selection) { // where('column', $db->$table())
+ $clone = clone $parameters;
+ if (!$clone->getSqlBuilder()->select) {
+ $clone->select($clone->primary);
+ }
+
+ if ($this->connection->getAttribute(PDO::ATTR_DRIVER_NAME) !== 'mysql') {
+ $condition .= ' IN (' . $clone->getSql() . ')';
+ } else {
+ $in = array();
+ foreach ($clone as $row) {
+ $this->parameters[] = array_values(iterator_to_array($row));
+ $in[] = (count($row) === 1 ? '?' : '(?)');
+ }
+ $condition .= ' IN (' . ($in ? implode(', ', $in) : 'NULL') . ')';
+ }
+
+ } elseif (!is_array($parameters)) { // where('column', 'x')
+ $condition .= ' = ?';
+ $this->parameters[] = $parameters;
+
+ } else { // where('column', array(1, 2))
+ if ($parameters) {
+ $condition .= " IN (?)";
+ $this->parameters[] = $parameters;
+ } else {
+ $condition .= " IN (NULL)";
+ }
+ }
+
+ $this->where[] = $condition;
+ return TRUE;
+ }
+
+
+
+ public function getConditions()
+ {
+ return array_values($this->conditions);
+ }
+
+
+
+ public function addOrder($columns)
+ {
+ $this->order[] = $columns;
+ }
+
+
+
+ public function getOrder()
+ {
+ return $this->order;
+ }
+
+
+
+ public function setLimit($limit, $offset)
+ {
+ $this->limit = $limit;
+ $this->offset = $offset;
+ }
+
+
+
+ public function getLimit()
+ {
+ return $this->limit;
+ }
+
+
+
+ public function getOffset()
+ {
+ return $this->offset;
+ }
+
+
+
+ public function setGroup($columns, $having)
+ {
+ $this->group = $columns;
+ $this->having = $having;
+ }
+
+
+
+ public function getGroup()
+ {
+ return $this->group;
+ }
+
+
+
+ public function getHaving()
+ {
+ return $this->having;
+ }
+
+
+
+ /********************* SQL building ****************d*g**/
+
+
+
+ /**
+ * Returns SQL query.
+ * @return string
+ */
+ public function buildSelectQuery()
+ {
+ $join = $this->buildJoins(implode(',', $this->conditions), TRUE);
+ $join += $this->buildJoins(implode(',', $this->select) . ",{$this->group},{$this->having}," . implode(',', $this->order));
+
+ $prefix = $join ? "{$this->delimitedTable}." : '';
+ if ($this->select) {
+ $cols = $this->tryDelimite($this->removeExtraTables(implode(', ', $this->select)));
+
+ } elseif ($prevAccessed = $this->selection->getPreviousAccessed()) {
+ $cols = array_map(array($this->connection->getSupplementalDriver(), 'delimite'), array_keys(array_filter($prevAccessed)));
+ $cols = $prefix . implode(', ' . $prefix, $cols);
+
+ } elseif ($this->group && !$this->connection->getSupplementalDriver()->isSupported(ISupplementalDriver::SUPPORT_SELECT_UNGROUPED_COLUMNS)) {
+ $cols = $this->tryDelimite($this->removeExtraTables($this->group));
+
+ } else {
+ $cols = $prefix . '*';
+
+ }
+
+ return "SELECT{$this->buildTopClause()} {$cols} FROM {$this->delimitedTable}" . implode($join) . $this->buildConditions();
+ }
+
+
+
+ public function getParameters()
+ {
+ return $this->parameters;
+ }
+
+
+
+ protected function buildJoins($val, $inner = FALSE)
+ {
+ $driver = $this->selection->getConnection()->getSupplementalDriver();
+ $reflection = $this->selection->getConnection()->getDatabaseReflection();
+ $joins = array();
+ preg_match_all('~\\b([a-z][\\w.:]*[.:])([a-z]\\w*|\*)(\\s+IS\\b|\\s*<=>)?~i', $val, $matches);
+ foreach ($matches[1] as $names) {
+ $parent = $this->selection->getName();
+ if ($names !== "$parent.") { // case-sensitive
+ preg_match_all('~\\b([a-z][\\w]*|\*)([.:])~i', $names, $matches, PREG_SET_ORDER);
+ foreach ($matches as $match) {
+ list(, $name, $delimiter) = $match;
+
+ if ($delimiter === ':') {
+ list($table, $primary) = $reflection->getHasManyReference($parent, $name);
+ $column = $reflection->getPrimary($parent);
+ } else {
+ list($table, $column) = $reflection->getBelongsToReference($parent, $name);
+ $primary = $reflection->getPrimary($table);
+ }
+
+ $joins[$name] = ' '
+ . (!isset($joins[$name]) && $inner && !isset($match[3]) ? 'INNER' : 'LEFT')
+ . ' JOIN ' . $driver->delimite($table) . ($table !== $name ? ' AS ' . $driver->delimite($name) : '')
+ . ' ON ' . $driver->delimite($parent) . '.' . $driver->delimite($column)
+ . ' = ' . $driver->delimite($name) . '.' . $driver->delimite($primary);
+
+ $parent = $name;
+ }
+ }
+ }
+ return $joins;
+ }
+
+
+
+ protected function buildConditions()
+ {
+ $return = '';
+ $driver = $this->connection->getAttribute(PDO::ATTR_DRIVER_NAME);
+ $where = $this->where;
+ if ($this->limit !== NULL && $driver === 'oci') {
+ $where[] = ($this->offset ? "rownum > $this->offset AND " : '') . 'rownum <= ' . ($this->limit + $this->offset);
+ }
+ if ($where) {
+ $return .= ' WHERE (' . implode(') AND (', $where) . ')';
+ }
+ if ($this->group) {
+ $return .= ' GROUP BY '. $this->tryDelimite($this->removeExtraTables($this->group));
+ }
+ if ($this->having) {
+ $return .= ' HAVING '. $this->tryDelimite($this->removeExtraTables($this->having));
+ }
+ if ($this->order) {
+ $return .= ' ORDER BY ' . $this->tryDelimite($this->removeExtraTables(implode(', ', $this->order)));
+ }
+ if ($this->limit !== NULL && $driver !== 'oci' && $driver !== 'dblib') {
+ $return .= " LIMIT $this->limit";
+ if ($this->offset !== NULL) {
+ $return .= " OFFSET $this->offset";
+ }
+ }
+ return $return;
+ }
+
+
+
+ protected function buildTopClause()
+ {
+ if ($this->limit !== NULL && $this->connection->getAttribute(PDO::ATTR_DRIVER_NAME) === 'dblib') {
+ return " TOP ($this->limit)"; //! offset is not supported
+ }
+ return '';
+ }
+
+
+
+ protected function tryDelimite($s)
+ {
+ $driver = $this->connection->getSupplementalDriver();
+ return preg_replace_callback('#(?<=[^\w`"\[]|^)[a-z_][a-z0-9_]*(?=[^\w`"(\]]|$)#i', function($m) use ($driver) {
+ return strtoupper($m[0]) === $m[0] ? $m[0] : $driver->delimite($m[0]);
+ }, $s);
+ }
+
+
+
+ protected function removeExtraTables($expression)
+ {
+ return preg_replace('~(?:\\b[a-z_][a-z0-9_.:]*[.:])?([a-z_][a-z0-9_]*)[.:]([a-z_*])~i', '\\1.\\2', $expression); // rewrite tab1.tab2.col
+ }
+
+}
diff --git a/libs/Nette/Diagnostics/Bar.php b/libs/Nette/Diagnostics/Bar.php
index cd40df1..cfe2d68 100644
--- a/libs/Nette/Diagnostics/Bar.php
+++ b/libs/Nette/Diagnostics/Bar.php
@@ -1,75 +1,79 @@
-panels[$id]));
- }
- $this->panels[$id] = $panel;
- }
-
-
-
- /**
- * Renders debug bar.
- * @return void
- */
- public function render()
- {
- $panels = array();
- foreach ($this->panels as $id => $panel) {
- try {
- $panels[] = array(
- 'id' => preg_replace('#[^a-z0-9]+#i', '-', $id),
- 'tab' => $tab = (string) $panel->getTab(),
- 'panel' => $tab ? (string) $panel->getPanel() : NULL,
- );
- } catch (\Exception $e) {
- $panels[] = array(
- 'id' => "error-$id",
- 'tab' => "Error: $id",
- 'panel' => nl2br(htmlSpecialChars((string) $e)),
- );
- }
- }
- require __DIR__ . '/templates/bar.phtml';
- }
-
-}
+panels[$id]));
+ }
+ $this->panels[$id] = $panel;
+ return $this;
+ }
+
+
+
+ /**
+ * Renders debug bar.
+ * @return void
+ */
+ public function render()
+ {
+ $obLevel = ob_get_level();
+ $panels = array();
+ foreach ($this->panels as $id => $panel) {
+ try {
+ $panels[] = array(
+ 'id' => preg_replace('#[^a-z0-9]+#i', '-', $id),
+ 'tab' => $tab = (string) $panel->getTab(),
+ 'panel' => $tab ? (string) $panel->getPanel() : NULL,
+ );
+ } catch (\Exception $e) {
+ $panels[] = array(
+ 'id' => "error-" . preg_replace('#[^a-z0-9]+#i', '-', $id),
+ 'tab' => "Error in $id",
+ 'panel' => 'Error: ' . $id . '
' . nl2br(htmlSpecialChars($e)) . '
',
+ );
+ while (ob_get_level() > $obLevel) { // restore ob-level if broken
+ ob_end_clean();
+ }
+ }
+ }
+ require __DIR__ . '/templates/bar.phtml';
+ }
+
+}
diff --git a/libs/Nette/Diagnostics/BlueScreen.php b/libs/Nette/Diagnostics/BlueScreen.php
index 06f1367..f9ce51e 100644
--- a/libs/Nette/Diagnostics/BlueScreen.php
+++ b/libs/Nette/Diagnostics/BlueScreen.php
@@ -1,122 +1,139 @@
-panels[] = $panel;
- } else {
- $this->panels[$id] = $panel;
- }
- }
-
-
-
- /**
- * Renders blue screen.
- * @param \Exception
- * @return void
- */
- public function render(\Exception $exception)
- {
- $panels = $this->panels;
- require __DIR__ . '/templates/bluescreen.phtml';
- }
-
-
-
- /**
- * Returns syntax highlighted source code.
- * @param string
- * @param int
- * @param int
- * @return string
- */
- public static function highlightFile($file, $line, $count = 15)
- {
- if (function_exists('ini_set')) {
- ini_set('highlight.comment', '#999; font-style: italic');
- ini_set('highlight.default', '#000');
- ini_set('highlight.html', '#06B');
- ini_set('highlight.keyword', '#D24; font-weight: bold');
- ini_set('highlight.string', '#080');
- }
-
- $start = max(1, $line - floor($count / 2));
-
- $source = @file_get_contents($file); // intentionally @
- if (!$source) {
- return;
- }
- $source = explode("\n", highlight_string($source, TRUE));
- $spans = 1;
- $out = $source[0]; //
- $source = explode('
', $source[1]);
- array_unshift($source, NULL);
-
- $i = $start; // find last highlighted block
- while (--$i >= 1) {
- if (preg_match('#.*(?span[^>]*>)#', $source[$i], $m)) {
- if ($m[1] !== '') {
- $spans++; $out .= $m[1];
- }
- break;
- }
- }
-
- $source = array_slice($source, $start, $count, TRUE);
- end($source);
- $numWidth = strlen((string) key($source));
-
- foreach ($source as $n => $s) {
- $spans += substr_count($s, ']+>#', $s, $tags);
- if ($n === $line) {
- $out .= sprintf(
- "%{$numWidth}s: %s\n%s",
- $n,
- strip_tags($s),
- implode('', $tags[0])
- );
- } else {
- $out .= sprintf("%{$numWidth}s: %s\n", $n, $s);
- }
- }
- return $out . str_repeat('', $spans) . '';
- }
-
-}
+panels, TRUE)) {
+ $this->panels[] = $panel;
+ }
+ return $this;
+ }
+
+
+
+ /**
+ * Renders blue screen.
+ * @param \Exception
+ * @return void
+ */
+ public function render(\Exception $exception)
+ {
+ $panels = $this->panels;
+ require __DIR__ . '/templates/bluescreen.phtml';
+ }
+
+
+
+ /**
+ * Returns syntax highlighted source code.
+ * @param string
+ * @param int
+ * @param int
+ * @return string
+ */
+ public static function highlightFile($file, $line, $lines = 15, $vars = array())
+ {
+ $source = @file_get_contents($file); // intentionally @
+ if ($source) {
+ return static::highlightPhp($source, $line, $lines, $vars);
+ }
+ }
+
+
+
+ /**
+ * Returns syntax highlighted source code.
+ * @param string
+ * @param int
+ * @param int
+ * @return string
+ */
+ public static function highlightPhp($source, $line, $lines = 15, $vars = array())
+ {
+ if (function_exists('ini_set')) {
+ ini_set('highlight.comment', '#998; font-style: italic');
+ ini_set('highlight.default', '#000');
+ ini_set('highlight.html', '#06B');
+ ini_set('highlight.keyword', '#D24; font-weight: bold');
+ ini_set('highlight.string', '#080');
+ }
+
+ $source = str_replace(array("\r\n", "\r"), "\n", $source);
+ $source = explode("\n", highlight_string($source, TRUE));
+ $spans = 1;
+ $out = $source[0]; //
+ $source = explode('
', $source[1]);
+ array_unshift($source, NULL);
+
+ $start = $i = max(1, $line - floor($lines * 2/3));
+ while (--$i >= 1) { // find last highlighted block
+ if (preg_match('#.*(?span[^>]*>)#', $source[$i], $m)) {
+ if ($m[1] !== '') {
+ $spans++; $out .= $m[1];
+ }
+ break;
+ }
+ }
+
+ $source = array_slice($source, $start, $lines, TRUE);
+ end($source);
+ $numWidth = strlen((string) key($source));
+
+ foreach ($source as $n => $s) {
+ $spans += substr_count($s, ']+>#', $s, $tags);
+ if ($n == $line) {
+ $out .= sprintf(
+ "%{$numWidth}s: %s\n%s",
+ $n,
+ strip_tags($s),
+ implode('', $tags[0])
+ );
+ } else {
+ $out .= sprintf("%{$numWidth}s: %s\n", $n, $s);
+ }
+ }
+ $out .= str_repeat('', $spans) . '';
+
+ $out = preg_replace_callback('#">\$(\w+)( )?#', function($m) use ($vars) {
+ return isset($vars[$m[1]])
+ ? '" title="' . str_replace('"', '"', strip_tags(Helpers::htmlDump($vars[$m[1]]))) . $m[0]
+ : $m[0];
+ }, $out);
+
+ return "$out
";
+ }
+
+}
diff --git a/libs/Nette/Diagnostics/Debugger.php b/libs/Nette/Diagnostics/Debugger.php
index dd0a4c2..d91c3a3 100644
--- a/libs/Nette/Diagnostics/Debugger.php
+++ b/libs/Nette/Diagnostics/Debugger.php
@@ -1,663 +1,698 @@
-directory;
- self::$email = & self::$logger->email;
- self::$mailer = & self::$logger->mailer;
- self::$emailSnooze = & Logger::$emailSnooze;
-
- self::$fireLogger = new FireLogger;
-
- self::$blueScreen = new BlueScreen;
- self::$blueScreen->addPanel(function($e) {
- if ($e instanceof Nette\Templating\FilterException) {
- return array(
- 'tab' => 'Template',
- 'panel' => 'File: ' . Helpers::editorLink($e->sourceFile, $e->sourceLine)
- . ' Line: ' . ($e->sourceLine ? $e->sourceLine : 'n/a') . '
'
- . ($e->sourceLine ? '' . BlueScreen::highlightFile($e->sourceFile, $e->sourceLine) . '
' : '')
- );
- }
- });
-
- self::$bar = new Bar;
- self::$bar->addPanel(new DefaultBarPanel('time'));
- self::$bar->addPanel(new DefaultBarPanel('memory'));
- self::$bar->addPanel(self::$errorPanel = new DefaultBarPanel('errors')); // filled by _errorHandler()
- self::$bar->addPanel(self::$dumpPanel = new DefaultBarPanel('dumps')); // filled by barDump()
- }
-
-
-
- /********************* errors and exceptions reporting ****************d*g**/
-
-
-
- /**
- * Enables displaying or logging errors and exceptions.
- * @param mixed production, development mode, autodetection or IP address(es) whitelist.
- * @param string error log directory; enables logging in production mode, FALSE means that logging is disabled
- * @param string administrator email; enables email sending in production mode
- * @return void
- */
- public static function enable($mode = NULL, $logDirectory = NULL, $email = NULL)
- {
- error_reporting(E_ALL | E_STRICT);
-
- // production/development mode detection
- if (is_bool($mode)) {
- self::$productionMode = $mode;
-
- } elseif (is_string($mode)) { // IP addresses
- $mode = preg_split('#[,\s]+#', "$mode 127.0.0.1 ::1");
- }
-
- if (is_array($mode)) { // IP addresses whitelist detection
- self::$productionMode = !isset($_SERVER['REMOTE_ADDR']) || !in_array($_SERVER['REMOTE_ADDR'], $mode, TRUE);
- }
-
- if (self::$productionMode === self::DETECT) {
- if (class_exists('Nette\Environment')) {
- self::$productionMode = Nette\Environment::isProduction();
-
- } elseif (isset($_SERVER['SERVER_ADDR']) || isset($_SERVER['LOCAL_ADDR'])) { // IP address based detection
- $addrs = array();
- if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { // proxy server detected
- $addrs = preg_split('#,\s*#', $_SERVER['HTTP_X_FORWARDED_FOR']);
- }
- if (isset($_SERVER['REMOTE_ADDR'])) {
- $addrs[] = $_SERVER['REMOTE_ADDR'];
- }
- $addrs[] = isset($_SERVER['SERVER_ADDR']) ? $_SERVER['SERVER_ADDR'] : $_SERVER['LOCAL_ADDR'];
- self::$productionMode = FALSE;
- foreach ($addrs as $addr) {
- $oct = explode('.', $addr);
- if ($addr !== '::1' && (count($oct) !== 4 || ($oct[0] !== '10' && $oct[0] !== '127' && ($oct[0] !== '172' || $oct[1] < 16 || $oct[1] > 31)
- && ($oct[0] !== '169' || $oct[1] !== '254') && ($oct[0] !== '192' || $oct[1] !== '168')))
- ) {
- self::$productionMode = TRUE;
- break;
- }
- }
-
- } else {
- self::$productionMode = !self::$consoleMode;
- }
- }
-
- // logging configuration
- if (is_string($logDirectory)) {
- self::$logDirectory = realpath($logDirectory);
- if (self::$logDirectory === FALSE) {
- throw new Nette\DirectoryNotFoundException("Directory '$logDirectory' is not found.");
- }
- } elseif ($logDirectory === FALSE) {
- self::$logDirectory = FALSE;
-
- } else {
- self::$logDirectory = defined('APP_DIR') ? APP_DIR . '/../log' : getcwd() . '/log';
- }
- if (self::$logDirectory) {
- ini_set('error_log', self::$logDirectory . '/php_error.log');
- }
-
- // php configuration
- if (function_exists('ini_set')) {
- ini_set('display_errors', !self::$productionMode); // or 'stderr'
- ini_set('html_errors', FALSE);
- ini_set('log_errors', FALSE);
-
- } elseif (ini_get('display_errors') != !self::$productionMode && ini_get('display_errors') !== (self::$productionMode ? 'stderr' : 'stdout')) { // intentionally ==
- throw new Nette\NotSupportedException('Function ini_set() must be enabled.');
- }
-
- if ($email) {
- if (!is_string($email)) {
- throw new Nette\InvalidArgumentException('Email address must be a string.');
- }
- self::$email = $email;
- }
-
- if (!defined('E_DEPRECATED')) {
- define('E_DEPRECATED', 8192);
- }
-
- if (!defined('E_USER_DEPRECATED')) {
- define('E_USER_DEPRECATED', 16384);
- }
-
- if (!self::$enabled) {
- register_shutdown_function(array(__CLASS__, '_shutdownHandler'));
- set_exception_handler(array(__CLASS__, '_exceptionHandler'));
- set_error_handler(array(__CLASS__, '_errorHandler'));
- self::$enabled = TRUE;
- }
- }
-
-
-
- /**
- * Is Debug enabled?
- * @return bool
- */
- public static function isEnabled()
- {
- return self::$enabled;
- }
-
-
-
- /**
- * Logs message or exception to file (if not disabled) and sends email notification (if enabled).
- * @param string|Exception
- * @param int one of constant Debugger::INFO, WARNING, ERROR (sends email), CRITICAL (sends email)
- * @return void
- */
- public static function log($message, $priority = self::INFO)
- {
- if (self::$logDirectory === FALSE) {
- return;
-
- } elseif (!self::$logDirectory) {
- throw new Nette\InvalidStateException('Logging directory is not specified in Nette\Diagnostics\Debugger::$logDirectory.');
- }
-
- if ($message instanceof \Exception) {
- $exception = $message;
- $message = "PHP Fatal error: "
- . ($message instanceof Nette\FatalErrorException
- ? $exception->getMessage()
- : "Uncaught exception " . get_class($exception) . " with message '" . $exception->getMessage() . "'")
- . " in " . $exception->getFile() . ":" . $exception->getLine();
-
- $hash = md5($exception );
- $exceptionFilename = "exception " . @date('Y-m-d H-i-s') . " $hash.html";
- foreach (new \DirectoryIterator(self::$logDirectory) as $entry) {
- if (strpos($entry, $hash)) {
- $exceptionFilename = NULL; break;
- }
- }
- }
-
- self::$logger->log(array(
- @date('[Y-m-d H-i-s]'),
- $message,
- self::$source ? ' @ ' . self::$source : NULL,
- !empty($exceptionFilename) ? ' @@ ' . $exceptionFilename : NULL
- ), $priority);
-
- if (!empty($exceptionFilename) && $logHandle = @fopen(self::$logDirectory . '/'. $exceptionFilename, 'w')) {
- ob_start(); // double buffer prevents sending HTTP headers in some PHP
- ob_start(function($buffer) use ($logHandle) { fwrite($logHandle, $buffer); }, 1);
- self::$blueScreen->render($exception);
- ob_end_flush();
- ob_end_clean();
- fclose($logHandle);
- }
- }
-
-
-
- /**
- * Shutdown handler to catch fatal errors and execute of the planned activities.
- * @return void
- * @internal
- */
- public static function _shutdownHandler()
- {
- // fatal error handler
- static $types = array(
- E_ERROR => 1,
- E_CORE_ERROR => 1,
- E_COMPILE_ERROR => 1,
- E_PARSE => 1,
- );
- $error = error_get_last();
- if (isset($types[$error['type']])) {
- self::_exceptionHandler(new Nette\FatalErrorException($error['message'], 0, $error['type'], $error['file'], $error['line'], NULL), TRUE);
- }
-
- // debug bar (require HTML & development mode)
- if (self::$bar && !self::$productionMode && !self::$ajaxDetected && !self::$consoleMode
- && !preg_match('#^Content-Type: (?!text/html)#im', implode("\n", headers_list()))
- ) {
- self::$bar->render();
- }
- }
-
-
-
- /**
- * Handler to catch uncaught exception.
- * @param \Exception
- * @return void
- * @internal
- */
- public static function _exceptionHandler(\Exception $exception, $drawBar = FALSE)
- {
- if (!headers_sent()) { // for PHP < 5.2.4
- header('HTTP/1.1 500 Internal Server Error');
- }
-
- $htmlMode = !self::$ajaxDetected && !preg_match('#^Content-Type: (?!text/html)#im', implode("\n", headers_list()));
-
- try {
- if (self::$productionMode) {
- self::log($exception, self::ERROR);
-
- if (self::$consoleMode) {
- echo "ERROR: the server encountered an internal error and was unable to complete your request.\n";
-
- } elseif ($htmlMode) {
- require __DIR__ . '/templates/error.phtml';
- }
-
- } else {
- if (self::$consoleMode) { // dump to console
- echo "$exception\n";
-
- } elseif ($htmlMode) { // dump to browser
- self::$blueScreen->render($exception);
- if ($drawBar && self::$bar) {
- self::$bar->render();
- }
-
- } elseif (!self::fireLog($exception, self::ERROR)) { // AJAX or non-HTML mode
- self::log($exception);
- }
- }
-
- foreach (self::$onFatalError as $handler) {
- call_user_func($handler, $exception);
- }
- } catch (\Exception $e) {
- echo "\nNette\\Debug FATAL ERROR: thrown ", get_class($e), ': ', $e->getMessage(),
- "\nwhile processing ", get_class($exception), ': ', $exception->getMessage(), "\n";
- }
- exit(255);
- }
-
-
-
- /**
- * Handler to catch warnings and notices.
- * @param int level of the error raised
- * @param string error message
- * @param string file that the error was raised in
- * @param int line number the error was raised at
- * @param array an array of variables that existed in the scope the error was triggered in
- * @return bool FALSE to call normal error handler, NULL otherwise
- * @throws Nette\FatalErrorException
- * @internal
- */
- public static function _errorHandler($severity, $message, $file, $line, $context)
- {
- if (self::$scream) {
- error_reporting(E_ALL | E_STRICT);
- }
-
- if (self::$lastError !== FALSE && ($severity & error_reporting()) === $severity) { // tryError mode
- self::$lastError = new \ErrorException($message, 0, $severity, $file, $line);
- return NULL;
- }
-
- if ($severity === E_RECOVERABLE_ERROR || $severity === E_USER_ERROR) {
- throw new Nette\FatalErrorException($message, 0, $severity, $file, $line, $context);
-
- } elseif (($severity & error_reporting()) !== $severity) {
- return FALSE; // calls normal error handler to fill-in error_get_last()
-
- } elseif (self::$strictMode && !self::$productionMode) {
- self::_exceptionHandler(new Nette\FatalErrorException($message, 0, $severity, $file, $line, $context));
- }
-
- static $types = array(
- E_WARNING => 'Warning',
- E_COMPILE_WARNING => 'Warning', // currently unable to handle
- E_USER_WARNING => 'Warning',
- E_NOTICE => 'Notice',
- E_USER_NOTICE => 'Notice',
- E_STRICT => 'Strict standards',
- E_DEPRECATED => 'Deprecated',
- E_USER_DEPRECATED => 'Deprecated',
- );
-
- $message = 'PHP ' . (isset($types[$severity]) ? $types[$severity] : 'Unknown error') . ": $message";
- $count = & self::$errorPanel->data["$message|$file|$line"];
-
- if ($count++) { // repeated error
- return NULL;
-
- } elseif (self::$productionMode) {
- self::log("$message in $file:$line", self::ERROR);
- return NULL;
-
- } else {
- $ok = self::fireLog(new \ErrorException($message, 0, $severity, $file, $line), self::WARNING);
- return self::$consoleMode || (!self::$bar && !$ok) ? FALSE : NULL;
- }
-
- return FALSE; // call normal error handler
- }
-
-
-
- /**
- * Handles exception thrown in __toString().
- * @param \Exception
- * @return void
- */
- public static function toStringException(\Exception $exception)
- {
- if (self::$enabled) {
- self::_exceptionHandler($exception);
- } else {
- trigger_error($exception->getMessage(), E_USER_ERROR);
- }
- }
-
-
-
- /**
- * Starts catching potential errors/warnings.
- * @return void
- */
- public static function tryError()
- {
- if (!self::$enabled && self::$lastError === FALSE) {
- set_error_handler(array(__CLASS__, '_errorHandler'));
- }
- self::$lastError = NULL;
- }
-
-
-
- /**
- * Returns catched error/warning message.
- * @param \ErrorException catched error
- * @return bool
- */
- public static function catchError(& $error)
- {
- if (!self::$enabled && self::$lastError !== FALSE) {
- restore_error_handler();
- }
- $error = self::$lastError;
- self::$lastError = FALSE;
- return (bool) $error;
- }
-
-
-
- /********************* useful tools ****************d*g**/
-
-
-
- /**
- * Dumps information about a variable in readable format.
- * @param mixed variable to dump
- * @param bool return output instead of printing it? (bypasses $productionMode)
- * @return mixed variable itself or dump
- */
- public static function dump($var, $return = FALSE)
- {
- if (!$return && self::$productionMode) {
- return $var;
- }
-
- $output = "" . Helpers::htmlDump($var) . "
\n";
-
- if (!$return) {
- $trace = debug_backtrace();
- $i = !isset($trace[1]['class']) && isset($trace[1]['function']) && $trace[1]['function'] === 'dump' ? 1 : 0;
- if (isset($trace[$i]['file'], $trace[$i]['line']) && is_file($trace[$i]['file'])) {
- $lines = file($trace[$i]['file']);
- preg_match('#dump\((.*)\)#', $lines[$trace[$i]['line'] - 1], $m);
- $output = substr_replace(
- $output,
- ' title="' . htmlspecialchars((isset($m[0]) ? "$m[0] \n" : '') . "in file {$trace[$i]['file']} on line {$trace[$i]['line']}") . '"',
- 4, 0);
-
- if (self::$showLocation) {
- $output = substr_replace(
- $output,
- ' in ' . Helpers::editorLink($trace[$i]['file'], $trace[$i]['line']) . ":{$trace[$i]['line']}",
- -8, 0);
- }
- }
- }
-
- if (self::$consoleMode) {
- $output = htmlspecialchars_decode(strip_tags($output), ENT_NOQUOTES);
- }
-
- if ($return) {
- return $output;
-
- } else {
- echo $output;
- return $var;
- }
- }
-
-
-
- /**
- * Starts/stops stopwatch.
- * @param string name
- * @return float elapsed seconds
- */
- public static function timer($name = NULL)
- {
- static $time = array();
- $now = microtime(TRUE);
- $delta = isset($time[$name]) ? $now - $time[$name] : 0;
- $time[$name] = $now;
- return $delta;
- }
-
-
-
- /**
- * Dumps information about a variable in Nette Debug Bar.
- * @param mixed variable to dump
- * @param string optional title
- * @return mixed variable itself
- */
- public static function barDump($var, $title = NULL)
- {
- if (!self::$productionMode) {
- $dump = array();
- foreach ((is_array($var) ? $var : array('' => $var)) as $key => $val) {
- $dump[$key] = Helpers::clickableDump($val);
- }
- self::$dumpPanel->data[] = array('title' => $title, 'dump' => $dump);
- }
- return $var;
- }
-
-
-
- /**
- * Sends message to FireLogger console.
- * @param mixed message to log
- * @return bool was successful?
- */
- public static function fireLog($message)
- {
- if (!self::$productionMode) {
- return self::$fireLogger->log($message);
- }
- }
-
-
-
- /** @deprecated */
- public static function addPanel(IBarPanel $panel, $id = NULL)
- {
- self::$bar->addPanel($panel, $id);
- }
-
-}
-
-
-
-Debugger::_init();
+ '1;33',
+ 'null' => '1;33',
+ 'int' => '1;36',
+ 'float' => '1;36',
+ 'string' => '1;32',
+ 'array' => '1;31',
+ 'key' => '1;37',
+ 'object' => '1;31',
+ 'visibility' => '1;30',
+ 'resource' => '1;37',
+ );
+
+ /********************* errors and exceptions reporting ****************d*g**/
+
+ /** server modes {@link Debugger::enable()} */
+ const DEVELOPMENT = FALSE,
+ PRODUCTION = TRUE,
+ DETECT = NULL;
+
+ /** @var BlueScreen */
+ public static $blueScreen;
+
+ /** @var bool|int determines whether any error will cause immediate death; if integer that it's matched against error severity */
+ public static $strictMode = FALSE; // $immediateDeath
+
+ /** @var bool disables the @ (shut-up) operator so that notices and warnings are no longer hidden */
+ public static $scream = FALSE;
+
+ /** @var array of callables specifies the functions that are automatically called after fatal error */
+ public static $onFatalError = array();
+
+ /** @var bool {@link Debugger::enable()} */
+ private static $enabled = FALSE;
+
+ /** @var mixed {@link Debugger::tryError()} FALSE means catching is disabled */
+ private static $lastError = FALSE;
+
+ /********************* logging ****************d*g**/
+
+ /** @var Logger */
+ public static $logger;
+
+ /** @var FireLogger */
+ public static $fireLogger;
+
+ /** @var string name of the directory where errors should be logged; FALSE means that logging is disabled */
+ public static $logDirectory;
+
+ /** @var string email to sent error notifications */
+ public static $email;
+
+ /** @deprecated */
+ public static $mailer;
+
+ /** @deprecated */
+ public static $emailSnooze;
+
+ /********************* debug bar ****************d*g**/
+
+ /** @var Bar */
+ public static $bar;
+
+ /** @var DefaultBarPanel */
+ private static $errorPanel;
+
+ /** @var DefaultBarPanel */
+ private static $dumpPanel;
+
+ /********************* Firebug extension ****************d*g**/
+
+ /** {@link Debugger::log()} and {@link Debugger::fireLog()} */
+ const DEBUG = 'debug',
+ INFO = 'info',
+ WARNING = 'warning',
+ ERROR = 'error',
+ CRITICAL = 'critical';
+
+
+
+ /**
+ * Static class - cannot be instantiated.
+ */
+ final public function __construct()
+ {
+ throw new Nette\StaticClassException;
+ }
+
+
+
+ /**
+ * Static class constructor.
+ * @internal
+ */
+ public static function _init()
+ {
+ self::$time = isset($_SERVER['REQUEST_TIME_FLOAT']) ? $_SERVER['REQUEST_TIME_FLOAT'] : microtime(TRUE);
+ self::$consoleMode = PHP_SAPI === 'cli';
+ self::$productionMode = self::DETECT;
+ if (self::$consoleMode) {
+ self::$source = empty($_SERVER['argv']) ? 'cli' : 'cli: ' . implode(' ', $_SERVER['argv']);
+ } else {
+ self::$ajaxDetected = isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest';
+ if (isset($_SERVER['REQUEST_URI'])) {
+ self::$source = (isset($_SERVER['HTTPS']) && strcasecmp($_SERVER['HTTPS'], 'off') ? 'https://' : 'http://')
+ . (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : (isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : ''))
+ . $_SERVER['REQUEST_URI'];
+ }
+ }
+
+ self::$logger = new Logger;
+ self::$logDirectory = & self::$logger->directory;
+ self::$email = & self::$logger->email;
+ self::$mailer = & self::$logger->mailer;
+ self::$emailSnooze = & Logger::$emailSnooze;
+
+ self::$fireLogger = new FireLogger;
+
+ self::$blueScreen = new BlueScreen;
+ self::$blueScreen->addPanel(function($e) {
+ if ($e instanceof Nette\Templating\FilterException) {
+ return array(
+ 'tab' => 'Template',
+ 'panel' => 'File: ' . Helpers::editorLink($e->sourceFile, $e->sourceLine)
+ . ' Line: ' . ($e->sourceLine ? $e->sourceLine : 'n/a') . '
'
+ . ($e->sourceLine ? BlueScreen::highlightFile($e->sourceFile, $e->sourceLine) : '')
+ );
+ } elseif ($e instanceof Nette\Utils\NeonException && preg_match('#line (\d+)#', $e->getMessage(), $m)) {
+ if ($item = Helpers::findTrace($e->getTrace(), 'Nette\Config\Adapters\NeonAdapter::load')) {
+ return array(
+ 'tab' => 'NEON',
+ 'panel' => 'File: ' . Helpers::editorLink($item['args'][0], $m[1]) . ' Line: ' . $m[1] . '
'
+ . BlueScreen::highlightFile($item['args'][0], $m[1])
+ );
+ } elseif ($item = Helpers::findTrace($e->getTrace(), 'Nette\Utils\Neon::decode')) {
+ return array(
+ 'tab' => 'NEON',
+ 'panel' => BlueScreen::highlightPhp($item['args'][0], $m[1])
+ );
+ }
+ }
+ });
+
+ self::$bar = new Bar;
+ self::$bar->addPanel(new DefaultBarPanel('time'));
+ self::$bar->addPanel(new DefaultBarPanel('memory'));
+ self::$bar->addPanel(self::$errorPanel = new DefaultBarPanel('errors')); // filled by _errorHandler()
+ self::$bar->addPanel(self::$dumpPanel = new DefaultBarPanel('dumps')); // filled by barDump()
+ }
+
+
+
+ /********************* errors and exceptions reporting ****************d*g**/
+
+
+
+ /**
+ * Enables displaying or logging errors and exceptions.
+ * @param mixed production, development mode, autodetection or IP address(es) whitelist.
+ * @param string error log directory; enables logging in production mode, FALSE means that logging is disabled
+ * @param string administrator email; enables email sending in production mode
+ * @return void
+ */
+ public static function enable($mode = NULL, $logDirectory = NULL, $email = NULL)
+ {
+ error_reporting(E_ALL | E_STRICT);
+
+ // production/development mode detection
+ if (is_bool($mode)) {
+ self::$productionMode = $mode;
+
+ } elseif ($mode !== self::DETECT || self::$productionMode === NULL) { // IP addresses or computer names whitelist detection
+ $list = is_string($mode) ? preg_split('#[,\s]+#', $mode) : (array) $mode;
+ if (!isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
+ $list[] = '127.0.0.1';
+ $list[] = '::1';
+ }
+ self::$productionMode = !in_array(isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : php_uname('n'), $list, TRUE);
+ }
+
+ // logging configuration
+ if (is_string($logDirectory)) {
+ self::$logDirectory = realpath($logDirectory);
+ if (self::$logDirectory === FALSE) {
+ die(__METHOD__ . "() error: Log directory is not found or is not directory.");
+ }
+ } elseif ($logDirectory === FALSE) {
+ self::$logDirectory = FALSE;
+
+ } elseif (self::$logDirectory === NULL) {
+ self::$logDirectory = defined('APP_DIR') ? APP_DIR . '/../log' : getcwd() . '/log';
+ }
+ if (self::$logDirectory) {
+ ini_set('error_log', self::$logDirectory . '/php_error.log');
+ }
+
+ // php configuration
+ if (function_exists('ini_set')) {
+ ini_set('display_errors', !self::$productionMode); // or 'stderr'
+ ini_set('html_errors', FALSE);
+ ini_set('log_errors', FALSE);
+
+ } elseif (ini_get('display_errors') != !self::$productionMode && ini_get('display_errors') !== (self::$productionMode ? 'stderr' : 'stdout')) { // intentionally ==
+ die(__METHOD__ . "() error: Unable to set 'display_errors' because function ini_set() is disabled.");
+ }
+
+ if ($email) {
+ if (!is_string($email)) {
+ die(__METHOD__ . '() error: Email address must be a string.');
+ }
+ self::$email = $email;
+ }
+
+ if (!defined('E_DEPRECATED')) {
+ define('E_DEPRECATED', 8192);
+ }
+
+ if (!defined('E_USER_DEPRECATED')) {
+ define('E_USER_DEPRECATED', 16384);
+ }
+
+ if (!self::$enabled) {
+ register_shutdown_function(array(__CLASS__, '_shutdownHandler'));
+ set_exception_handler(array(__CLASS__, '_exceptionHandler'));
+ set_error_handler(array(__CLASS__, '_errorHandler'));
+ self::$enabled = TRUE;
+ }
+ }
+
+
+
+ /**
+ * Is Debug enabled?
+ * @return bool
+ */
+ public static function isEnabled()
+ {
+ return self::$enabled;
+ }
+
+
+
+ /**
+ * Logs message or exception to file (if not disabled) and sends email notification (if enabled).
+ * @param string|Exception
+ * @param int one of constant Debugger::INFO, WARNING, ERROR (sends email), CRITICAL (sends email)
+ * @return string logged error filename
+ */
+ public static function log($message, $priority = self::INFO)
+ {
+ if (self::$logDirectory === FALSE) {
+ return;
+
+ } elseif (!self::$logDirectory) {
+ throw new Nette\InvalidStateException('Logging directory is not specified in Nette\Diagnostics\Debugger::$logDirectory.');
+ }
+
+ if ($message instanceof \Exception) {
+ $exception = $message;
+ $message = ($message instanceof Nette\FatalErrorException
+ ? 'Fatal error: ' . $exception->getMessage()
+ : get_class($exception) . ": " . $exception->getMessage())
+ . " in " . $exception->getFile() . ":" . $exception->getLine();
+
+ $hash = md5($exception /*5.2*. (method_exists($exception, 'getPrevious') ? $exception->getPrevious() : (isset($exception->previous) ? $exception->previous : ''))*/);
+ $exceptionFilename = "exception-" . @date('Y-m-d-H-i-s') . "-$hash.html";
+ foreach (new \DirectoryIterator(self::$logDirectory) as $entry) {
+ if (strpos($entry, $hash)) {
+ $exceptionFilename = $entry;
+ $saved = TRUE;
+ break;
+ }
+ }
+ }
+
+ self::$logger->log(array(
+ @date('[Y-m-d H-i-s]'),
+ trim($message),
+ self::$source ? ' @ ' . self::$source : NULL,
+ !empty($exceptionFilename) ? ' @@ ' . $exceptionFilename : NULL
+ ), $priority);
+
+ if (!empty($exceptionFilename)) {
+ $exceptionFilename = self::$logDirectory . '/' . $exceptionFilename;
+ if (empty($saved) && $logHandle = @fopen($exceptionFilename, 'w')) {
+ ob_start(); // double buffer prevents sending HTTP headers in some PHP
+ ob_start(function($buffer) use ($logHandle) { fwrite($logHandle, $buffer); }, 4096);
+ self::$blueScreen->render($exception);
+ ob_end_flush();
+ ob_end_clean();
+ fclose($logHandle);
+ }
+ return strtr($exceptionFilename, '\\/', DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR);
+ }
+ }
+
+
+
+ /**
+ * Shutdown handler to catch fatal errors and execute of the planned activities.
+ * @return void
+ * @internal
+ */
+ public static function _shutdownHandler()
+ {
+ if (!self::$enabled) {
+ return;
+ }
+
+ // fatal error handler
+ static $types = array(
+ E_ERROR => 1,
+ E_CORE_ERROR => 1,
+ E_COMPILE_ERROR => 1,
+ E_PARSE => 1,
+ );
+ $error = error_get_last();
+ if (isset($types[$error['type']])) {
+ self::_exceptionHandler(new Nette\FatalErrorException($error['message'], 0, $error['type'], $error['file'], $error['line'], NULL));
+ }
+
+ // debug bar (require HTML & development mode)
+ if (self::$bar && !self::$productionMode && self::isHtmlMode()) {
+ self::$bar->render();
+ }
+ }
+
+
+
+ /**
+ * Handler to catch uncaught exception.
+ * @param \Exception
+ * @return void
+ * @internal
+ */
+ public static function _exceptionHandler(\Exception $exception)
+ {
+ if (!headers_sent()) { // for PHP < 5.2.4
+ $protocol = isset($_SERVER['SERVER_PROTOCOL']) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.1';
+ header($protocol . ' 500', TRUE, 500);
+ }
+
+ try {
+ if (self::$productionMode) {
+ try {
+ self::log($exception, self::ERROR);
+ } catch (\Exception $e) {
+ echo 'FATAL ERROR: unable to log error';
+ }
+
+ if (self::$consoleMode) {
+ echo "ERROR: the server encountered an internal error and was unable to complete your request.\n";
+
+ } elseif (self::isHtmlMode()) {
+ require __DIR__ . '/templates/error.phtml';
+ }
+
+ } else {
+ if (self::$consoleMode) { // dump to console
+ echo "$exception\n";
+ if ($file = self::log($exception)) {
+ echo "(stored in $file)\n";
+ if (self::$browser) {
+ exec(self::$browser . ' ' . escapeshellarg($file));
+ }
+ }
+
+ } elseif (self::isHtmlMode()) { // dump to browser
+ self::$blueScreen->render($exception);
+ if (self::$bar) {
+ self::$bar->render();
+ }
+
+ } elseif (!self::fireLog($exception, self::ERROR)) { // AJAX or non-HTML mode
+ $file = self::log($exception);
+ if (!headers_sent()) {
+ header("X-Nette-Error-Log: $file");
+ }
+ }
+ }
+
+ foreach (self::$onFatalError as $handler) {
+ call_user_func($handler, $exception);
+ }
+
+ } catch (\Exception $e) {
+ if (self::$productionMode) {
+ echo self::isHtmlMode() ? 'FATAL ERROR' : 'FATAL ERROR';
+ } else {
+ echo "FATAL ERROR: thrown ", get_class($e), ': ', $e->getMessage(),
+ "\nwhile processing ", get_class($exception), ': ', $exception->getMessage(), "\n";
+ }
+ }
+
+ self::$enabled = FALSE; // un-register shutdown function
+ exit(255);
+ }
+
+
+
+ /**
+ * Handler to catch warnings and notices.
+ * @param int level of the error raised
+ * @param string error message
+ * @param string file that the error was raised in
+ * @param int line number the error was raised at
+ * @param array an array of variables that existed in the scope the error was triggered in
+ * @return bool FALSE to call normal error handler, NULL otherwise
+ * @throws Nette\FatalErrorException
+ * @internal
+ */
+ public static function _errorHandler($severity, $message, $file, $line, $context)
+ {
+ if (self::$scream) {
+ error_reporting(E_ALL | E_STRICT);
+ }
+
+ if (self::$lastError !== FALSE && ($severity & error_reporting()) === $severity) { // tryError mode
+ self::$lastError = new \ErrorException($message, 0, $severity, $file, $line);
+ return NULL;
+ }
+
+ if ($severity === E_RECOVERABLE_ERROR || $severity === E_USER_ERROR) {
+ if (Helpers::findTrace(/*5.2*PHP_VERSION_ID < 50205 ? debug_backtrace() : */debug_backtrace(FALSE), '*::__toString')) {
+ $previous = isset($context['e']) && $context['e'] instanceof \Exception ? $context['e'] : NULL;
+ self::_exceptionHandler(new Nette\FatalErrorException($message, 0, $severity, $file, $line, $context, $previous));
+ }
+ throw new Nette\FatalErrorException($message, 0, $severity, $file, $line, $context);
+
+ } elseif (($severity & error_reporting()) !== $severity) {
+ return FALSE; // calls normal error handler to fill-in error_get_last()
+
+ } elseif (!self::$productionMode && (is_bool(self::$strictMode) ? self::$strictMode : ((self::$strictMode & $severity) === $severity))) {
+ self::_exceptionHandler(new Nette\FatalErrorException($message, 0, $severity, $file, $line, $context));
+ }
+
+ static $types = array(
+ E_WARNING => 'Warning',
+ E_COMPILE_WARNING => 'Warning', // currently unable to handle
+ E_USER_WARNING => 'Warning',
+ E_NOTICE => 'Notice',
+ E_USER_NOTICE => 'Notice',
+ E_STRICT => 'Strict standards',
+ E_DEPRECATED => 'Deprecated',
+ E_USER_DEPRECATED => 'Deprecated',
+ );
+
+ $message = 'PHP ' . (isset($types[$severity]) ? $types[$severity] : 'Unknown error') . ": $message";
+ $count = & self::$errorPanel->data["$message|$file|$line"];
+
+ if ($count++) { // repeated error
+ return NULL;
+
+ } elseif (self::$productionMode) {
+ self::log("$message in $file:$line", self::ERROR);
+ return NULL;
+
+ } else {
+ $ok = self::fireLog(new \ErrorException($message, 0, $severity, $file, $line), self::WARNING);
+ return !self::isHtmlMode() || (!self::$bar && !$ok) ? FALSE : NULL;
+ }
+
+ return FALSE; // call normal error handler
+ }
+
+
+
+ /**
+ * Handles exception thrown in __toString().
+ * @param \Exception
+ * @return void
+ */
+ public static function toStringException(\Exception $exception)
+ {
+ if (self::$enabled) {
+ self::_exceptionHandler($exception);
+ } else {
+ trigger_error($exception->getMessage(), E_USER_ERROR);
+ }
+ }
+
+
+
+ /**
+ * Starts catching potential errors/warnings.
+ * @return void
+ */
+ public static function tryError()
+ {
+ if (!self::$enabled && self::$lastError === FALSE) {
+ set_error_handler(array(__CLASS__, '_errorHandler'));
+ }
+ self::$lastError = NULL;
+ }
+
+
+
+ /**
+ * Returns catched error/warning message.
+ * @param \ErrorException catched error
+ * @return bool
+ */
+ public static function catchError(& $error)
+ {
+ if (!self::$enabled && self::$lastError !== FALSE) {
+ restore_error_handler();
+ }
+ $error = self::$lastError;
+ self::$lastError = FALSE;
+ return (bool) $error;
+ }
+
+
+
+ /********************* useful tools ****************d*g**/
+
+
+
+ /**
+ * Dumps information about a variable in readable format.
+ * @param mixed variable to dump
+ * @param bool return output instead of printing it? (bypasses $productionMode)
+ * @return mixed variable itself or dump
+ */
+ public static function dump($var, $return = FALSE)
+ {
+ if (!$return && self::$productionMode) {
+ return $var;
+ }
+
+ $output = "" . Helpers::htmlDump($var) . "
\n";
+
+ if (!$return) {
+ $trace = /*5.2*PHP_VERSION_ID < 50205 ? debug_backtrace() : */debug_backtrace(FALSE);
+ $item = Helpers::findTrace($trace, 'dump') ?: Helpers::findTrace($trace, __CLASS__ . '::dump');
+ if (isset($item['file'], $item['line']) && is_file($item['file'])) {
+ $lines = file($item['file']);
+ preg_match('#dump\((.*)\)#', $lines[$item['line'] - 1], $m);
+ $output = substr_replace(
+ $output,
+ ' title="' . htmlspecialchars((isset($m[0]) ? "$m[0] \n" : '') . "in file {$item['file']} on line {$item['line']}") . '"',
+ 4, 0);
+
+ if (self::$showLocation) {
+ $output = substr_replace(
+ $output,
+ ' in ' . Helpers::editorLink($item['file'], $item['line']) . ":{$item['line']}",
+ -8, 0);
+ }
+ }
+ }
+
+ if (self::$consoleMode) {
+ if (self::$consoleColors && substr(getenv('TERM'), 0, 5) === 'xterm') {
+ $output = preg_replace_callback('#|#', function($m) {
+ return "\033[" . (isset($m[1], Debugger::$consoleColors[$m[1]]) ? Debugger::$consoleColors[$m[1]] : '0') . "m";
+ }, $output);
+ }
+ $output = htmlspecialchars_decode(strip_tags($output), ENT_QUOTES);
+ }
+
+ if ($return) {
+ return $output;
+
+ } else {
+ echo $output;
+ return $var;
+ }
+ }
+
+
+
+ /**
+ * Starts/stops stopwatch.
+ * @param string name
+ * @return float elapsed seconds
+ */
+ public static function timer($name = NULL)
+ {
+ static $time = array();
+ $now = microtime(TRUE);
+ $delta = isset($time[$name]) ? $now - $time[$name] : 0;
+ $time[$name] = $now;
+ return $delta;
+ }
+
+
+
+ /**
+ * Dumps information about a variable in Nette Debug Bar.
+ * @param mixed variable to dump
+ * @param string optional title
+ * @return mixed variable itself
+ */
+ public static function barDump($var, $title = NULL)
+ {
+ if (!self::$productionMode) {
+ $dump = array();
+ foreach ((is_array($var) ? $var : array('' => $var)) as $key => $val) {
+ $dump[$key] = Helpers::clickableDump($val);
+ }
+ self::$dumpPanel->data[] = array('title' => $title, 'dump' => $dump);
+ }
+ return $var;
+ }
+
+
+
+ /**
+ * Sends message to FireLogger console.
+ * @param mixed message to log
+ * @return bool was successful?
+ */
+ public static function fireLog($message)
+ {
+ if (!self::$productionMode) {
+ return self::$fireLogger->log($message);
+ }
+ }
+
+
+
+ private static function isHtmlMode()
+ {
+ return !self::$ajaxDetected && !self::$consoleMode
+ && !preg_match('#^Content-Type: (?!text/html)#im', implode("\n", headers_list()));
+ }
+
+
+
+ /** @deprecated */
+ public static function addPanel(IBarPanel $panel, $id = NULL)
+ {
+ return self::$bar->addPanel($panel, $id);
+ }
+
+}
diff --git a/libs/Nette/Diagnostics/DefaultBarPanel.php b/libs/Nette/Diagnostics/DefaultBarPanel.php
index 1779305..c969a79 100644
--- a/libs/Nette/Diagnostics/DefaultBarPanel.php
+++ b/libs/Nette/Diagnostics/DefaultBarPanel.php
@@ -1,76 +1,76 @@
-id = $id;
- }
-
-
-
- /**
- * Renders HTML code for custom tab.
- * @return string
- */
- public function getTab()
- {
- ob_start();
- $data = $this->data;
- if ($this->id === 'time') {
- require __DIR__ . '/templates/bar.time.tab.phtml';
- } elseif ($this->id === 'memory') {
- require __DIR__ . '/templates/bar.memory.tab.phtml';
- } elseif ($this->id === 'dumps' && $this->data) {
- require __DIR__ . '/templates/bar.dumps.tab.phtml';
- } elseif ($this->id === 'errors' && $this->data) {
- require __DIR__ . '/templates/bar.errors.tab.phtml';
- }
- return ob_get_clean();
- }
-
-
-
- /**
- * Renders HTML code for custom panel.
- * @return string
- */
- public function getPanel()
- {
- ob_start();
- $data = $this->data;
- if ($this->id === 'dumps') {
- require __DIR__ . '/templates/bar.dumps.panel.phtml';
- } elseif ($this->id === 'errors') {
- require __DIR__ . '/templates/bar.errors.panel.phtml';
- }
- return ob_get_clean();
- }
-
-}
+id = $id;
+ }
+
+
+
+ /**
+ * Renders HTML code for custom tab.
+ * @return string
+ */
+ public function getTab()
+ {
+ ob_start();
+ $data = $this->data;
+ if ($this->id === 'time') {
+ require __DIR__ . '/templates/bar.time.tab.phtml';
+ } elseif ($this->id === 'memory') {
+ require __DIR__ . '/templates/bar.memory.tab.phtml';
+ } elseif ($this->id === 'dumps' && $this->data) {
+ require __DIR__ . '/templates/bar.dumps.tab.phtml';
+ } elseif ($this->id === 'errors' && $this->data) {
+ require __DIR__ . '/templates/bar.errors.tab.phtml';
+ }
+ return ob_get_clean();
+ }
+
+
+
+ /**
+ * Renders HTML code for custom panel.
+ * @return string
+ */
+ public function getPanel()
+ {
+ ob_start();
+ $data = $this->data;
+ if ($this->id === 'dumps') {
+ require __DIR__ . '/templates/bar.dumps.panel.phtml';
+ } elseif ($this->id === 'errors') {
+ require __DIR__ . '/templates/bar.errors.panel.phtml';
+ }
+ return ob_get_clean();
+ }
+
+}
diff --git a/libs/Nette/Diagnostics/FireLogger.php b/libs/Nette/Diagnostics/FireLogger.php
index 2a23cd9..a8e9b3d 100644
--- a/libs/Nette/Diagnostics/FireLogger.php
+++ b/libs/Nette/Diagnostics/FireLogger.php
@@ -1,188 +1,188 @@
- array());
-
-
-
- /**
- * Sends message to FireLogger console.
- * @param mixed
- * @return bool was successful?
- */
- public static function log($message, $priority = self::DEBUG)
- {
- if (!isset($_SERVER['HTTP_X_FIRELOGGER']) || headers_sent()) {
- return FALSE;
- }
-
- $item = array(
- 'name' => 'PHP',
- 'level' => $priority,
- 'order' => count(self::$payload['logs']),
- 'time' => str_pad(number_format((microtime(TRUE) - Debugger::$time) * 1000, 1, '.', ' '), 8, '0', STR_PAD_LEFT) . ' ms',
- 'template' => '',
- 'message' => '',
- 'style' => 'background:#767ab6',
- );
-
- $args = func_get_args();
- if (isset($args[0]) && is_string($args[0])) {
- $item['template'] = array_shift($args);
- }
-
- if (isset($args[0]) && $args[0] instanceof \Exception) {
- $e = array_shift($args);
- $trace = $e->getTrace();
- if (isset($trace[0]['class']) && $trace[0]['class'] === 'Nette\Diagnostics\Debugger'
- && ($trace[0]['function'] === '_shutdownHandler' || $trace[0]['function'] === '_errorHandler')
- ) {
- unset($trace[0]);
- }
-
- $file = str_replace(dirname(dirname(dirname($e->getFile()))), "\xE2\x80\xA6", $e->getFile());
- $item['template'] = ($e instanceof \ErrorException ? '' : get_class($e) . ': ')
- . $e->getMessage() . ($e->getCode() ? ' #' . $e->getCode() : '') . ' in ' . $file . ':' . $e->getLine();
- $item['pathname'] = $e->getFile();
- $item['lineno'] = $e->getLine();
-
- } else {
- $trace = debug_backtrace();
- if (isset($trace[1]['class']) && $trace[1]['class'] === 'Nette\Diagnostics\Debugger'
- && ($trace[1]['function'] === 'fireLog')
- ) {
- unset($trace[0]);
- }
-
- foreach ($trace as $frame) {
- if (isset($frame['file']) && is_file($frame['file'])) {
- $item['pathname'] = $frame['file'];
- $item['lineno'] = $frame['line'];
- break;
- }
- }
- }
-
- $item['exc_info'] = array('', '', array());
- $item['exc_frames'] = array();
-
- foreach ($trace as $frame) {
- $frame += array('file' => NULL, 'line' => NULL, 'class' => NULL, 'type' => NULL, 'function' => NULL, 'object' => NULL, 'args' => NULL);
- $item['exc_info'][2][] = array($frame['file'], $frame['line'], "$frame[class]$frame[type]$frame[function]", $frame['object']);
- $item['exc_frames'][] = $frame['args'];
- }
-
- if (isset($args[0]) && in_array($args[0], array(self::DEBUG, self::INFO, self::WARNING, self::ERROR, self::CRITICAL), TRUE)) {
- $item['level'] = array_shift($args);
- }
-
- $item['args'] = $args;
-
- self::$payload['logs'][] = self::jsonDump($item, -1);
- foreach (str_split(base64_encode(@json_encode(self::$payload)), 4990) as $k => $v) { // intentionally @
- header("FireLogger-de11e-$k:$v");
- }
- return TRUE;
- }
-
-
-
- /**
- * Dump implementation for JSON.
- * @param mixed variable to dump
- * @param int current recursion level
- * @return string
- */
- private static function jsonDump(&$var, $level = 0)
- {
- if (is_bool($var) || is_null($var) || is_int($var) || is_float($var)) {
- return $var;
-
- } elseif (is_string($var)) {
- if (Debugger::$maxLen && strlen($var) > Debugger::$maxLen) {
- $var = substr($var, 0, Debugger::$maxLen) . " \xE2\x80\xA6 ";
- }
- return Nette\Utils\Strings::fixEncoding($var);
-
- } elseif (is_array($var)) {
- static $marker;
- if ($marker === NULL) {
- $marker = uniqid("\x00", TRUE);
- }
- if (isset($var[$marker])) {
- return "\xE2\x80\xA6RECURSION\xE2\x80\xA6";
-
- } elseif ($level < Debugger::$maxDepth || !Debugger::$maxDepth) {
- $var[$marker] = TRUE;
- $res = array();
- foreach ($var as $k => &$v) {
- if ($k !== $marker) {
- $res[self::jsonDump($k)] = self::jsonDump($v, $level + 1);
- }
- }
- unset($var[$marker]);
- return $res;
-
- } else {
- return " \xE2\x80\xA6 ";
- }
-
- } elseif (is_object($var)) {
- $arr = (array) $var;
- static $list = array();
- if (in_array($var, $list, TRUE)) {
- return "\xE2\x80\xA6RECURSION\xE2\x80\xA6";
-
- } elseif ($level < Debugger::$maxDepth || !Debugger::$maxDepth) {
- $list[] = $var;
- $res = array("\x00" => '(object) ' . get_class($var));
- foreach ($arr as $k => &$v) {
- if ($k[0] === "\x00") {
- $k = substr($k, strrpos($k, "\x00") + 1);
- }
- $res[self::jsonDump($k)] = self::jsonDump($v, $level + 1);
- }
- array_pop($list);
- return $res;
-
- } else {
- return " \xE2\x80\xA6 ";
- }
-
- } elseif (is_resource($var)) {
- return "resource " . get_resource_type($var);
-
- } else {
- return "unknown type";
- }
- }
-
-}
+ array());
+
+
+
+ /**
+ * Sends message to FireLogger console.
+ * @param mixed
+ * @return bool was successful?
+ */
+ public static function log($message, $priority = self::DEBUG)
+ {
+ if (!isset($_SERVER['HTTP_X_FIRELOGGER']) || headers_sent()) {
+ return FALSE;
+ }
+
+ $item = array(
+ 'name' => 'PHP',
+ 'level' => $priority,
+ 'order' => count(self::$payload['logs']),
+ 'time' => str_pad(number_format((microtime(TRUE) - Debugger::$time) * 1000, 1, '.', ' '), 8, '0', STR_PAD_LEFT) . ' ms',
+ 'template' => '',
+ 'message' => '',
+ 'style' => 'background:#767ab6',
+ );
+
+ $args = func_get_args();
+ if (isset($args[0]) && is_string($args[0])) {
+ $item['template'] = array_shift($args);
+ }
+
+ if (isset($args[0]) && $args[0] instanceof \Exception) {
+ $e = array_shift($args);
+ $trace = $e->getTrace();
+ if (isset($trace[0]['class']) && $trace[0]['class'] === 'Nette\Diagnostics\Debugger'
+ && ($trace[0]['function'] === '_shutdownHandler' || $trace[0]['function'] === '_errorHandler')
+ ) {
+ unset($trace[0]);
+ }
+
+ $file = str_replace(dirname(dirname(dirname($e->getFile()))), "\xE2\x80\xA6", $e->getFile());
+ $item['template'] = ($e instanceof \ErrorException ? '' : get_class($e) . ': ')
+ . $e->getMessage() . ($e->getCode() ? ' #' . $e->getCode() : '') . ' in ' . $file . ':' . $e->getLine();
+ $item['pathname'] = $e->getFile();
+ $item['lineno'] = $e->getLine();
+
+ } else {
+ $trace = debug_backtrace();
+ if (isset($trace[1]['class']) && $trace[1]['class'] === 'Nette\Diagnostics\Debugger'
+ && ($trace[1]['function'] === 'fireLog')
+ ) {
+ unset($trace[0]);
+ }
+
+ foreach ($trace as $frame) {
+ if (isset($frame['file']) && is_file($frame['file'])) {
+ $item['pathname'] = $frame['file'];
+ $item['lineno'] = $frame['line'];
+ break;
+ }
+ }
+ }
+
+ $item['exc_info'] = array('', '', array());
+ $item['exc_frames'] = array();
+
+ foreach ($trace as $frame) {
+ $frame += array('file' => NULL, 'line' => NULL, 'class' => NULL, 'type' => NULL, 'function' => NULL, 'object' => NULL, 'args' => NULL);
+ $item['exc_info'][2][] = array($frame['file'], $frame['line'], "$frame[class]$frame[type]$frame[function]", $frame['object']);
+ $item['exc_frames'][] = $frame['args'];
+ }
+
+ if (isset($args[0]) && in_array($args[0], array(self::DEBUG, self::INFO, self::WARNING, self::ERROR, self::CRITICAL), TRUE)) {
+ $item['level'] = array_shift($args);
+ }
+
+ $item['args'] = $args;
+
+ self::$payload['logs'][] = self::jsonDump($item, -1);
+ foreach (str_split(base64_encode(@json_encode(self::$payload)), 4990) as $k => $v) { // intentionally @
+ header("FireLogger-de11e-$k:$v");
+ }
+ return TRUE;
+ }
+
+
+
+ /**
+ * Dump implementation for JSON.
+ * @param mixed variable to dump
+ * @param int current recursion level
+ * @return string
+ */
+ private static function jsonDump(&$var, $level = 0)
+ {
+ if (is_bool($var) || is_null($var) || is_int($var) || is_float($var)) {
+ return $var;
+
+ } elseif (is_string($var)) {
+ if (Debugger::$maxLen && strlen($var) > Debugger::$maxLen) {
+ $var = substr($var, 0, Debugger::$maxLen) . " \xE2\x80\xA6 ";
+ }
+ return Nette\Utils\Strings::fixEncoding($var);
+
+ } elseif (is_array($var)) {
+ static $marker;
+ if ($marker === NULL) {
+ $marker = uniqid("\x00", TRUE);
+ }
+ if (isset($var[$marker])) {
+ return "\xE2\x80\xA6RECURSION\xE2\x80\xA6";
+
+ } elseif ($level < Debugger::$maxDepth || !Debugger::$maxDepth) {
+ $var[$marker] = TRUE;
+ $res = array();
+ foreach ($var as $k => &$v) {
+ if ($k !== $marker) {
+ $res[self::jsonDump($k)] = self::jsonDump($v, $level + 1);
+ }
+ }
+ unset($var[$marker]);
+ return $res;
+
+ } else {
+ return " \xE2\x80\xA6 ";
+ }
+
+ } elseif (is_object($var)) {
+ $arr = (array) $var;
+ static $list = array();
+ if (in_array($var, $list, TRUE)) {
+ return "\xE2\x80\xA6RECURSION\xE2\x80\xA6";
+
+ } elseif ($level < Debugger::$maxDepth || !Debugger::$maxDepth) {
+ $list[] = $var;
+ $res = array("\x00" => '(object) ' . get_class($var));
+ foreach ($arr as $k => &$v) {
+ if ($k[0] === "\x00") {
+ $k = substr($k, strrpos($k, "\x00") + 1);
+ }
+ $res[self::jsonDump($k)] = self::jsonDump($v, $level + 1);
+ }
+ array_pop($list);
+ return $res;
+
+ } else {
+ return " \xE2\x80\xA6 ";
+ }
+
+ } elseif (is_resource($var)) {
+ return "resource " . get_resource_type($var);
+
+ } else {
+ return "unknown type";
+ }
+ }
+
+}
diff --git a/libs/Nette/Diagnostics/Helpers.php b/libs/Nette/Diagnostics/Helpers.php
index ed1520f..0315555 100644
--- a/libs/Nette/Diagnostics/Helpers.php
+++ b/libs/Nette/Diagnostics/Helpers.php
@@ -1,196 +1,233 @@
-href(strtr(Debugger::$editor, array('%file' => rawurlencode($file), '%line' => $line)));
- } else {
- $el = Nette\Utils\Html::el('span');
- }
- return $el->title("$file:$line")
- ->setHtml(htmlSpecialChars(rtrim($dir, DIRECTORY_SEPARATOR)) . DIRECTORY_SEPARATOR . '' . htmlSpecialChars(basename($file)) . '');
- }
-
-
-
- /**
- * Internal dump() implementation.
- * @param mixed variable to dump
- * @param int current recursion level
- * @return string
- */
- public static function htmlDump(&$var, $level = 0)
- {
- static $tableUtf, $tableBin, $reBinary = '#[^\x09\x0A\x0D\x20-\x7E\xA0-\x{10FFFF}]#u';
- if ($tableUtf === NULL) {
- foreach (range("\x00", "\xFF") as $ch) {
- if (ord($ch) < 32 && strpos("\r\n\t", $ch) === FALSE) {
- $tableUtf[$ch] = $tableBin[$ch] = '\\x' . str_pad(dechex(ord($ch)), 2, '0', STR_PAD_LEFT);
- } elseif (ord($ch) < 127) {
- $tableUtf[$ch] = $tableBin[$ch] = $ch;
- } else {
- $tableUtf[$ch] = $ch; $tableBin[$ch] = '\\x' . dechex(ord($ch));
- }
- }
- $tableBin["\\"] = '\\\\';
- $tableBin["\r"] = '\\r';
- $tableBin["\n"] = '\\n';
- $tableBin["\t"] = '\\t';
- $tableUtf['\\x'] = $tableBin['\\x'] = '\\\\x';
- }
-
- if (is_bool($var)) {
- return ($var ? 'TRUE' : 'FALSE') . "\n";
-
- } elseif ($var === NULL) {
- return "NULL\n";
-
- } elseif (is_int($var)) {
- return "$var\n";
-
- } elseif (is_float($var)) {
- $var = var_export($var, TRUE);
- if (strpos($var, '.') === FALSE) {
- $var .= '.0';
- }
- return "$var\n";
-
- } elseif (is_string($var)) {
- if (Debugger::$maxLen && strlen($var) > Debugger::$maxLen) {
- $s = htmlSpecialChars(substr($var, 0, Debugger::$maxLen), ENT_NOQUOTES) . ' ... ';
- } else {
- $s = htmlSpecialChars($var, ENT_NOQUOTES);
- }
- $s = strtr($s, preg_match($reBinary, $s) || preg_last_error() ? $tableBin : $tableUtf);
- $len = strlen($var);
- return "\"$s\"" . ($len > 1 ? " ($len)" : "") . "\n";
-
- } elseif (is_array($var)) {
- $s = "array(" . count($var) . ") ";
- $space = str_repeat($space1 = ' ', $level);
- $brackets = range(0, count($var) - 1) === array_keys($var) ? "[]" : "{}";
-
- static $marker;
- if ($marker === NULL) {
- $marker = uniqid("\x00", TRUE);
- }
- if (empty($var)) {
-
- } elseif (isset($var[$marker])) {
- $brackets = $var[$marker];
- $s .= "$brackets[0] *RECURSION* $brackets[1]";
-
- } elseif ($level < Debugger::$maxDepth || !Debugger::$maxDepth) {
- $s .= "$brackets[0]\n";
- $var[$marker] = $brackets;
- foreach ($var as $k => &$v) {
- if ($k === $marker) {
- continue;
- }
- $k = is_int($k) ? $k : '"' . htmlSpecialChars(strtr($k, preg_match($reBinary, $k) || preg_last_error() ? $tableBin : $tableUtf)) . '"';
- $s .= "$space$space1$k => " . self::htmlDump($v, $level + 1);
- }
- unset($var[$marker]);
- $s .= "$space$brackets[1]";
-
- } else {
- $s .= "$brackets[0] ... $brackets[1]";
- }
- return $s . "\n";
-
- } elseif (is_object($var)) {
- $arr = (array) $var;
- $s = "" . get_class($var) . "(" . count($arr) . ") ";
- $space = str_repeat($space1 = ' ', $level);
-
- static $list = array();
- if (empty($arr)) {
-
- } elseif (in_array($var, $list, TRUE)) {
- $s .= "{ *RECURSION* }";
-
- } elseif ($level < Debugger::$maxDepth || !Debugger::$maxDepth) {
- $s .= "{\n";
- $list[] = $var;
- foreach ($arr as $k => &$v) {
- $m = '';
- if ($k[0] === "\x00") {
- $m = $k[1] === '*' ? ' protected' : ' private';
- $k = substr($k, strrpos($k, "\x00") + 1);
- }
- $k = htmlSpecialChars(strtr($k, preg_match($reBinary, $k) || preg_last_error() ? $tableBin : $tableUtf));
- $s .= "$space$space1\"$k\"$m => " . self::htmlDump($v, $level + 1);
- }
- array_pop($list);
- $s .= "$space}";
-
- } else {
- $s .= "{ ... }";
- }
- return $s . "\n";
-
- } elseif (is_resource($var)) {
- return "" . htmlSpecialChars(get_resource_type($var)) . " resource\n";
-
- } else {
- return "unknown type\n";
- }
- }
-
-
-
- /**
- * Dumps variable.
- * @param string
- * @return string
- */
- public static function clickableDump($dump)
- {
- return '' . preg_replace_callback(
- '#^( *)((?>[^(]{1,200}))\((\d+)\) #m',
- function ($m) {
- return "$m[1]$m[2]($m[3]) "
- . (trim($m[1]) || $m[3] < 7
- ? '▼ '
- : '► ');
- },
- self::htmlDump($dump)
- ) . '';
- }
-
-}
+href(strtr(Debugger::$editor, array('%file' => rawurlencode($file), '%line' => $line)))
+ ->title("$file:$line")
+ ->setHtml(htmlSpecialChars(rtrim($dir, DIRECTORY_SEPARATOR)) . DIRECTORY_SEPARATOR . '' . htmlSpecialChars(basename($file)) . '');
+ } else {
+ return Nette\Utils\Html::el('span')->setText($file);
+ }
+ }
+
+
+
+ /**
+ * Internal dump() implementation.
+ * @param mixed variable to dump
+ * @param int current recursion level
+ * @return string
+ */
+ public static function htmlDump(&$var, $level = 0)
+ {
+ static $tableUtf, $tableBin, $reBinary = '#[^\x09\x0A\x0D\x20-\x7E\xA0-\x{10FFFF}]#u';
+ if ($tableUtf === NULL) {
+ foreach (range("\x00", "\xFF") as $ch) {
+ if (ord($ch) < 32 && strpos("\r\n\t", $ch) === FALSE) {
+ $tableUtf[$ch] = $tableBin[$ch] = '\\x' . str_pad(dechex(ord($ch)), 2, '0', STR_PAD_LEFT);
+ } elseif (ord($ch) < 127) {
+ $tableUtf[$ch] = $tableBin[$ch] = $ch;
+ } else {
+ $tableUtf[$ch] = $ch; $tableBin[$ch] = '\\x' . dechex(ord($ch));
+ }
+ }
+ $tableBin["\\"] = '\\\\';
+ $tableBin["\r"] = '\\r';
+ $tableBin["\n"] = '\\n';
+ $tableBin["\t"] = '\\t';
+ $tableUtf['\\x'] = $tableBin['\\x'] = '\\\\x';
+ }
+
+ if (is_bool($var)) {
+ return '' . ($var ? 'TRUE' : 'FALSE') . "\n";
+
+ } elseif ($var === NULL) {
+ return "NULL\n";
+
+ } elseif (is_int($var)) {
+ return "$var\n";
+
+ } elseif (is_float($var)) {
+ $var = var_export($var, TRUE);
+ if (strpos($var, '.') === FALSE) {
+ $var .= '.0';
+ }
+ return "$var\n";
+
+ } elseif (is_string($var)) {
+ if (Debugger::$maxLen && strlen($var) > Debugger::$maxLen) {
+ $s = htmlSpecialChars(substr($var, 0, Debugger::$maxLen), ENT_NOQUOTES, 'ISO-8859-1') . ' ... ';
+ } else {
+ $s = htmlSpecialChars($var, ENT_NOQUOTES, 'ISO-8859-1');
+ }
+ $s = strtr($s, preg_match($reBinary, $s) || preg_last_error() ? $tableBin : $tableUtf);
+ $len = strlen($var);
+ return "\"$s\"" . ($len > 1 ? " ($len)" : "") . "\n";
+
+ } elseif (is_array($var)) {
+ $s = 'array(' . count($var) . ") ";
+ $space = str_repeat($space1 = ' ', $level);
+ $brackets = range(0, count($var) - 1) === array_keys($var) ? "[]" : "{}";
+
+ static $marker;
+ if ($marker === NULL) {
+ $marker = uniqid("\x00", TRUE);
+ }
+ if (empty($var)) {
+
+ } elseif (isset($var[$marker])) {
+ $brackets = $var[$marker];
+ $s .= "$brackets[0] *RECURSION* $brackets[1]";
+
+ } elseif ($level < Debugger::$maxDepth || !Debugger::$maxDepth) {
+ $s .= "$brackets[0]\n";
+ $var[$marker] = $brackets;
+ foreach ($var as $k => &$v) {
+ if ($k === $marker) {
+ continue;
+ }
+ $k = strtr($k, preg_match($reBinary, $k) || preg_last_error() ? $tableBin : $tableUtf);
+ $k = htmlSpecialChars(preg_match('#^\w+$#', $k) ? $k : "\"$k\"");
+ $s .= "$space$space1$k => " . self::htmlDump($v, $level + 1);
+ }
+ unset($var[$marker]);
+ $s .= "$space$brackets[1]";
+
+ } else {
+ $s .= "$brackets[0] ... $brackets[1]";
+ }
+ return $s . "\n";
+
+ } elseif (is_object($var)) {
+ if ($var instanceof \Closure) {
+ $rc = new \ReflectionFunction($var);
+ $arr = array();
+ foreach ($rc->getParameters() as $param) {
+ $arr[] = '$' . $param->getName();
+ }
+ $arr = array('file' => $rc->getFileName(), 'line' => $rc->getStartLine(), 'parameters' => implode(', ', $arr));
+ } else {
+ $arr = (array) $var;
+ }
+ $s = '' . get_class($var) . "(" . count($arr) . ") ";
+ $space = str_repeat($space1 = ' ', $level);
+
+ static $list = array();
+ if (empty($arr)) {
+
+ } elseif (in_array($var, $list, TRUE)) {
+ $s .= "{ *RECURSION* }";
+
+ } elseif ($level < Debugger::$maxDepth || !Debugger::$maxDepth || $var instanceof \Closure) {
+ $s .= "{\n";
+ $list[] = $var;
+ foreach ($arr as $k => &$v) {
+ $m = '';
+ if ($k[0] === "\x00") {
+ $m = ' ' . ($k[1] === '*' ? 'protected' : 'private') . '';
+ $k = substr($k, strrpos($k, "\x00") + 1);
+ }
+ $k = strtr($k, preg_match($reBinary, $k) || preg_last_error() ? $tableBin : $tableUtf);
+ $k = htmlSpecialChars(preg_match('#^\w+$#', $k) ? $k : "\"$k\"");
+ $s .= "$space$space1$k$m => " . self::htmlDump($v, $level + 1);
+ }
+ array_pop($list);
+ $s .= "$space}";
+
+ } else {
+ $s .= "{ ... }";
+ }
+ return $s . "\n";
+
+ } elseif (is_resource($var)) {
+ $type = get_resource_type($var);
+ $s = '' . htmlSpecialChars($type) . " resource ";
+
+ static $info = array('stream' => 'stream_get_meta_data', 'curl' => 'curl_getinfo');
+ if (isset($info[$type])) {
+ $space = str_repeat($space1 = ' ', $level);
+ $s .= "{\n";
+ foreach (call_user_func($info[$type], $var) as $k => $v) {
+ $s .= $space . $space1 . '' . htmlSpecialChars($k) . " => " . self::htmlDump($v, $level + 1);
+ }
+ $s .= "$space}";
+ }
+ return $s . "\n";
+
+ } else {
+ return "unknown type\n";
+ }
+ }
+
+
+
+ /**
+ * Dumps variable.
+ * @param string
+ * @return string
+ */
+ public static function clickableDump($dump, $collapsed = FALSE)
+ {
+ return '' . preg_replace_callback(
+ '#^( *)((?>[^(\r\n]{1,200}))\((\d+)\) #m',
+ function($m) use ($collapsed) {
+ return "$m[1]$m[2]($m[3]) "
+ . (($m[1] || !$collapsed) && ($m[3] < 7)
+ ? '▼ '
+ : '► ');
+ },
+ self::htmlDump($dump)
+ ) . '';
+ }
+
+
+
+ public static function findTrace(array $trace, $method, & $index = NULL)
+ {
+ $m = explode('::', $method);
+ foreach ($trace as $i => $item) {
+ if (isset($item['function']) && $item['function'] === end($m)
+ && isset($item['class']) === isset($m[1])
+ && (!isset($item['class']) || $item['class'] === $m[0] || $m[0] === '*' || is_subclass_of($item['class'], $m[0]))
+ ) {
+ $index = $i;
+ return $item;
+ }
+ }
+ }
+
+}
diff --git a/libs/Nette/Diagnostics/IBarPanel.php b/libs/Nette/Diagnostics/IBarPanel.php
index f959503..97262b5 100644
--- a/libs/Nette/Diagnostics/IBarPanel.php
+++ b/libs/Nette/Diagnostics/IBarPanel.php
@@ -1,38 +1,38 @@
-directory)) {
- throw new Nette\DirectoryNotFoundException("Directory '$this->directory' is not found or is not directory.");
- }
-
- if (is_array($message)) {
- $message = implode(' ', $message);
- }
- $res = error_log(trim($message) . PHP_EOL, 3, $this->directory . '/' . strtolower($priority) . '.log');
-
- if (($priority === self::ERROR || $priority === self::CRITICAL) && $this->email && $this->mailer
- && @filemtime($this->directory . '/email-sent') + self::$emailSnooze < time() // @ - file may not exist
- && @file_put_contents($this->directory . '/email-sent', 'sent') // @ - file may not be writable
- ) {
- call_user_func($this->mailer, $message, $this->email);
- }
- return $res;
- }
-
-
-
- /**
- * Default mailer.
- * @param string
- * @param string
- * @return void
- */
- private static function defaultMailer($message, $email)
- {
- $host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] :
- (isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : '');
-
- $parts = str_replace(
- array("\r\n", "\n"),
- array("\n", PHP_EOL),
- array(
- 'headers' => "From: noreply@$host\nX-Mailer: Nette Framework\n",
- 'subject' => "PHP: An error occurred on the server $host",
- 'body' => "[" . @date('Y-m-d H:i:s') . "] $message", // @ - timezone may not be set
- )
- );
-
- mail($email, $parts['subject'], $parts['body'], $parts['headers']);
- }
-
-}
+directory)) {
+ throw new Nette\DirectoryNotFoundException("Directory '$this->directory' is not found or is not directory.");
+ }
+
+ if (is_array($message)) {
+ $message = implode(' ', $message);
+ }
+ $res = error_log(trim($message) . PHP_EOL, 3, $this->directory . '/' . strtolower($priority) . '.log');
+
+ if (($priority === self::ERROR || $priority === self::CRITICAL) && $this->email && $this->mailer
+ && @filemtime($this->directory . '/email-sent') + self::$emailSnooze < time() // @ - file may not exist
+ && @file_put_contents($this->directory . '/email-sent', 'sent') // @ - file may not be writable
+ ) {
+ Nette\Callback::create($this->mailer)->invoke($message, $this->email);
+ }
+ return $res;
+ }
+
+
+
+ /**
+ * Default mailer.
+ * @param string
+ * @param string
+ * @return void
+ */
+ public static function defaultMailer($message, $email)
+ {
+ $host = php_uname('n');
+ foreach (array('HTTP_HOST','SERVER_NAME', 'HOSTNAME') as $item) {
+ if (isset($_SERVER[$item])) {
+ $host = $_SERVER[$item]; break;
+ }
+ }
+
+ $parts = str_replace(
+ array("\r\n", "\n"),
+ array("\n", PHP_EOL),
+ array(
+ 'headers' => implode("\n", array(
+ "From: noreply@$host",
+ 'X-Mailer: Nette Framework',
+ 'Content-Type: text/plain; charset=UTF-8',
+ 'Content-Transfer-Encoding: 8bit',
+ )) . "\n",
+ 'subject' => "PHP: An error occurred on the server $host",
+ 'body' => "[" . @date('Y-m-d H:i:s') . "] $message", // @ - timezone may not be set
+ )
+ );
+
+ mail($email, $parts['subject'], $parts['body'], $parts['headers']);
+ }
+
+}
diff --git a/libs/Nette/Diagnostics/shortcuts.php b/libs/Nette/Diagnostics/shortcuts.php
new file mode 100644
index 0000000..b18d20e
--- /dev/null
+++ b/libs/Nette/Diagnostics/shortcuts.php
@@ -0,0 +1,25 @@
+
-
-
-
-Dumped variables
-
-
-
-
-
-
-
-
-
- $dump): ?>
-
- |
- |
-
-
-
-
-
+
+
+
+
+Dumped variables
+
+
+
+
+
+
+
+
+
+ $dump): ?>
+
+ |
+ |
+
+
+
+
+
diff --git a/libs/Nette/Diagnostics/templates/bar.dumps.tab.phtml b/libs/Nette/Diagnostics/templates/bar.dumps.tab.phtml
index fd6a0c7..72c5bcb 100644
--- a/libs/Nette/Diagnostics/templates/bar.dumps.tab.phtml
+++ b/libs/Nette/Diagnostics/templates/bar.dumps.tab.phtml
@@ -1,14 +1,14 @@
-
-
variables
+
+
variables
diff --git a/libs/Nette/Diagnostics/templates/bar.errors.panel.phtml b/libs/Nette/Diagnostics/templates/bar.errors.panel.phtml
index c0b7388..9dbc604 100644
--- a/libs/Nette/Diagnostics/templates/bar.errors.panel.phtml
+++ b/libs/Nette/Diagnostics/templates/bar.errors.panel.phtml
@@ -1,26 +1,26 @@
-
-Errors
-
-
-
-
- $count): list($message, $file, $line) = explode('|', $item) ?>
-
- |
- |
-
-
-
-
+
+Errors
+
+
+
+
+ $count): list($message, $file, $line) = explode('|', $item) ?>
+
+ |
+ |
+
+
+
+
diff --git a/libs/Nette/Diagnostics/templates/bar.errors.tab.phtml b/libs/Nette/Diagnostics/templates/bar.errors.tab.phtml
index 35fb7bc..4eb5b15 100644
--- a/libs/Nette/Diagnostics/templates/bar.errors.tab.phtml
+++ b/libs/Nette/Diagnostics/templates/bar.errors.tab.phtml
@@ -1,15 +1,15 @@
-
-
errors
+
+
errors
diff --git a/libs/Nette/Diagnostics/templates/bar.memory.tab.phtml b/libs/Nette/Diagnostics/templates/bar.memory.tab.phtml
index 6998880..b434cc4 100644
--- a/libs/Nette/Diagnostics/templates/bar.memory.tab.phtml
+++ b/libs/Nette/Diagnostics/templates/bar.memory.tab.phtml
@@ -1,15 +1,15 @@
-
-
MB
+
+
MB
diff --git a/libs/Nette/Diagnostics/templates/bar.phtml b/libs/Nette/Diagnostics/templates/bar.phtml
index f089a4c..6735180 100644
--- a/libs/Nette/Diagnostics/templates/bar.phtml
+++ b/libs/Nette/Diagnostics/templates/bar.phtml
@@ -1,102 +1,621 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- $panel): if (!$panel['panel']) continue; ?>
-
-
-
-
-
-
- - "> Nette Framework
-
- - ', trim($panel['tab']), ''; endif ?>
-
- - ×
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $panel): if (!$panel['panel']) continue; ?>
+
+
+
+
+
+
+ - "> Nette Framework
+
+ - ', trim($panel['tab']), ''; endif ?>
+
+ - ×
+
+
+
+
+
+
+
+
+
+
+
diff --git a/libs/Nette/Diagnostics/templates/bar.time.tab.phtml b/libs/Nette/Diagnostics/templates/bar.time.tab.phtml
index b7a3d22..4bcd33b 100644
--- a/libs/Nette/Diagnostics/templates/bar.time.tab.phtml
+++ b/libs/Nette/Diagnostics/templates/bar.time.tab.phtml
@@ -1,15 +1,15 @@
-
-
ms
+
+
ms
diff --git a/libs/Nette/Diagnostics/templates/bluescreen.phtml b/libs/Nette/Diagnostics/templates/bluescreen.phtml
index 5ba38a3..2e9cc9e 100644
--- a/libs/Nette/Diagnostics/templates/bluescreen.phtml
+++ b/libs/Nette/Diagnostics/templates/bluescreen.phtml
@@ -1,332 +1,644 @@
- 'Fatal Error',
- E_USER_ERROR => 'User Error',
- E_RECOVERABLE_ERROR => 'Recoverable Error',
- E_CORE_ERROR => 'Core Error',
- E_COMPILE_ERROR => 'Compile Error',
- E_PARSE => 'Parse Error',
- E_WARNING => 'Warning',
- E_CORE_WARNING => 'Core Warning',
- E_COMPILE_WARNING => 'Compile Warning',
- E_USER_WARNING => 'User Warning',
- E_NOTICE => 'Notice',
- E_USER_NOTICE => 'User Notice',
- E_STRICT => 'Strict',
- E_DEPRECATED => 'Deprecated',
- E_USER_DEPRECATED => 'User Deprecated',
-);
-
-$title = ($exception instanceof Nette\FatalErrorException && isset($errorTypes[$exception->getSeverity()])) ? $errorTypes[$exception->getSeverity()] : get_class($exception);
-
-$expandPath = NETTE_DIR . DIRECTORY_SEPARATOR; // . 'Utils' . DIRECTORY_SEPARATOR . 'Object';
-$counter = 0;
-
-?>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
▼
-
-
-
-
-
-
-
-
-
-
-
-
-
-
getCode() ? ' #' . $ex->getCode() : '') ?>
-
-
getMessage()) ?>
-
-
-
-
-
-
-
-
-
-
-
-
- getTrace(); $expanded = NULL ?>
- getFile(), $expandPath) === 0) {
- foreach ($stack as $key => $row) {
- if (isset($row['file']) && strpos($row['file'], $expandPath) !== 0) { $expanded = $key; break; }
- }
- } ?>
- getFile())): ?>
-
-
-
-
-
File: getFile(), $ex->getLine()) ?> Line: getLine() ?>
-
getFile(), $ex->getLine()) ?>
-
-
-
-
-
-
-
-
-
-
-
-
- $row): ?>
-
-
-
-
-
- inner-code
-
-
- ">source ►
-
-
-
-
- (">arguments ►)
-
-
-
- ">
-
- getParameters();
- } catch (\Exception $e) {
- $params = array();
- }
- foreach ($row['args'] as $k => $v) {
- echo '| ', (isset($params[$k]) ? '$' . $params[$k]->name : "#$k"), ' | ';
- echo Helpers::clickableDump($v);
- echo " |
\n";
- }
- ?>
-
-
-
-
-
-
- id="netteBsSrc">
-
-
-
-
-
-
-
-
-
-
- context) && is_array($ex->context)):?>
-
-
-
-
-
-
- context as $k => $v) {
- echo '| $', htmlspecialchars($k), ' | ', Helpers::clickableDump($v), " |
\n";
- }
- ?>
-
-
-
-
-
- getPrevious()) || (isset($ex->previous) && $ex = $ex->previous)); ?>
-
' ?>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- $v) {
- echo '| ', htmlspecialchars($k), ' | ';
- echo '', Helpers::clickableDump($v), " |
\n";
- }
- ?>
-
-
-
-
-
-
-
-
- | ', htmlspecialchars($v), " | \n";
- }
- ?>
-
-
-
-
-
$_SERVER
-
-
empty
-
-
-
- $v) echo '| ', htmlspecialchars($k), ' | ', Helpers::clickableDump($v), " |
\n";
- ?>
-
-
-
-
-
-
-
-
-
-
-
-
-
Headers
-
-
- $v) echo '| ', htmlspecialchars($k), ' | ', htmlspecialchars($v), " |
\n";
- ?>
-
-
-
-
-
-
-
$
-
-
empty
-
-
-
- $v) echo '| ', htmlspecialchars($k), ' | ', Helpers::clickableDump($v), " |
\n";
- ?>
-
-
-
-
-
-
-
-
-
-
-
-
-
Headers
-
-
';
- ?>
-
-
no headers
-
-
-
-
-
- - Report generated at
-
-
-
-
-
- - PHP
-
- - (revision )
-
-
-
-
-
-
-
+ 'Fatal Error',
+ E_USER_ERROR => 'User Error',
+ E_RECOVERABLE_ERROR => 'Recoverable Error',
+ E_CORE_ERROR => 'Core Error',
+ E_COMPILE_ERROR => 'Compile Error',
+ E_PARSE => 'Parse Error',
+ E_WARNING => 'Warning',
+ E_CORE_WARNING => 'Core Warning',
+ E_COMPILE_WARNING => 'Compile Warning',
+ E_USER_WARNING => 'User Warning',
+ E_NOTICE => 'Notice',
+ E_USER_NOTICE => 'User Notice',
+ E_STRICT => 'Strict',
+ E_DEPRECATED => 'Deprecated',
+ E_USER_DEPRECATED => 'User Deprecated',
+);
+
+$title = ($exception instanceof Nette\FatalErrorException && isset($errorTypes[$exception->getSeverity()])) ? $errorTypes[$exception->getSeverity()] : get_class($exception);
+
+$expandPath = NETTE_DIR . DIRECTORY_SEPARATOR; // . 'Utils' . DIRECTORY_SEPARATOR . 'Object';
+$counter = 0;
+
+?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
▼
+
+
+
+
+
+
+
+
+
+
+
+
+
+
getCode() ? ' #' . $ex->getCode() : '')) ?>
+
+
getMessage()) ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+ getTrace(); $expanded = NULL ?>
+ getFile(), $expandPath) === 0) {
+ foreach ($stack as $key => $row) {
+ if (isset($row['file']) && strpos($row['file'], $expandPath) !== 0) { $expanded = $key; break; }
+ }
+ } ?>
+
+
+
+
+
+
File: getFile(), $ex->getLine()) ?> Line: getLine() ?>
+ getFile())): ?>getFile(), $ex->getLine(), 15, isset($ex->context) ? $ex->context : NULL) ?>
+
+
+
+
+
+
+
+
+
+
+
+ $row): ?>
+
+
+
+
+
+ inner-code
+
+
+ ">source ►
+
+
+
+
+ (">arguments ►)
+
+
+
+ ">
+
+ getParameters();
+ } catch (\Exception $e) {
+ $params = array();
+ }
+ foreach ($row['args'] as $k => $v) {
+ echo '| ', htmlspecialchars(isset($params[$k]) ? '$' . $params[$k]->name : "#$k"), ' | ';
+ echo Helpers::clickableDump($v);
+ echo " |
\n";
+ }
+ ?>
+
+
+
+
+
+
+ id="netteBsSrc">
+
+
+
+
+
+
+
+
+
+
+ context) && is_array($ex->context)):?>
+
+
+
+
+
+
+ context as $k => $v) {
+ echo '| $', htmlspecialchars($k), ' | ', Helpers::clickableDump($v), " |
\n";
+ }
+ ?>
+
+
+
+
+
+ getPrevious()) || (isset($ex->previous) && $ex = $ex->previous)); ?>
+
' ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $v) echo '| ', htmlspecialchars($k), ' | ', Helpers::clickableDump($v), " |
\n";
+ ?>
+
+
+
+
+
+
+
+
empty
+
+
+ $v) echo '| ', htmlspecialchars($k), ' | ', $k === '__NF' ? 'Nette Session' : Helpers::clickableDump($v), " |
\n";
+ ?>
+
+
+
+
+
+
+
+
+
+ $v) echo '| ', htmlspecialchars($k), ' | ', Helpers::clickableDump($v), " |
\n";
+ ?>
+
+
+
+
+
+
+
+
+
+ $v) {
+ echo '| ', htmlspecialchars($k), ' | ';
+ echo '', Helpers::clickableDump($v), " |
\n";
+ }
+ ?>
+
+
+
+
+
+
+
+
+ | ', htmlspecialchars($v), " | \n";
+ }
+ ?>
+
+
+
+
+
+
+ |.+$#s', '', ob_get_clean()) ?>
+
+
+
+
+
+
+
+
+
+
+
Headers
+
+
+ $v) echo '| ', htmlspecialchars($k), ' | ', htmlspecialchars($v), " |
\n";
+ ?>
+
+
+
+
+
+
+
$
+
+
empty
+
+
+
+ $v) echo '| ', htmlspecialchars($k), ' | ', Helpers::clickableDump($v), " |
\n";
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
Headers
+
+
';
+ ?>
+
+
no headers
+
+
+
+
+
+
+
+
+
+
+
+
+ - Report generated at
+
+
+
+
+
+ - PHP
+
+ - (revision )
+
+
+
+
+
+
+