Skip to content

Instantly share code, notes, and snippets.

@tPl0ch
Created December 5, 2013 01:46
Show Gist options
  • Save tPl0ch/7798862 to your computer and use it in GitHub Desktop.
Save tPl0ch/7798862 to your computer and use it in GitHub Desktop.
Symfony2 ContainerAwareCommand to transfer file sessions to a memcached interface.
<?php
namespace Application\Bundle\DefaultBundle\Command;
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Finder\Finder;
use Symfony\Component\HttpFoundation\Session\Storage\Handler\MemcachedSessionHandler;
use Symfony\Component\Stopwatch\Stopwatch;
/**
* Class MoveFileSessionsToMemcachedCommand
*/
class MoveFileSessionsToMemcachedCommand extends ContainerAwareCommand
{
/**
* @var \Symfony\Component\HttpFoundation\Session\Storage\Handler\MemcachedSessionHandler
*/
protected $memcachedService;
/**
* @var \Symfony\Component\Finder\Finder
*/
protected $finder;
/**
* @var string
*/
protected $sessionDirectory;
/**
* @var string
*/
protected $prefix;
/**
* @var \Symfony\Component\Stopwatch\Stopwatch
*/
protected $stopwatch;
/**
* {@inheritDoc}
*/
public function configure()
{
$this->setName('sessions:transfer:memcached');
$this->addArgument(
'directory',
InputArgument::REQUIRED,
'The session directory to read the file sessions from.'
);
$this->addOption(
'handler',
'hid',
InputOption::VALUE_REQUIRED,
'The session handler service id for the memcached session handler.',
'session.handler.memcached'
);
$this->addOption(
'prefix',
'pr',
InputOption::VALUE_REQUIRED,
'The prefix of the current file sessions.',
'sess_'
);
$this->addOption(
'only-modified-after',
'after',
InputOption::VALUE_REQUIRED,
'The `strtotime` date format after which the sessions should be transferred. Compares `modified` time!'
);
}
/**
* {@inheritDoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$successful = 0;
$step = 1;
/** @var \Symfony\Component\Console\Helper\ProgressHelper $progress */
$progress = $this->getHelperSet()->get('progress');
$invalidFiles = array();
$errorFiles = array();
if ($this->isVerbose($output) || $this->isVeryVerbose($output)) {
$output->writeln('<comment>Starting processing!</comment>');
$output->writeln('');
}
$this->readFiles($input);
$progress->start($output, $this->finder->count());
/** @var \Symfony\Component\Finder\SplFileInfo $fileInfo */
foreach ($this->finder as $fileInfo) {
$fileName = $fileInfo->getFilename();
$realPath = $fileInfo->getRealPath();
// if prefix doesn't match, skip file
if (!$this->hasPrefix($fileName)) {
$invalidFiles[] = $realPath;
$step++;
continue;
}
// Make this check again to make concurrency with garbage collected session files less probable
if (!file_exists($realPath) || !$fileInfo->isReadable()) {
$invalidFiles[] = $realPath;
$step++;
continue;
}
$writeSuccessful = $this->memcachedService->write(
str_replace($this->prefix, '', $fileName),
$fileInfo->getContents()
);
if (!$writeSuccessful) {
$errorFiles[] = $realPath;
$step++;
continue;
}
$progress->advance($step);
$step = 1;
$successful++;
}
$progress->finish();
$stopEvent = $this->stopwatch->stop('sessionMove');
if ($this->isVerbose($output) || $this->isVeryVerbose($output)) {
$output->writeln('');
$output->writeln(sprintf("Files processed: %d", $successful));
$output->writeln(sprintf("Files with errors: %d", count($errorFiles)));
$output->writeln(sprintf("Invalid files: %d", count($invalidFiles)));
$output->writeln(sprintf("Memory usage: %d kiB", $stopEvent->getMemory() / 1024));
$output->writeln(sprintf("Running time: %d ms", $stopEvent->getDuration()));
$output->writeln('<info>Command complete!</info>');
}
}
/**
* @param InputInterface $input
*
* @return \Symfony\Component\Finder\SplFileInfo[]
*/
protected function readFiles(InputInterface $input)
{
$this->finder
->in($this->sessionDirectory)
->files();
$after = $input->getOption('only-modified-after');
if (null !== $after) {
$this->finder->date(sprintf("after %s", $after));
}
return $this->finder;
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @throws \InvalidArgumentException
*/
protected function initialize(InputInterface $input, OutputInterface $output)
{
$input->validate();
if ($this->isVerbose($output) || $this->isVeryVerbose($output)) {
$output->writeln('<info>Initializing...</info>');
}
$this->stopwatch = new Stopwatch();
$this->stopwatch->start('sessionMove');
$this->initializeFinder($input, $output);
$this->initializeMemcached($input, $output);
$this->prefix = $input->getOption('prefix');
if ($this->isVeryVerbose($output) || $this->isVeryVerbose($output)) {
$output->writeln('<info>Initialization complete!</info>');
$output->writeln('');
}
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @throws \InvalidArgumentException
*/
protected function initializeFinder(InputInterface $input, OutputInterface $output)
{
$fileSystem = new Filesystem();
$this->sessionDirectory = $input->getArgument('directory');
if (!$fileSystem->exists($this->sessionDirectory)) {
throw new \InvalidArgumentException(
sprintf(
"The session directory '%s' does not exist or is not readable",
$this->sessionDirectory
)
);
}
unset($fileSystem);
$this->finder = new Finder();
if ($this->isVeryVerbose($output)) {
$output->writeln('Initialized session directory finder!');
}
}
/**
* @param InputInterface $input
* @param OutputInterface $output
*
* @throws \InvalidArgumentException
*/
protected function initializeMemcached(InputInterface $input, OutputInterface $output)
{
$memcachedHandlerId = $input->getOption('handler');
if (!$this->getContainer()->has($memcachedHandlerId)) {
throw new \InvalidArgumentException(
sprintf("The handler service '%s' is not registered with the container", $this->sessionDirectory)
);
}
$this->memcachedService = $this->getContainer()->get($memcachedHandlerId);
if (!$this->memcachedService instanceof MemcachedSessionHandler) {
throw new \InvalidArgumentException(
sprintf(
"The handler service with id '%s' is not an instance of %s",
$memcachedHandlerId,
'Symfony\Component\HttpFoundation\Session\Storage\Handler\MemcachedSessionHandler'
)
);
}
if ($this->isVeryVerbose($output)) {
$output->writeln('Initialized Memcached handler!');
}
}
/**
* @param OutputInterface $output
*
* @return bool
*/
protected function isVerbose(OutputInterface $output)
{
return $output->getVerbosity() === OutputInterface::VERBOSITY_VERBOSE;
}
/**
* @param OutputInterface $output
*
* @return bool
*/
protected function isVeryVerbose(OutputInterface $output)
{
return $output->getVerbosity() === OutputInterface::VERBOSITY_VERY_VERBOSE;
}
/**
* @param string $fileName
*
* @return bool
*/
protected function hasPrefix($fileName)
{
return substr($fileName, 0, strlen($this->prefix)) === $this->prefix;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment