├── LICENSE ├── README.md ├── composer.json └── src ├── Console └── GenerateProxiesCommand.php └── DI └── ProxyExtension.php /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Lukáš Unger 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Lookyman/Nette/Proxy 2 | ==================== 3 | 4 | Integration of [Proxy Manager](https://ocramius.github.io/ProxyManager) into [Nette Framework](https://nette.org). 5 | 6 | [![Build Status](https://travis-ci.org/lookyman/nette-proxy.svg?branch=master)](https://travis-ci.org/lookyman/nette-proxy) 7 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/lookyman/nette-proxy/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/lookyman/nette-proxy/?branch=master) 8 | [![Coverage Status](https://coveralls.io/repos/github/lookyman/nette-proxy/badge.svg?branch=master)](https://coveralls.io/github/lookyman/nette-proxy?branch=master) 9 | [![Downloads](https://img.shields.io/packagist/dt/lookyman/nette-proxy.svg)](https://packagist.org/packages/lookyman/nette-proxy) 10 | [![Latest stable](https://img.shields.io/packagist/v/lookyman/nette-proxy.svg)](https://packagist.org/packages/lookyman/nette-proxy) 11 | 12 | 13 | Installation 14 | ------------ 15 | 16 | ### Install 17 | 18 | ```sh 19 | composer require lookyman/nette-proxy 20 | ``` 21 | 22 | ### Config 23 | 24 | ```yaml 25 | extensions: 26 | proxy: Lookyman\Nette\Proxy\DI\ProxyExtension 27 | 28 | proxy: 29 | proxyDir: %appDir%/../temp/proxies # this is the default value 30 | default: off # turn on to proxy everything 31 | ``` 32 | 33 | ### Usage 34 | 35 | Tag services with `lookyman.lazy` and they get magically proxied. 36 | 37 | ```yaml 38 | services: 39 | - 40 | class: MyHeavyService 41 | tags: [lookyman.lazy] 42 | ``` 43 | 44 | If you have `proxy.default` turned on and you don't want a particular service to be proxied, you can do it like this: 45 | 46 | ```yaml 47 | services: 48 | - 49 | class: DontProxyMeService 50 | tags: [lookyman.lazy: off] 51 | ``` 52 | 53 | Proxying certain Nette services is automaticaly disabled due to [known limitations](https://ocramius.github.io/ProxyManager/docs/lazy-loading-value-holder.html#known-limitations). 54 | 55 | ### Pre-generating proxies 56 | 57 | Proxy generation causes I/O operations and uses a lot of reflection, so it is handy to have them pre-generated before the application starts. For this, install [Kdyby/Console](https://github.com/kdyby/console) and run: 58 | 59 | ```sh 60 | php www/index.php lookyman:nette-proxy:generate 61 | ``` 62 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lookyman/nette-proxy", 3 | "description": "Integration of Proxy Manager into Nette Framework", 4 | "keywords": ["Nette", "proxy", "lazy"], 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Lukáš Unger", 9 | "email": "looky.msc@gmail.com", 10 | "homepage": "https://lookyman.net" 11 | } 12 | ], 13 | "require": { 14 | "ocramius/proxy-manager": "^2.0", 15 | "nette/di": "^2.4", 16 | "nette/php-generator": "^2.6|^3.0" 17 | }, 18 | "require-dev": { 19 | "phpunit/phpunit": "^5.6", 20 | "nette/bootstrap": "^2.4", 21 | "kdyby/console": "^2.6" 22 | }, 23 | "suggest": { 24 | "kdyby/console": "For pre-generating proxies via a command" 25 | }, 26 | "autoload": { 27 | "psr-4": { 28 | "Lookyman\\Nette\\Proxy\\": "src/" 29 | } 30 | }, 31 | "autoload-dev": { 32 | "psr-4": { 33 | "Lookyman\\Nette\\Proxy\\Tests\\": "tests/" 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Console/GenerateProxiesCommand.php: -------------------------------------------------------------------------------- 1 | setName('lookyman:nette-proxy:generate') 18 | ->setDescription('Generate proxies for lazy services') 19 | ->addOption('debug', null, InputOption::VALUE_NONE, 'Print debug info'); 20 | } 21 | 22 | protected function execute(InputInterface $input, OutputInterface $output) 23 | { 24 | $debug = $input->getOption('debug'); 25 | 26 | /** @var Container $container */ 27 | $container = $this->getHelper('container')->getContainer(); 28 | foreach (array_keys($container->findByTag(ProxyExtension::TAG_LAZY)) as $name) { 29 | $container->getService($name); 30 | if ($debug) { 31 | $output->writeln(sprintf('Proxy for service %s generated', $name)); 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/DI/ProxyExtension.php: -------------------------------------------------------------------------------- 1 | '%appDir%/../temp/proxies', 28 | 'default' => false, 29 | ]; 30 | 31 | /** 32 | * @var array 33 | */ 34 | private $excluded = [ 35 | 'Nette\ComponentModel\Component', 36 | 'Nette\Database\Connection', 37 | 'Nette\Http\Request', 38 | 'Nette\Security\User', 39 | ]; 40 | 41 | public function loadConfiguration() 42 | { 43 | $builder = $this->getContainerBuilder(); 44 | $config = $this->validateConfig($this->defaults); 45 | Validators::assertField($config, 'default', 'bool'); 46 | 47 | // create proxy dir 48 | Validators::assertField($config, 'proxyDir', 'string'); 49 | $config['proxyDir'] = Helpers::expand($config['proxyDir'], $builder->parameters); 50 | if (!@mkdir($config['proxyDir'], 0777, true) && !is_dir($config['proxyDir'])) { 51 | // @codeCoverageIgnoreStart 52 | throw new \RuntimeException(sprintf('Cannot create proxy directory %s', $config['proxyDir'])); 53 | // @codeCoverageIgnoreEnd 54 | } 55 | 56 | // generator strategy 57 | $builder->addDefinition($this->prefix('generatorStrategy')) 58 | ->setClass(FileWriterGeneratorStrategy::class, [new Statement(FileLocator::class, [$config['proxyDir']])]) 59 | ->setAutowired(false) 60 | ->addTag(self::TAG_LAZY, false); 61 | 62 | // configuration 63 | $builder->addDefinition($this->prefix('configuration')) 64 | ->setClass(Configuration::class) 65 | ->addSetup('setProxiesTargetDir', [$config['proxyDir']]) 66 | ->addSetup('setGeneratorStrategy', [$this->prefix('@generatorStrategy')]) 67 | ->setAutowired(false) 68 | ->addTag(self::TAG_LAZY, false); 69 | 70 | // proxy factory 71 | $builder->addDefinition($this->prefix('lazyLoadingValueHolderFactory')) 72 | ->setClass(LazyLoadingValueHolderFactory::class, [$this->prefix('@configuration')]) 73 | ->setAutowired(false) 74 | ->addTag(self::TAG_LAZY, false); 75 | 76 | // command 77 | /** @var \Kdyby\Console\DI\ConsoleExtension $extension */ 78 | foreach ($this->compiler->getExtensions('Kdyby\Console\DI\ConsoleExtension') as $extension) { 79 | $builder->addDefinition($this->prefix('generateProxiesCommand')) 80 | ->setClass(GenerateProxiesCommand::class) 81 | ->addTag($extension::TAG_COMMAND); 82 | break; 83 | } 84 | } 85 | 86 | public function beforeCompile() 87 | { 88 | $builder = $this->getContainerBuilder(); 89 | $config = $this->getConfig(); 90 | 91 | // do not proxy these services 92 | // @see https://ocramius.github.io/ProxyManager/docs/lazy-loading-value-holder.html#known-limitations 93 | foreach ($this->excluded as $type) { 94 | foreach ($builder->findByType($type) as $def) { 95 | $def->addTag(self::TAG_LAZY, false); 96 | } 97 | } 98 | 99 | // add service type as tag attribute 100 | foreach (array_keys($config['default'] ? $builder->getDefinitions() : $builder->findByTag(self::TAG_LAZY)) as $name) { 101 | $def = $builder->getDefinition($name); 102 | if ($def->getTag(self::TAG_LAZY) === false) { 103 | $def->setTags(array_diff_key($def->getTags(), [self::TAG_LAZY => null])); 104 | continue; 105 | } 106 | $def->addTag(self::TAG_LAZY, $def->getImplement() ?: $def->getClass()); 107 | } 108 | } 109 | 110 | /** 111 | * @param ClassType $class 112 | */ 113 | public function afterCompile(ClassType $class) 114 | { 115 | foreach ($this->getContainerBuilder()->findByTag(self::TAG_LAZY) as $name => $type) { 116 | // modify original method body to return proxy instead 117 | $method = $class->getMethod(Container::getMethodName($name)); 118 | $method->setBody(sprintf( 119 | "return \$this->getService('%s')->createProxy(\n\t%s::class,\n\tfunction (&\$wrappedObject, \$proxy, \$method, \$parameters, &\$initializer) {\n\t\t\$wrappedObject = (%s)();\n\t\t\$initializer = null;\n\t\treturn true;\n\t}\n);", 120 | $this->prefix('lazyLoadingValueHolderFactory'), $type, ltrim(preg_replace('#^#m', "\t\t", (new Closure())->addBody($method->getBody()))) 121 | )); 122 | } 123 | 124 | // register proxy autoloader 125 | $init = $class->getMethod('initialize'); 126 | $body = $init->getBody(); 127 | $init->setBody("spl_autoload_register(\$this->getService(?)->getProxyAutoloader());\n", [$this->prefix('configuration')])->addBody($body); 128 | } 129 | } 130 | --------------------------------------------------------------------------------