Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/develop' into bugfixes
Browse files Browse the repository at this point in the history
  • Loading branch information
olysenko committed May 15, 2017
2 parents a741f8a + 1eb9d76 commit b3e7eeb
Show file tree
Hide file tree
Showing 19 changed files with 412 additions and 164 deletions.
158 changes: 143 additions & 15 deletions app/code/Magento/Config/App/Config/Type/System.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/

namespace Magento\Config\App\Config\Type;

use Magento\Framework\App\Config\ConfigTypeInterface;
Expand All @@ -23,7 +24,7 @@ class System implements ConfigTypeInterface
private $source;

/**
* @var DataObject[]
* @var DataObject
*/
private $data;

Expand Down Expand Up @@ -64,6 +65,16 @@ class System implements ConfigTypeInterface
*/
private $configType;

/**
* Key name for flag which displays whether configuration is cached or not.
*
* Once configuration is cached additional flag pushed to cache storage
* to be able check cache existence without data load.
*
* @var string
*/
private $cacheExistenceKey;

/**
* @param \Magento\Framework\App\Config\ConfigSourceInterface $source
* @param \Magento\Framework\App\Config\Spi\PostProcessorInterface $postProcessor
Expand Down Expand Up @@ -92,6 +103,7 @@ public function __construct(
$this->fallback = $fallback;
$this->serializer = $serializer;
$this->configType = $configType;
$this->cacheExistenceKey = $this->configType . '_CACHE_EXISTS';
}

/**
Expand All @@ -102,23 +114,135 @@ public function get($path = '')
if ($path === null) {
$path = '';
}
if (!$this->data) {
$data = $this->cache->load($this->configType);
if (!$data) {
$data = $this->preProcessor->process($this->source->get());
$this->data = new DataObject($data);
$data = $this->fallback->process($data);
$this->data = new DataObject($data);
//Placeholder processing need system config - so we need to save intermediate result
$data = $this->postProcessor->process($data);
$this->data = new DataObject($data);
if ($this->isConfigRead($path)) {
return $this->data->getData($path);
}

if (!empty($path) && $this->isCacheExists()) {
return $this->readFromCache($path);
}

$config = $this->loadConfig();
$this->cacheConfig($config);
$this->data = new DataObject($config);
return $this->data->getData($path);
}

/**
* Check whether configuration is cached
*
* In case configuration cache exists method 'load' returns
* value equal to $this->cacheExistenceKey
*
* @return bool
*/
private function isCacheExists()
{
return $this->cache->load($this->cacheExistenceKey) !== false;
}

/**
* Explode path by '/'(forward slash) separator
*
* In case $path string contains forward slash symbol(/) the $path is exploded and parts array is returned
* In other case empty array is returned
*
* @param string $path
* @return array
*/
private function getPathParts($path)
{
$pathParts = [];
if (strpos($path, '/') !== false) {
$pathParts = explode('/', $path);
}
return $pathParts;
}

/**
* Check whether requested configuration data is read to memory
*
* Because of configuration is cached partially each part can be loaded separately
* Method performs check if corresponding system configuration part is already loaded to memory
* and value can be retrieved directly without cache look up
*
*
* @param string $path
* @return bool
*/
private function isConfigRead($path)
{
$pathParts = $this->getPathParts($path);
return !empty($pathParts) && isset($this->data[$pathParts[0]][$pathParts[1]]);
}

/**
* Load configuration from all the sources
*
* System configuration is loaded in 3 steps performing consecutive calls to
* Pre Processor, Fallback Processor, Post Processor accordingly
*
* @return array
*/
private function loadConfig()
{
$data = $this->preProcessor->process($this->source->get());
$this->data = new DataObject($data);
$data = $this->fallback->process($data);
$this->data = new DataObject($data);

return $this->postProcessor->process($data);
}

/**
*
* Load configuration and caching it by parts.
*
* To be cached configuration is loaded first.
* Then it is cached by parts to minimize memory usage on load.
* Additional flag cached as well to give possibility check cache existence without data load.
*
* @param array $data
* @return void
*/
private function cacheConfig($data)
{
foreach ($data as $scope => $scopeData) {
foreach ($scopeData as $key => $config) {
$this->cache->save(
$this->serializer->serialize($this->data->getData()),
$this->configType,
$this->serializer->serialize($config),
$this->configType . '_' . $scope . $key,
[self::CACHE_TAG]
);
} else {
$this->data = new DataObject($this->serializer->unserialize($data));
}
}
$this->cache->save($this->cacheExistenceKey, $this->cacheExistenceKey, [self::CACHE_TAG]);
}

