- <?php
- namespace Application\CommonBundle\Command;
- use Symfony\Component\Console\Input\InputInterface;
- use Symfony\Component\Console\Output\OutputInterface;
- use Symfony\Component\Console\Input\InputOption;
- use Symfony\Component\Console\Input\ArrayInput;
- use Symfony\Component\Console\Output\NullOutput;
- use Assetic\Asset\AssetInterface;
- /**
- * Command to setup application assets in one step
- *
- */
- class SetupAssetsCommand extends Command
- {
- private $_asseticManager;
- private $_webDir;
- protected function configure()
- {
- parent::configure();
- $this->setName('tracpoint:assets')
- ->setDescription('Setup system assets & assetic')
- ->addOption('warm', null, InputOption::VALUE_NONE, 'Warm up the cache before doing any operations')
- ->addOption('watch', null, InputOption::VALUE_NONE, 'Check for changes every second, debug mode only')
- ->addOption('force', null, InputOption::VALUE_NONE, 'Force an initial generation of all assets (used with --watch)')
- ->addOption('period', null, InputOption::VALUE_REQUIRED, 'Set the polling period in seconds (used with --watch)', 1)
- ->setHelp(<<<EOT
- The only reason for this command is a current limitation of the assetic issue described here:
- https://github.com/kriswallsmith/assetic/issues/53
- Because of this assetic is reading the files not directly from the Resources directory in each
- bundle, but from the installed assets. This forces us to install the assets everytime before
- telling assetic to update the compiled assets.
- The <info>tracpoint:assets</info> command prepares all public assets to be ready to be served.
- It fist installs all the assets from the bundles directories and then compiles the corresponding assetic files.
- When the --watch option is used, the command will monitor the original resource files and when it detects
- a change it will install its asset and then compile them with assetic.
- EOT
- );
- }
- protected function initialize(InputInterface $input, OutputInterface $output)
- {
- parent::initialize($input, $output);
- $this->_webDir = $webDir = realpath(__DIR__ . '/../../../../web');
- $this->_asseticManager = $this->getContainer()->get('assetic.asset_manager');
- }
- protected function execute(InputInterface $input, OutputInterface $output)
- {
- $env = $this->getContainer()->getParameter('kernel.environment');
- $output->write("Running in Application Environment: $env", true);
- $output->write('',true);
- // We need to clear the cache because assetic bundle metadadata is there
- if ($input->getOption('warm')) {
- $output->write('-> Warming up the cache to refresh assetic metadata...');
- $tmpInput = new ArrayInput(array('cache:warmup'));
- $this->getApplication()->doRun($tmpInput, new NullOutput);
- $output->write('OK.',true);
- }
- if ($input->getOption('watch')) {
- $output->write('-> Watcher booting', true);
- $this->_watch($input, $output);
- return 0;
- }
- $webDir = $this->_webDir;
- $output->write("-> Installing assets to '$webDir' ...", true);
- $tmpInput = new ArrayInput(array('assets:install','target'=>$webDir));
- $this->getApplication()->doRun($tmpInput, $output);
- $output->write('-> Dumping assetic bundle files...', true);
- $tmpInput = new ArrayInput(array('assetic:dump'));
- $this->getApplication()->doRun($tmpInput, $output);
- $output->write('',true);
- $output->write('Ready.', true);
- }
- private function _watch(InputInterface $input, OutputInterface $output)
- {
- $cacheFileName = sys_get_temp_dir() . 'tracpoint_assets_watch.cache';
- //If true all assets will be regenerated on the first cycle
- $forceRefreshFirstTime = false;
- // Array with cached files modification time
- $timestamps = array();
- // Try to load cache
- if (!$input->getOption('force') && file_exists($cacheFileName)) {
- $output->write(" Timestamps cached. Loading '$cacheFileName' ...");
- $timestamps = @unserialize(file_get_contents($cacheFileName));
- if (!$timestamps) {
- $output->write("Error - resetting cache.", true);
- $timestamps = array();
- }else {
- $output->write("Loaded.", true);
- }
- }
- if (!count($timestamps)) {
- $output->write("New timestamp cache: Saving cache of files modified time to '$cacheFileName'", true);
- $output->write('', true);
- $timestamps = array();
- $forceRefreshFirstTime = true;
- }
- if ($forceRefreshFirstTime) {
- $output->write("Processing all assets for first time: ", true);
- }
- else {
- $output->write('Now waiting for changes...',true);
- }
- $error = '';
- while (true) {
- try {
- foreach ($this->getContainer()->get('kernel')->getBundles() as $bundle) {
- $resourcePath = $this->_getBundleResourcesPath($bundle);
- if (!is_dir($resourcePath)) {
- continue;
- }
- $finder = new \Symfony\Component\Finder\Finder();
- $finder->files()->in($resourcePath);
- foreach($finder as $file) {
- if (!$forceRefreshFirstTime && !$this->_assetShouldBeUpdated($file, $timestamps)) {
- continue;
- }
- if ($forceRefreshFirstTime) {
- $output->write('.');
- }
- else {
- $output->write('',true);
- $output->write(" -> Change detected on {$file->getRealPath()}", true);
- $output->write(" * Updating asset", true);
- }
- $installedAssetPath = $this->_installAsset($bundle, $file, $timestamps, $output);
- if (!$forceRefreshFirstTime) {
- $output->write(" * Updating corresponding assetic files...");
- }
- $assetDestinationRelativePath = ltrim(str_replace($this->_webDir, '', $installedAssetPath),'\\/');
- if (!$this->_installAsseticForMatchingAsset($assetDestinationRelativePath)) {
- $asseticCompiled = false;
- }
- else {
- $asseticCompiled = true;
- }
- if (!$asseticCompiled && !$forceRefreshFirstTime) {
- $output->write('',true);
- $output->write(" *** WARNING *** No assetic data found for this asset, is it new? - recreating the cache may help", true);
- }
- elseif (!$forceRefreshFirstTime) {
- $output->write('OK.',true);
- }
- @file_put_contents($cacheFileName, serialize($timestamps));
- }
- }
- if ($forceRefreshFirstTime) {
- $output->write('Finished!',true);
- $output->write('Now waiting for changes...',true);
- $forceRefreshFirstTime = false;
- }
- sleep($input->getOption('period'));
- } catch (\Exception $e) {
- if ($error != $msg = $e->getMessage()) {
- $output->writeln('<error>[error]</error> ' . $msg);
- $error = $msg;
- }
- }
- }
- }
- private function _assetShouldBeUpdated($asset, array &$cachedTimestamps)
- {
- $assetHash = $this->_getAssetHash($asset);
- if (isset($cachedTimestamps[$assetHash])) {
- $cacheTime = $cachedTimestamps[$assetHash];
- }
- else {
- $cacheTime = false;
- }
- $mtime = filemtime($this->_getAssetFullPath($asset));
- if (!$cacheTime || $cacheTime < $mtime) {
- return true;
- }
- return false;
- }
- private function _installAsset($bundle, $asset, array &$cachedTimestamps, OutputInterface $output)
- {
- $assetFullPath = $this->_getAssetFullPath($asset);
- // Bundle resources directory
- $bundleResourcesPath = $this->_getBundleResourcesPath($bundle);
- // Get the asset relative path form bundle resources directory
- $assetRelativePath = str_replace($bundleResourcesPath . '/' , '', $assetFullPath);
- $bundleName = preg_replace('/bundle$/', '', strtolower($bundle->getName()));
- $assetDestinationRelativePath = 'bundles/' . $bundleName . '/' . $assetRelativePath;
- $destination = rtrim($this->_webDir,'\\/') . '/' . $assetDestinationRelativePath;
- $filesystem = $this->getContainer()->get('filesystem');
- $filesystem->mkdir(dirname($assetDestinationRelativePath), 0777);
- if (!@copy($assetFullPath, $destination)) {
- throw new \Exception("Error copying $assetFullPath to $destination");
- }
- // Update cache
- $assetHash = $this->_getAssetHash($asset);
- $cachedTimestamps[$assetHash] = filemtime($assetFullPath);
- return $destination;
- }
- private function _installAsseticForMatchingAsset($assetDestinationRelativePath)
- {
- foreach ($this->_asseticManager->getNames() as $name) {
- $assets = $this->_asseticManager->get($name)->all();
- foreach($assets as $asset) {
- if ($asset->getSourcePath() == $assetDestinationRelativePath) {
- $this->_compileAssetic($name);
- return true;
- }
- }
- }
- return false;
- }
- /**
- * Writes an assetic asset.
- *
- * If the application or asset is in debug mode, each leaf asset will be
- * dumped as well.
- *
- * @param string $name An asset name
- * @param OutputInterface $output The command output
- */
- private function _compileAssetic($name)
- {
- $asset = $this->_asseticManager->get($name);
- $formula = $this->_asseticManager->getFormula($name);
- // start by dumping the main asset
- $this->_doCompileAssetic($asset);
- // dump each leaf if debug
- if (isset($formula[2]['debug']) ? $formula[2]['debug'] : $this->_asseticManager->isDebug()) {
- foreach ($asset as $leaf) {
- $this->_doCompileAssetic($leaf);
- }
- }
- }
- /**
- * Performs the assetic asset dump.
- *
- * @param AssetInterface $asset An asset
- * @param OutputInterface $output The command output
- *
- * @throws RuntimeException If there is a problem writing the asset
- */
- private function _doCompileAssetic(AssetInterface $asset)
- {
- $asseticBasePath = $this->getContainer()->getParameter('assetic.write_to');
- $target = rtrim($asseticBasePath, '/').'/'.str_replace('_controller/', '', $asset->getTargetPath());
- if (!is_dir($dir = dirname($target))) {
- if (false === @mkdir($dir, 0777, true)) {
- throw new \RuntimeException('Unable to create directory '.$dir);
- }
- }
- if (false === @file_put_contents($target, $asset->dump())) {
- throw new \RuntimeException('Unable to write file '.$target);
- }
- }
- private function _getAssetFullPath($asset)
- {
- return str_replace(DIRECTORY_SEPARATOR,'/',$asset->getRealPath());
- }
- private function _getAssetHash($asset)
- {
- return sha1($this->_getAssetFullPath($asset));
- }
- private function _getBundleResourcesPath($bundle)
- {
- return str_replace(DIRECTORY_SEPARATOR,'/',$bundle->getPath()) . '/Resources/public';
- }
- }