From 81340d349be07d3931965c1c1b297dde5776f898 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Tue, 12 Jan 2010 16:00:32 +0100 Subject: [PATCH 1/3] [Foundation] updated the autoloader, added a new one --- src/Symfony/Foundation/ClassLoader.php | 77 +++------ .../Foundation/UniversalClassLoader.php | 147 ++++++++++++++++++ 2 files changed, 166 insertions(+), 58 deletions(-) create mode 100644 src/Symfony/Foundation/UniversalClassLoader.php diff --git a/src/Symfony/Foundation/ClassLoader.php b/src/Symfony/Foundation/ClassLoader.php index 081776a2ec..27eaad343c 100644 --- a/src/Symfony/Foundation/ClassLoader.php +++ b/src/Symfony/Foundation/ClassLoader.php @@ -15,7 +15,7 @@ namespace Symfony\Foundation; * ClassLoader implementation that implements the technical interoperability * standards for PHP 5.3 namespaces and class names. * - * Based on http://groups.google.com/group/php-standards/web/final-proposal + * Based on http://groups.google.com/group/php-standards/web/psr-0-final-proposal * * Example usage: * @@ -42,19 +42,7 @@ class ClassLoader */ public function registerNamespace($namespace, $includePath = null) { - if (!isset($this->namespaces[$namespace])) - { - $this->namespaces[$namespace] = $includePath; - } - else - { - if (!is_array($this->namespaces[$namespace])) - { - $this->namespaces[$namespace] = array($this->namespaces[$namespace]); - } - - $this->namespaces[$namespace][] = $includePath; - } + $this->namespaces[$namespace] = $includePath; } /** @@ -65,14 +53,6 @@ class ClassLoader spl_autoload_register(array($this, 'loadClass')); } - /** - * Uninstalls this class loader from the SPL autoloader stack. - */ - public function unregister() - { - spl_autoload_unregister(array($this, 'loadClass')); - } - /** * Loads the given class or interface. * @@ -81,43 +61,24 @@ class ClassLoader public function loadClass($className) { $vendor = substr($className, 0, stripos($className, '\\')); - if ($vendor || isset($this->namespaces[''])) + if (!isset($this->namespaces[$vendor])) { - $fileName = ''; - $namespace = ''; - if (false !== ($lastNsPos = strripos($className, '\\'))) - { - $namespace = substr($className, 0, $lastNsPos); - $className = substr($className, $lastNsPos + 1); - $fileName = str_replace('\\', DIRECTORY_SEPARATOR, $namespace).DIRECTORY_SEPARATOR; - } - $fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className).'.php'; - - if (null !== $this->namespaces[$vendor]) - { - if (is_array($this->namespaces[$vendor])) - { - foreach ($this->namespaces[$vendor] as $dir) - { - if (!file_exists($dir.DIRECTORY_SEPARATOR.$fileName)) - { - continue; - } - - require $dir.DIRECTORY_SEPARATOR.$fileName; - - break; - } - } - else - { - require $this->namespaces[$vendor].DIRECTORY_SEPARATOR.$fileName; - } - } - else - { - require $fileName; - } + return; } + + if (false !== ($lastNsPos = strripos($className, '\\'))) + { + $namespace = substr($className, 0, $lastNsPos); + $className = substr($className, $lastNsPos + 1); + $fileName = str_replace('\\', DIRECTORY_SEPARATOR, $namespace).DIRECTORY_SEPARATOR; + } + else + { + $namespace = ''; + $fileName = ''; + } + $fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className).'.php'; + + require $this->namespaces[$vendor].DIRECTORY_SEPARATOR.$fileName; } } diff --git a/src/Symfony/Foundation/UniversalClassLoader.php b/src/Symfony/Foundation/UniversalClassLoader.php new file mode 100644 index 0000000000..5cffc6b0e0 --- /dev/null +++ b/src/Symfony/Foundation/UniversalClassLoader.php @@ -0,0 +1,147 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * UniversalClassLoader implements a "universal" autoloder for PHP 5.3. + * + * It is able to load classes that use either: + * + * * The technical interoperability standards for PHP 5.3 namespaces and + * class names (http://groups.google.com/group/php-standards/web/final-proposal); + * + * * The PEAR naming convention for classes (http://pear.php.net/). + * + * Classes from a sub-namespace or a sub-hierarchy of PEAR classes can be + * looked for in a list of locations to ease the vendoring of a sub-set of + * classes for large projects. + * + * Example usage: + * + * $loader = new ClassLoader(); + * + * // register classes with namespaces + * $loader->registerNamespaces(array( + * 'Symfony\Components' => __DIR__.'/components', + * 'Symfony' => __DIR__.'/framework', + * )); + * + * // register a library using the PEAR naming convention + * $loader->registerClasses(array( + * 'Swift_' => __DIR__.'/Swift', + * )); + * + * // activate the autoloader + * $loader->register(); + * + * In this example, if you try to use a class in the Symfony\Components + * namespace or one of its children (Symfony\Components\Console for instance), + * the autoloader will first look for the class under the components/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * @package symfony + * @subpackage foundation + * @author Fabien Potencier + */ +class UniversalClassLoader +{ + protected $namespaces = array(); + protected $prefixes = array(); + + /** + * Registers an array of namespaces + * + * @param array $namespaces An array of namespaces (namespaces as keys and locations as values) + */ + public function registerNamespaces(array $namespaces) + { + $this->namespaces = array_merge($this->namespaces, $namespaces); + } + + /** + * Registers a namespace. + * + * @param string $namespace The namespace + * @param string $path The location of the namespace + */ + public function registerNamespace($namespace, $path) + { + $this->namespaces[$namespace] = $path; + } + + /** + * Registers an array of classes using the PEAR naming convention. + * + * @param array $classes An array of classes (prefixes as keys and locations as values) + */ + public function registerPrefixes(array $classes) + { + $this->prefixes = array_merge($this->prefixes, $classes); + } + + /** + * Registers a set of classes using the PEAR naming convention. + * + * @param string $prefix The classes prefix + * @param string $path The location of the classes + */ + public function registerPrefix($prefix, $path) + { + $this->prefixes[$prefix] = $path; + } + + /** + * Registers this instance as an autoloader. + */ + public function register() + { + spl_autoload_register(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + */ + public function loadClass($class) + { + if (false !== ($pos = strripos($class, '\\'))) + { + // namespaced class name + $namespace = substr($class, 0, $pos); + foreach ($this->namespaces as $ns => $dir) + { + if (0 === strpos($namespace, $ns)) + { + $class = substr($class, $pos + 1); + require $dir.DIRECTORY_SEPARATOR.str_replace('\\', DIRECTORY_SEPARATOR, $namespace).DIRECTORY_SEPARATOR.str_replace('_', DIRECTORY_SEPARATOR, $class).'.php'; + + return; + } + } + } + else + { + // PEAR-like class name + foreach ($this->prefixes as $prefix => $dir) + { + if (0 === strpos($class, $prefix)) + { + require $dir.DIRECTORY_SEPARATOR.str_replace('_', DIRECTORY_SEPARATOR, $class).'.php'; + + return; + } + } + } + } +} From ad1d11b032a523a9d989cda8f01419717081f6a6 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Thu, 14 Jan 2010 10:45:37 +0100 Subject: [PATCH 2/3] [Console] added a Shell --- .../Components/Console/Command/Command.php | 10 ++ src/Symfony/Components/Console/Shell.php | 146 ++++++++++++++++++ .../Components/Console/TestCommand.php | 5 - .../Console/Command/CommandTest.php | 4 +- 4 files changed, 158 insertions(+), 7 deletions(-) create mode 100644 src/Symfony/Components/Console/Shell.php diff --git a/src/Symfony/Components/Console/Command/Command.php b/src/Symfony/Components/Console/Command/Command.php index 6489c27838..68385097ef 100644 --- a/src/Symfony/Components/Console/Command/Command.php +++ b/src/Symfony/Components/Console/Command/Command.php @@ -196,6 +196,16 @@ class Command return $this; } + /** + * Gets the InputDefinition attached to this Command. + * + * @return InputDefinition $definition An InputDefinition instance + */ + public function getDefinition() + { + return $this->definition; + } + /** * Adds an argument. * diff --git a/src/Symfony/Components/Console/Shell.php b/src/Symfony/Components/Console/Shell.php new file mode 100644 index 0000000000..08e0152ffc --- /dev/null +++ b/src/Symfony/Components/Console/Shell.php @@ -0,0 +1,146 @@ + + * + * This source file is subject to the MIT license that is bundled + * with this source code in the file LICENSE. + */ + +/** + * A Shell wraps an Application to add shell capabilities to it. + * + * This class only works with a PHP compiled with readline support + * (either --with-readline or --with-libedit) + * + * @package symfony + * @subpackage cli + * @author Fabien Potencier + */ +class Shell +{ + protected $application; + protected $history; + protected $output; + + /** + * Constructor. + * + * If there is no readline support for the current PHP executable + * a \RuntimeException exception is thrown. + * + * @param Application $application An application instance + */ + public function __construct(Application $application) + { + if (!function_exists('readline')) + { + throw new \RuntimeException('Unable to start the shell as the Readline extension is not enabled.'); + } + + $this->application = $application; + $this->history = getenv('HOME').'/.history_'.$application->getName(); + $this->output = new ConsoleOutput(); + } + + /** + * Runs the shell. + */ + public function run() + { + $this->application->setAutoExit(false); + $this->application->setCatchExceptions(true); + + readline_read_history($this->history); + readline_completion_function(array($this, 'autocompleter')); + + $this->output->write($this->getHeader()); + while (true) + { + $command = readline($this->application->getName().' > '); + + if (false === $command) + { + $this->output->write("\n"); + + break; + } + + readline_add_history($command); + readline_write_history($this->history); + + if (0 !== $ret = $this->application->run(new StringInput($command), $this->output)) + { + $this->output->write(sprintf('The command terminated with an error status (%s)', $ret)); + } + } + } + + /** + * Tries to return autocompletion for the current entered text. + * + * @param string $text The last segment of the entered text + * @param integer $position The current position + */ + protected function autocompleter($text, $position) + { + $info = readline_info(); + $text = substr($info['line_buffer'], 0, $info['end']); + + if ($info['point'] !== $info['end']) + { + return true; + } + + // task name? + if (false === strpos($text, ' ') || !$text) + { + return array_keys($this->application->getCommands()); + } + + // options and arguments? + try + { + $command = $this->application->findCommand(substr($text, 0, strpos($text, ' '))); + } + catch (\Exception $e) + { + return true; + } + + $list = array('--help'); + foreach ($command->getDefinition()->getOptions() as $option) + { + $list[] = '--'.$option->getName(); + } + + return $list; + } + + /** + * Returns the shell header. + * + * @return string The header string + */ + protected function getHeader() + { + return <<{$this->application->getName()} shell. + +At the prompt, type help for some help, +or list to get a list available commands. + +To exist the shell, type ^D. + +EOF; + } +} diff --git a/tests/fixtures/Symfony/Components/Console/TestCommand.php b/tests/fixtures/Symfony/Components/Console/TestCommand.php index b42544f753..c0c02dd280 100644 --- a/tests/fixtures/Symfony/Components/Console/TestCommand.php +++ b/tests/fixtures/Symfony/Components/Console/TestCommand.php @@ -26,11 +26,6 @@ class TestCommand extends Command return $this->application; } - public function getDefinition() - { - return $this->definition; - } - protected function execute(InputInterface $input, OutputInterface $output) { $output->write('execute called'); diff --git a/tests/unit/Symfony/Components/Console/Command/CommandTest.php b/tests/unit/Symfony/Components/Console/Command/CommandTest.php index e3c9215775..b3f734b46f 100644 --- a/tests/unit/Symfony/Components/Console/Command/CommandTest.php +++ b/tests/unit/Symfony/Components/Console/Command/CommandTest.php @@ -50,8 +50,8 @@ $command = new TestCommand(); $command->setApplication($application); $t->is($command->getApplication(), $application, '->setApplication() sets the current application'); -// ->setDefinition() -$t->diag('->setDefinition()'); +// ->setDefinition() ->getDefinition() +$t->diag('->setDefinition() ->getDefinition()'); $ret = $command->setDefinition($definition = new InputDefinition()); $t->is($ret, $command, '->setDefinition() implements a fluent interface'); $t->is($command->getDefinition(), $definition, '->setDefinition() sets the current InputDefinition instance'); From 7074036924069927de1a09870e973209ffe9095f Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Thu, 14 Jan 2010 10:56:23 +0100 Subject: [PATCH 3/3] [Console] fixed typo --- src/Symfony/Components/Console/Shell.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Components/Console/Shell.php b/src/Symfony/Components/Console/Shell.php index 08e0152ffc..a57ce6453f 100644 --- a/src/Symfony/Components/Console/Shell.php +++ b/src/Symfony/Components/Console/Shell.php @@ -139,7 +139,7 @@ Welcome to the {$this->application->getName()} shell. At the prompt, type help for some help, or list to get a list available commands. -To exist the shell, type ^D. +To exit the shell, type ^D. EOF; }