/**
* Read cached configuration
*
* Read section of system configuration corresponding to requested $path from cache
* Configuration stored to internal property right after load to prevent additional
* requests to cache storage
*
* @param string $path
* @return mixed
*/
private function readFromCache($path)
{
if ($this->data === null) {
$this->data = new DataObject();
}

$result = null;
$pathParts = $this->getPathParts($path);
if (!empty($pathParts)) {
$result = $this->cache->load($this->configType . '_' . $pathParts[0] . $pathParts[1]);
if ($result !== false) {
$readData = $this->data->getData();
$readData[$pathParts[0]][$pathParts[1]] = $this->serializer->unserialize($result);
$this->data->setData($readData);
}
}

Expand All @@ -128,6 +252,10 @@ public function get($path = '')
/**
* Clean cache and global variables cache
*
* Next items cleared:
* - Internal property intended to store already loaded configuration data
* - All records in cache storage tagged with CACHE_TAG
*
* @return void
*/
public function clean()
Expand Down
119 changes: 64 additions & 55 deletions app/code/Magento/Config/Test/Unit/App/Config/Type/SystemTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/

namespace Magento\Config\Test\Unit\App\Config\Type;

use Magento\Config\App\Config\Type\System;
Expand Down Expand Up @@ -68,8 +69,8 @@ public function setUp()
$this->preProcessor = $this->getMockBuilder(PreProcessorInterface::class)
->getMockForAbstractClass();
$this->serializer = $this->getMockBuilder(SerializerInterface::class)
->disableOriginalConstructor()
->getMock();

$this->configType = new System(
$this->source,
$this->postProcessor,
Expand All @@ -80,15 +81,31 @@ public function setUp()
);
}

/**
* @param bool $isCached
* @dataProvider getDataProvider
*/
public function testGet($isCached)
public function testGetCached()
{
$path = 'default/dev/unsecure/url';
$url = 'http://magento.test/';
$data = [
'unsecure' => [
'url' => $url
]
];

$this->cache->expects($this->any())
->method('load')
->withConsecutive(['system_CACHE_EXISTS'], ['system_defaultdev'])
->willReturnOnConsecutiveCalls('1', serialize($data));
$this->serializer->expects($this->once())
->method('unserialize')
->willReturn($data);
$this->assertEquals($url, $this->configType->get($path));
}

public function testGetNotCached()
{
$path = 'default/dev/unsecure/url';
$url = 'http://magento.test/';
$unserializedData = [
$data = [
'default' => [
'dev' => [
'unsecure' => [
Expand All @@ -97,58 +114,50 @@ public function testGet($isCached)
]
]
];
$serializedDataString = '{"serialized-data"}';

$this->cache->expects($this->once())
$dataToCache = [
'unsecure' => [
'url' => $url
]
];
$this->cache->expects($this->any())
->method('load')
->with(System::CONFIG_TYPE)
->willReturn($isCached ? $unserializedData : null);

if ($isCached) {
$this->serializer->expects($this->once())
->method('unserialize')
->willReturn($unserializedData);
}

if (!$isCached) {
$this->serializer->expects($this->once())
->method('serialize')
->willReturn($serializedDataString);
$this->source->expects($this->once())
->method('get')
->willReturn($unserializedData);
$this->fallback->expects($this->once())
->method('process')
->with($unserializedData)
->willReturnArgument(0);
$this->preProcessor->expects($this->once())
->method('process')
->with($unserializedData)
->willReturnArgument(0);
$this->postProcessor->expects($this->once())
->method('process')
->with($unserializedData)
->willReturnArgument(0);
$this->cache->expects($this->once())
->method('save')
->with(
$serializedDataString,
System::CONFIG_TYPE,
->withConsecutive(['system_CACHE_EXISTS'], ['system_defaultdev'])
->willReturnOnConsecutiveCalls(false, false);

$this->serializer->expects($this->once())
->method('serialize')
->willReturn(serialize($dataToCache));
$this->source->expects($this->once())
->method('get')
->willReturn($data);
$this->fallback->expects($this->once())
->method('process')
->with($data)
->willReturnArgument(0);
$this->preProcessor->expects($this->once())
->method('process')
->with($data)
->willReturnArgument(0);
$this->postProcessor->expects($this->once())
->method('process')
->with($data)
->willReturnArgument(0);
$this->cache->expects($this->any())
->method('save')
->withConsecutive(
[
serialize($dataToCache),
'system_defaultdev',
[System::CACHE_TAG]
],
[
'system_CACHE_EXISTS',
'system_CACHE_EXISTS',
[System::CACHE_TAG]
);
}
]
);

$this->assertEquals($url, $this->configType->get($path));
}

/**
* @return array
*/
public function getDataProvider()
{
return [
[true],
[false]
];
}
}
14 changes: 13 additions & 1 deletion app/code/Magento/Deploy/Console/DeployStaticOptions.php
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@ class DeployStaticOptions
*/
const LANGUAGES_ARGUMENT = 'languages';

/**
* Static content version
*/
const CONTENT_VERSION = 'content-version';

/**
* Deploy static command options list
*
Expand Down Expand Up @@ -211,7 +216,14 @@ private function getBasicOptions()
null,
InputOption::VALUE_NONE,
'Create symlinks for the files of those locales, which are passed for deployment, '
. 'but have no customizations'
. 'but have no customizations.'
),
new InputOption(
self::CONTENT_VERSION,
null,
InputArgument::OPTIONAL,
'Custom version of static content can be used if running deployment on multiple nodes '
. 'to ensure that static content version is identical and caching works properly.'
),
new InputArgument(
self::LANGUAGES_ARGUMENT,
Expand Down
4 changes: 3 additions & 1 deletion app/code/Magento/Deploy/Service/DeployStaticContent.php
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,9 @@ public function deploy(array $options)
]
);

$version = (new \DateTime())->getTimestamp();
$version = !empty($options[Options::CONTENT_VERSION]) && is_string($options[Options::CONTENT_VERSION])
? $options[Options::CONTENT_VERSION]
: (new \DateTime())->getTimestamp();
$this->versionStorage->save($version);

$packages = $deployStrategy->deploy($options);
Expand Down
Loading

0 comments on commit b3e7eeb

Please sign in to comment.