├── tools ├── .gitignore ├── psalm │ └── composer.json └── infection │ └── composer.json ├── config ├── params.php └── di.php ├── composer-require-checker.json ├── rector.php ├── LICENSE.md ├── CHANGELOG.md ├── src ├── AliasReference.php └── Aliases.php ├── composer.json └── README.md /tools/.gitignore: -------------------------------------------------------------------------------- 1 | /*/vendor 2 | /*/composer.lock 3 | -------------------------------------------------------------------------------- /tools/psalm/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require-dev": { 3 | "vimeo/psalm": "^5.26.1 || ^6.13" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /config/params.php: -------------------------------------------------------------------------------- 1 | [ 7 | 'aliases' => [], 8 | ], 9 | ]; 10 | -------------------------------------------------------------------------------- /tools/infection/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require-dev": { 3 | "infection/infection": "^0.26 || ^0.31.9" 4 | }, 5 | "config": { 6 | "allow-plugins": { 7 | "infection/extension-installer": true 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /config/di.php: -------------------------------------------------------------------------------- 1 | [ 11 | '__construct()' => [$params['yiisoft/aliases']['aliases']], 12 | ], 13 | ]; 14 | -------------------------------------------------------------------------------- /composer-require-checker.json: -------------------------------------------------------------------------------- 1 | { 2 | "symbol-whitelist": [ 3 | "Psr\\Container\\ContainerInterface", 4 | "Yiisoft\\Definitions\\Contract\\ReferenceInterface" 5 | ], 6 | "php-core-extensions": [ 7 | "Core", 8 | "date", 9 | "json", 10 | "pcre", 11 | "Phar", 12 | "Reflection", 13 | "SPL", 14 | "standard", 15 | "fileinfo" 16 | ], 17 | "scan-files": [] 18 | } 19 | -------------------------------------------------------------------------------- /rector.php: -------------------------------------------------------------------------------- 1 | paths([ 12 | __DIR__ . '/src', 13 | __DIR__ . '/tests', 14 | ]); 15 | 16 | // register a single rule 17 | $rectorConfig->rule(InlineConstructorDefaultToPropertyRector::class); 18 | 19 | // define sets of rules 20 | $rectorConfig->sets([ 21 | LevelSetList::UP_TO_PHP_74, 22 | ]); 23 | 24 | $rectorConfig->skip([ 25 | ClosureToArrowFunctionRector::class, 26 | ]); 27 | }; 28 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright © 2008 by Yii Software () 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions 6 | are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | * Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in 12 | the documentation and/or other materials provided with the 13 | distribution. 14 | * Neither the name of Yii Software nor the names of its 15 | contributors may be used to endorse or promote products derived 16 | from this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 21 | FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 22 | COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 23 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 24 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 | LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 28 | ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Yii Aliases Change Log 2 | 3 | ## 3.1.2 under development 4 | 5 | - no changes in this release. 6 | 7 | ## 3.1.1 December 04, 2025 8 | 9 | - Enh #85: Add PHP 8.5 support (@vjik) 10 | 11 | ## 3.1.0 August 12, 2025 12 | 13 | - New #84: Add `AliasReference` (@vjik) 14 | - Chg #81, #83: Change PHP constraint in `composer.json` to `8.1 - 8.4` (@vjik) 15 | - Chg #83: Bump PHP minimal version to 8.1 (@vjik) 16 | 17 | ## 3.0.0 February 13, 2023 18 | 19 | - New #44: Add method `Aliases::getArray()` that bulk translates path aliases into actual paths (@vjik) 20 | - Chg #59: Adapt configuration group names to Yii conventions (@vjik) 21 | 22 | ## 2.0.0 April 13, 2021 23 | 24 | - Chg #40: Remove magic `__set()` that was leading to weird side-effects (@samdark) 25 | 26 | ## 1.1.4 April 13, 2021 27 | 28 | - Chg: Adjust config for yiisoft/factory changes (@vjik, @samdark) 29 | 30 | ## 1.1.3 March 23, 2021 31 | 32 | - Chg: Adjust config for new config plugin (@samdark) 33 | 34 | ## 1.1.2 December 24, 2020 35 | 36 | - Bug #35: Don't throw TypeError from method `getAll()`, when alias is nested (@Fantom409) 37 | 38 | ## 1.1.1 November 06, 2020 39 | 40 | - Bug #28: Add `params` to `config-plugin` section in `composer.json` (@vjik) 41 | 42 | ## 1.1.0 November 4, 2020 43 | 44 | - Chg #26: Change Yii configuration `$params['aliases']` to `$params['yiisoft/aliases']['aliases']` (@vjik) 45 | 46 | ## 1.0.3 October 6, 2020 47 | 48 | - Enh: Don't error on config assembly in case aliases aren't set (@samdark) 49 | 50 | ## 1.0.2 October 5, 2020 51 | 52 | - Enh: Add a config for composer-config-plugin (@xepozz) 53 | 54 | ## 1.0.1 August 21, 2020 55 | 56 | - Enh: Allow installing on PHP 8 (@samdark) 57 | 58 | ## 1.0.0 June 7, 2020 59 | 60 | - Initial release. 61 | -------------------------------------------------------------------------------- /src/AliasReference.php: -------------------------------------------------------------------------------- 1 | alias = $alias; 29 | } 30 | 31 | /** 32 | * Creates a new alias reference. 33 | * 34 | * @param string $id The alias to be resolved. 35 | * 36 | * @return self An instance of reference. 37 | * 38 | * @psalm-suppress MoreSpecificImplementedParamType, DocblockTypeContradiction 39 | */ 40 | public static function to(mixed $id): self 41 | { 42 | if (!is_string($id)) { 43 | throw new InvalidArgumentException('Alias must be a string.'); 44 | } 45 | return new self($id); 46 | } 47 | 48 | /** 49 | * Retrieves {@see Aliases} from the container and uses it to resolve the alias to its actual path. 50 | * 51 | * @param ContainerInterface $container The DI container. 52 | * 53 | * @return string The resolved path for the alias. 54 | */ 55 | public function resolve(ContainerInterface $container): string 56 | { 57 | /** @var Aliases $aliases */ 58 | $aliases = $container->get(Aliases::class); 59 | return $aliases->get($this->alias); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yiisoft/aliases", 3 | "type": "library", 4 | "description": "Named paths and URLs storage", 5 | "keywords": [ 6 | "alias" 7 | ], 8 | "homepage": "https://www.yiiframework.com/", 9 | "license": "BSD-3-Clause", 10 | "support": { 11 | "issues": "https://github.com/yiisoft/aliases/issues?state=open", 12 | "source": "https://github.com/yiisoft/aliases", 13 | "forum": "https://www.yiiframework.com/forum/", 14 | "wiki": "https://www.yiiframework.com/wiki/", 15 | "irc": "ircs://irc.libera.chat:6697/yii", 16 | "chat": "https://t.me/yii3en" 17 | }, 18 | "funding": [ 19 | { 20 | "type": "opencollective", 21 | "url": "https://opencollective.com/yiisoft" 22 | }, 23 | { 24 | "type": "github", 25 | "url": "https://github.com/sponsors/yiisoft" 26 | } 27 | ], 28 | "require": { 29 | "php": "8.1 - 8.5" 30 | }, 31 | "require-dev": { 32 | "bamarni/composer-bin-plugin": "^1.8.3", 33 | "maglnet/composer-require-checker": "^4.7.1", 34 | "phpunit/phpunit": "^10.5.48", 35 | "psr/container": "^2.0.2", 36 | "rector/rector": "^2.1.2", 37 | "spatie/phpunit-watcher": "^1.24.0", 38 | "yiisoft/definitions": "^3.4", 39 | "yiisoft/di": "^1.4", 40 | "yiisoft/test-support": "^3.0.2" 41 | }, 42 | "autoload": { 43 | "psr-4": { 44 | "Yiisoft\\Aliases\\": "src" 45 | } 46 | }, 47 | "autoload-dev": { 48 | "psr-4": { 49 | "Yiisoft\\Aliases\\Tests\\": "tests" 50 | } 51 | }, 52 | "config": { 53 | "sort-packages": true, 54 | "allow-plugins": { 55 | "bamarni/composer-bin-plugin": true, 56 | "composer/package-versions-deprecated": true 57 | } 58 | }, 59 | "extra": { 60 | "bamarni-bin": { 61 | "bin-links": true, 62 | "target-directory": "tools", 63 | "forward-command": true 64 | }, 65 | "config-plugin-options": { 66 | "source-directory": "config" 67 | }, 68 | "config-plugin": { 69 | "params": "params.php", 70 | "di": "di.php" 71 | } 72 | }, 73 | "scripts": { 74 | "test": "phpunit --testdox --no-interaction", 75 | "test-watch": "phpunit-watcher watch" 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Yii 4 | 5 |

Yii Aliases

6 |
7 |

8 | 9 | [![Latest Stable Version](https://poser.pugx.org/yiisoft/aliases/v)](https://packagist.org/packages/yiisoft/aliases) 10 | [![Total Downloads](https://poser.pugx.org/yiisoft/aliases/downloads)](https://packagist.org/packages/yiisoft/aliases) 11 | [![Build status](https://github.com/yiisoft/aliases/actions/workflows/build.yml/badge.svg)](https://github.com/yiisoft/aliases/actions/workflows/build.yml) 12 | [![Code Coverage](https://codecov.io/gh/yiisoft/aliases/branch/master/graph/badge.svg)](https://codecov.io/gh/yiisoft/aliases) 13 | [![Mutation testing badge](https://img.shields.io/endpoint?style=flat&url=https://badge-api.stryker-mutator.io/github.com/yiisoft/aliases/master)](https://dashboard.stryker-mutator.io/reports/github.com/yiisoft/aliases/master) 14 | [![Static analysis](https://github.com/yiisoft/aliases/actions/workflows/static.yml/badge.svg?branch=master)](https://github.com/yiisoft/aliases/actions/workflows/static.yml?query=branch%3Amaster) 15 | [![type-coverage](https://shepherd.dev/github/yiisoft/aliases/coverage.svg)](https://shepherd.dev/github/yiisoft/aliases) 16 | 17 | The package aim is to store path aliases, i.e. short name representing a long path (a file path, a URL, etc.). 18 | Path alias value may have another value as its part. For example, `@vendor` may store path to `vendor` directory 19 | while `@bin` may store `@vendor/bin`. 20 | 21 | ## Requirements 22 | 23 | - PHP 8.1 - 8.5. 24 | 25 | ## Installation 26 | 27 | The package could be installed with [Composer](https://getcomposer.org): 28 | 29 | ```shell 30 | composer require yiisoft/aliases 31 | ``` 32 | 33 | ## General usage 34 | 35 | A path alias must start with the character '@' so that it can be easily differentiated from non-alias paths. 36 | 37 | ```php 38 | use Yiisoft\Aliases\Aliases; 39 | 40 | $aliases = new Aliases([ 41 | '@root' => __DIR__, 42 | ]); 43 | $aliases->set('@vendor', '@root/vendor'); 44 | $aliases->set('@bin', '@vendor/bin'); 45 | 46 | echo $aliases->get('@bin/phpunit'); 47 | ``` 48 | 49 | The code about would output "/path/to/vendor/bin/phpunit". 50 | 51 | Note that `set()` method does not check if the given path exists or not. All it does is to associate the alias with 52 | the path. 53 | 54 | The path could be: 55 | 56 | - a directory or a file path (e.g. `/tmp`, `/tmp/main.txt`) 57 | - a URL (e.g. `https://www.yiiframework.com`) 58 | - a path alias (e.g. `@yii/base`). It will be resolved on {@see get()} call. 59 | 60 | Any trailing `/` and `\` characters in the given path will be trimmed. 61 | 62 | To bulk translate path aliases into actual paths use `getArray()` method: 63 | 64 | ```php 65 | $aliases = new Aliases([ 66 | '@root' => '/my/app', 67 | ]); 68 | 69 | // Value will be ['src' => '/my/app/src', 'tests' => '/my/app/tests'] 70 | $directories = $aliases->getAll(['src' => '@root/src', 'tests' => '@root/tests']); 71 | ``` 72 | 73 | ### Alias priorities 74 | 75 | In case multiple aliases are registered with same root (prefix), then the most specific has precedence: 76 | 77 | ```php 78 | use Yiisoft\Aliases\Aliases; 79 | 80 | $aliases = new Aliases([ 81 | '@vendor' => __DIR__ . '/vendor', 82 | '@vendor/test' => '/special/location' 83 | ]); 84 | echo $aliases->get('@vendor/test'); 85 | ``` 86 | 87 | That would output `/special/location` since `@vendor/test` is more specific match than `@vendor`. 88 | 89 | ### Alias removal 90 | 91 | If you need to remove alias runtime: 92 | 93 | ```php 94 | use Yiisoft\Aliases\Aliases; 95 | 96 | $aliases = new Aliases([ 97 | '@root' => __DIR__, 98 | ]); 99 | $aliases->remove('@root'); 100 | ``` 101 | 102 | ### Alias references 103 | 104 | The package provides `AliasReference` class that implements `ReferenceInterface` from 105 | [Yii Definition](https://github.com/yiisoft/definitions). It allows you to create references to aliases 106 | that will be resolved at runtime: 107 | 108 | ```php 109 | // Create a reference to an alias 110 | $reference = \Yiisoft\Aliases\AliasReference::to('@public/assets'); 111 | 112 | // The reference will be resolved when needed 113 | $configPath = $reference->resolve($container); 114 | ``` 115 | 116 | This is particularly useful in dependency injection configurations where you want to inject resolved paths 117 | but the aliases are not available at configuration time. 118 | 119 | ## Documentation 120 | 121 | - [Internals](docs/internals.md) 122 | 123 | If you need help or have a question, the [Yii Forum](https://forum.yiiframework.com/c/yii-3-0/63) is a good place for that. 124 | You may also check out other [Yii Community Resources](https://www.yiiframework.com/community). 125 | 126 | ## License 127 | 128 | The Yii Aliases is free software. It is released under the terms of the BSD License. 129 | Please see [`LICENSE`](./LICENSE.md) for more information. 130 | 131 | Maintained by [Yii Software](https://www.yiiframework.com/). 132 | 133 | ## Support the project 134 | 135 | [![Open Collective](https://img.shields.io/badge/Open%20Collective-sponsor-7eadf1?logo=open%20collective&logoColor=7eadf1&labelColor=555555)](https://opencollective.com/yiisoft) 136 | 137 | ## Follow updates 138 | 139 | [![Official website](https://img.shields.io/badge/Powered_by-Yii_Framework-green.svg?style=flat)](https://www.yiiframework.com/) 140 | [![Twitter](https://img.shields.io/badge/twitter-follow-1DA1F2?logo=twitter&logoColor=1DA1F2&labelColor=555555?style=flat)](https://twitter.com/yiiframework) 141 | [![Telegram](https://img.shields.io/badge/telegram-join-1DA1F2?style=flat&logo=telegram)](https://t.me/yii3en) 142 | [![Facebook](https://img.shields.io/badge/facebook-join-1DA1F2?style=flat&logo=facebook&logoColor=ffffff)](https://www.facebook.com/groups/yiitalk) 143 | [![Slack](https://img.shields.io/badge/slack-join-1DA1F2?style=flat&logo=slack)](https://yiiframework.com/go/slack) 144 | -------------------------------------------------------------------------------- /src/Aliases.php: -------------------------------------------------------------------------------- 1 | > 18 | */ 19 | private array $aliases = []; 20 | 21 | /** 22 | * @psalm-param array $config 23 | * 24 | * @throws InvalidArgumentException If `$path` is an invalid alias. 25 | * 26 | * @see set() 27 | * @see get() 28 | */ 29 | public function __construct(array $config = []) 30 | { 31 | foreach ($config as $alias => $path) { 32 | $this->set($alias, $path); 33 | } 34 | } 35 | 36 | /** 37 | * Registers a path alias. 38 | * 39 | * A path alias is a short name representing a long path (a file path, a URL, etc.) 40 | * 41 | * For example, `@vendor` may store path to `vendor` directory. 42 | * 43 | * A path alias must start with the character '@' so that it can be easily differentiated 44 | * from non-alias paths. 45 | * 46 | * Note that this method does not check if the given path exists or not. All it does is 47 | * to associate the alias with the path. 48 | * 49 | * Any trailing '/' and '\' characters in the given path will be trimmed. 50 | * 51 | * @param string $alias the alias name (e.g. "@vendor"). It must start with a '@' character. 52 | * It may contain the forward slash '/' which serves as boundary character when performing 53 | * alias translation by {@see get()}. 54 | * @param string $path the path corresponding to the alias. 55 | * Trailing '/' and '\' characters will be trimmed. This can be 56 | * 57 | * - a directory or a file path (e.g. `/tmp`, `/tmp/main.txt`) 58 | * - a URL (e.g. `https://www.yiiframework.com`) 59 | * - a path alias (e.g. `@vendor/yiisoft`). It will be resolved on {@see get()} call. 60 | * 61 | * @see get() 62 | */ 63 | public function set(string $alias, string $path): void 64 | { 65 | if (!$this->isAlias($alias)) { 66 | $alias = '@' . $alias; 67 | } 68 | $pos = strpos($alias, '/'); 69 | /** @psalm-var string $root */ 70 | $root = $pos === false ? $alias : substr($alias, 0, $pos); 71 | 72 | $path = rtrim($path, '\\/'); 73 | if (!array_key_exists($root, $this->aliases)) { 74 | if ($pos === false) { 75 | $this->aliases[$root] = $path; 76 | } else { 77 | $this->aliases[$root] = [$alias => $path]; 78 | } 79 | } elseif (is_string($this->aliases[$root])) { 80 | if ($pos === false) { 81 | $this->aliases[$root] = $path; 82 | } else { 83 | $this->aliases[$root] = [ 84 | $alias => $path, 85 | $root => $this->aliases[$root], 86 | ]; 87 | } 88 | } else { 89 | $this->aliases[$root][$alias] = $path; 90 | krsort($this->aliases[$root]); 91 | } 92 | } 93 | 94 | /** 95 | * Remove alias. 96 | * 97 | * @param string $alias Alias to be removed. 98 | */ 99 | public function remove(string $alias): void 100 | { 101 | if (!$this->isAlias($alias)) { 102 | $alias = '@' . $alias; 103 | } 104 | $pos = strpos($alias, '/'); 105 | $root = $pos === false ? $alias : substr($alias, 0, $pos); 106 | 107 | if (array_key_exists($root, $this->aliases)) { 108 | if (is_array($this->aliases[$root])) { 109 | unset($this->aliases[$root][$alias]); 110 | } elseif ($pos === false) { 111 | unset($this->aliases[$root]); 112 | } 113 | } 114 | } 115 | 116 | /** 117 | * Translates a path alias into an actual path. 118 | * 119 | * The translation is done according to the following procedure: 120 | * 121 | * 1. If the given alias does not start with '@', it is returned back without change; 122 | * 2. Otherwise, look for the longest registered alias that matches the beginning part 123 | * of the given alias. If it exists, replace the matching part of the given alias with 124 | * the corresponding registered path. 125 | * 3. Throw an exception if path alias cannot be resolved. 126 | * 127 | * For example, if '@vendor' is registered as the alias to the vendor directory, 128 | * say '/path/to/vendor'. The alias '@vendor/yiisoft' would then be translated into '/path/to/vendor/yiisoft'. 129 | * 130 | * If you have registered two aliases '@foo' and '@foo/bar'. Then translating '@foo/bar/config' 131 | * would replace the part '@foo/bar' (instead of '@foo') with the corresponding registered path. 132 | * This is because the longest alias takes precedence. 133 | * 134 | * However, if the alias to be translated is '@foo/barbar/config', then '@foo' will be replaced 135 | * instead of '@foo/bar', because '/' serves as the boundary character. 136 | * 137 | * Note, this method does not check if the returned path exists or not. 138 | * 139 | * @param string $alias The alias to be translated. 140 | * 141 | * @throws InvalidArgumentException If the root alias is not previously registered. 142 | * 143 | * @return string The path corresponding to the alias. 144 | * 145 | * @see setAlias() 146 | */ 147 | public function get(string $alias): string 148 | { 149 | if (!$this->isAlias($alias)) { 150 | return $alias; 151 | } 152 | 153 | $foundAlias = $this->findAlias($alias); 154 | 155 | if ($foundAlias === null) { 156 | throw new InvalidArgumentException("Invalid path alias: $alias"); 157 | } 158 | 159 | $foundSubAlias = $this->findAlias($foundAlias); 160 | if ($foundSubAlias === null) { 161 | return $foundAlias; 162 | } 163 | 164 | return $this->get($foundSubAlias); 165 | } 166 | 167 | /** 168 | * Bulk translates path aliases into actual paths. 169 | * 170 | * @param string[] $aliases Aliases to be translated. 171 | * 172 | * @throws InvalidArgumentException If the root alias was not previously registered. 173 | * 174 | * @return string[] The paths corresponding to the aliases. 175 | */ 176 | public function getArray(array $aliases): array 177 | { 178 | return array_map( 179 | fn (string $alias) => $this->get($alias), 180 | $aliases, 181 | ); 182 | } 183 | 184 | /** 185 | * Returns all path aliases translated into an actual paths. 186 | * 187 | * @return array Actual paths indexed by alias name. 188 | */ 189 | public function getAll(): array 190 | { 191 | $result = []; 192 | foreach ($this->aliases as $name => $path) { 193 | if (is_array($path)) { 194 | foreach ($path as $innerName => $innerPath) { 195 | $result[$innerName] = $innerPath; 196 | } 197 | } else { 198 | $result[$name] = $this->get($path); 199 | } 200 | } 201 | 202 | return $result; 203 | } 204 | 205 | private function findAlias(string $alias): ?string 206 | { 207 | $pos = strpos($alias, '/'); 208 | $root = $pos === false ? $alias : substr($alias, 0, $pos); 209 | 210 | if (array_key_exists($root, $this->aliases)) { 211 | if (is_string($this->aliases[$root])) { 212 | return $pos === false ? $this->aliases[$root] : $this->aliases[$root] . substr($alias, $pos); 213 | } 214 | 215 | foreach ($this->aliases[$root] as $name => $path) { 216 | if (strpos($alias . '/', $name . '/') === 0) { 217 | return $path . substr($alias, strlen($name)); 218 | } 219 | } 220 | } 221 | 222 | return null; 223 | } 224 | 225 | private function isAlias(string $alias): bool 226 | { 227 | return !strncmp($alias, '@', 1); 228 | } 229 | } 230 | --------------------------------------------------------------------------------