Advertisement
Guest User

Untitled

a guest
Jun 23rd, 2017
88
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 12.96 KB | None | 0 0
  1. <?php
  2.  
  3. class sfDatabaseBackup extends sfBaseTask
  4. {
  5.     protected $workingDirectory;
  6.     protected $pathToExecutable;
  7.    
  8.     protected function configure()
  9.     {
  10.         $this->addOptions(array(
  11.         new sfCommandOption('application', null, sfCommandOption::PARAMETER_REQUIRED, 'The application name', 'frontend'),
  12.         new sfCommandOption('env', null, sfCommandOption::PARAMETER_REQUIRED, 'The environment', 'dev'),
  13.         new sfCommandOption('connection', null, sfCommandOption::PARAMETER_REQUIRED, 'The connection name', 'doctrine'),
  14.         // add your own options here
  15.         ));
  16.  
  17.         $this->namespace        = 'database';
  18.         $this->name             = 'backup';
  19.         $this->briefDescription = 'Makes a snapshot of the database. Triggers the automatic archiving system.';
  20.         $this->detailedDescription = <<<EOF
  21.         The [backup:db|INFO] task makes snapshots of the database, for backup purposes.
  22.           - Supports multiple snapshots per day
  23.           - Keeps all snapshots generated during the current and the previous month.
  24.           - Keeps the most recent snapshot of each week among those saved two months ago.
  25.           - Keeps the most recent snapshot of the month among the weekly backups saved three months ago.
  26.         Call it with:
  27.  
  28.         [php symfony backup:db|INFO]
  29. EOF;
  30.     }
  31.    
  32.     /**
  33.      * Main callable. Gets the config, does the snapshot, makes the cleaning.
  34.      *
  35.      * @todo make it work not only with DSN, but with all kinds of configs put in database.yml
  36.      * @todo Make it work with more RDBMS
  37.      * @todo Automatic syncing with distant storage services (Amazon S3, etc.)
  38.      *
  39.      *
  40.      */
  41.     protected function execute($arguments = array(), $options = array())
  42.     {
  43.         //-- Fetching the current active database
  44.         $databaseManager  = new sfDatabaseManager($this->configuration);
  45.         $database         = $databaseManager->getDatabase($options['connection'] ? $options['connection'] : null);
  46.         $connection       = $database->getConnection();
  47.        
  48.         //-- Proceeding to configuraiton
  49.         $this->initializeConfig();
  50.        
  51.         //-- Parsing the database connection information
  52.         $dsn  =  $database->getParameter('dsn');
  53.         $name =  $database->getParameter('name');
  54.    
  55.         if ( !strpos($dsn, '://'))
  56.         {
  57.             $dsn = array($dsn, $database->getParameter('username'), $database->getParameter('password'));
  58.         }
  59.        
  60.         $parts = $this->parseDsn($dsn);
  61.        
  62.         //-- preparing the backup command
  63.         $command = $this->getDumpCommand(
  64.             $parts['scheme'],
  65.             $parts['host'],
  66.             $parts['user'],
  67.             $parts['pass'],
  68.             $parts['path']
  69.         );
  70.        
  71.         //-- Creating the snapshot
  72.         exec($command);
  73.         $this->logSection('backup', sprintf('Backup done for %s', date('d M Y')));
  74.        
  75.        
  76.         //-- let's do some cleanup
  77.         $this->cleanup();
  78.     }
  79.    
  80.    
  81.     /**
  82.      * Gets the name of the file of the day, accordingly to date x time x sequence.
  83.      *
  84.      *
  85.      */
  86.     protected function getCurrentFilename()
  87.     {
  88.         $file = $finder = sfFinder::type('file')
  89.             ->name(sprintf('%s_*.sql', date('Y-m-d')))
  90.             ->in($this->getDirectory());
  91.            
  92.         $cnt    = count($file) == 0 ? 1 : count($file)+1;
  93.         $cntdef = ($cnt < 10) ? '0' . $cnt : $cnt;
  94.        
  95.         return sprintf('%s/%s_%s.sql', $this->getDirectory(), date('Y-m-d'), $cntdef);
  96.     }
  97.    
  98.    
  99.     /**
  100.      * Parsing the DSN set in databases.yml to make it compatible with both sf1.0 and sf1.2+ writing conventions
  101.      *
  102.      * @param mixed $dsn - The DSN set in databases.yml. Can be array, can be string (Pear-like).
  103.      *
  104.      */
  105.     protected function parseDsn($dsn)
  106.     {
  107.         if (is_array($dsn))
  108.         {
  109.             $dsnParts    =  explode(':', $dsn[0]);
  110.             $decryptDsn  =  array();
  111.            
  112.             foreach (explode(';', $dsnParts[1]) as $val)
  113.             {
  114.                 $miniParts = explode('=', $val);
  115.                 $decryptDsn[$miniParts[0]] = $miniParts[1];
  116.             }
  117.            
  118.             if ($dsnParts[0] == 'uri')
  119.             {
  120.                 $dsnParts[0] = 'odbc';
  121.             }
  122.            
  123.             $parts = array(
  124.                 'user'   => (isset($dsn[1])) ? $dsn[1] : null,
  125.                 'pass'   => (isset($dsn[2])) ? $dsn[2] : null,
  126.                 'scheme' => $dsnParts[0],
  127.                 'host'   => $decryptDsn['host'],
  128.                 'path'   => $decryptDsn['dbname']
  129.             );
  130.         }
  131.         else
  132.         {
  133.             $parts = parse_url($dsn);
  134.         }
  135.        
  136.         //-- cleaning starting slash for path (see parse_url behaviour)
  137.         if (preg_replace('^\/', $parts['path']))
  138.         {
  139.             $parts['path'] = substr($parts['path'], 1);
  140.         }
  141.        
  142.         return $parts;
  143.     }
  144.    
  145.    
  146.     /**
  147.      * Cleans up old backups
  148.      *
  149.      *
  150.      */
  151.     protected function cleanup()
  152.     {
  153.         //-- Timestamps we will be needing.
  154.         $twoMonthsAgo     = mktime(0, 0, 0, date('m')-2, date('d'), date('Y'));
  155.         $threeMonthsAgo   = mktime(0, 0, 0, date('m')-3, date('d'), date('Y'));
  156.        
  157.         //-- If we cleaned that already up, we left a trace
  158.         if (!file_exists($this->getBackupLog($twoMonthsAgo, 'weekly')))
  159.         {
  160.             $this->log('-----------------------------');
  161.             $this->log(sprintf('Weekly Cleaning for %s', date('F Y', $twoMonthsAgo)));
  162.             $this->log('-----------------------------');
  163.            
  164.             //-- Fetching all files, classifying them into weeks, keeping the most recent each week.
  165.             $finder = sfFinder::type('file')
  166.                 ->name(sprintf('%s-*.sql', date('Y-m', $twoMonthsAgo)))
  167.                 ->in($this->getDirectory());
  168.            
  169.             $weeks = array();
  170.            
  171.             foreach ($finder as $file)
  172.             {
  173.                 $week = date('W', $this->getTimestampFromFile($file));
  174.                
  175.                 if (array_key_exists($week, $weeks))
  176.                 {
  177.                     $old = $weeks[$week];
  178.                    
  179.                     if ($this->getNumericValue($file) > $this->getNumericValue($old))
  180.                     {
  181.                         $weeks[$week] = basename($file);
  182.                         $this->deleteFile($old);
  183.                     }
  184.                     else
  185.                     {
  186.                         $this->deleteFile($file);
  187.                     }
  188.                 }
  189.                 else
  190.                 {
  191.                     $weeks[$week] = basename($file);
  192.                 }
  193.             }
  194.            
  195.            
  196.             file_put_contents($this->getBackupLog($twoMonthsAgo, 'weekly'), 'saved');
  197.             $this->logSection('cleaning', sprintf('Weekly cleaning done for %s', date('F Y', $twoMonthsAgo)));
  198.         }
  199.         else
  200.         {
  201.             $this->logSection('cleaning', sprintf('The weekly archive for %s already exists.', date('F Y', $twoMonthsAgo)));
  202.         }
  203.        
  204.        
  205.        
  206.        
  207.         //-- Keeping the most recent backup from all the snapshots made 3 months ago
  208.         if (!file_exists($this->getBackupLog($threeMonthsAgo, 'monthly')))
  209.         {
  210.             if (!file_exists($this->getBackupLog($threeMonthsAgo, 'weekly')))
  211.             {
  212.                 $this->logSection('nothing to clean', sprintf('No weekly for %s', date('M Y', $threeMonthsAgo)));
  213.                 return;
  214.             }
  215.             else
  216.             {
  217.                 $this->log('-----------------------------');
  218.                 $this->log(sprintf('Monthly Cleaning for %s', date('F Y', $threeMonthsAgo)));
  219.                 $this->log('-----------------------------');
  220.             }
  221.            
  222.             //-- Keeping the highest weekly file.
  223.             $finder = sfFinder::type('file')
  224.                 ->name(sprintf('%s-*.sql', date('Y-m', $threeMonthsAgo)))
  225.                 ->in($this->getDirectory());
  226.            
  227.             $latest = max($finder);
  228.            
  229.             foreach ($finder as $weekly)
  230.             {
  231.                 if ($weekly != $latest)
  232.                 {
  233.                     $this->deletefile($weekly);
  234.                 }
  235.             }
  236.            
  237.             //-- Leaving our trace
  238.             file_put_contents($this->getBackupLog($threeMonthsAgo, 'monthly'), 'saved');
  239.             $this->logSection('cleaning', sprintf('Monthly cleaning done for %s', date('F Y', $threeMonthsAgo)));
  240.         }
  241.         else
  242.         {
  243.             $this->logSection('passing', sprintf('The archive for %s already exists.', date('F Y', $threeMonthsAgo)));
  244.         }
  245.     }
  246.    
  247.    
  248.    
  249.     /**
  250.      * Gets the backup command, according to the driver
  251.      *
  252.      * @param string $driver
  253.      * @param string $host
  254.      * @param string user
  255.      * @param string pass
  256.      * @param string dbname
  257.      * @todo other RDBMS
  258.      * @todo test it on pgsql (not tested!!)
  259.      *
  260.      *
  261.      */
  262.     protected function getDumpCommand($driver = null, $host = null, $user = null, $pass = null, $dbname = null)
  263.     {
  264.         if ($driver === null || $host === null || $dbname === null || $user === null)
  265.         {
  266.             throw(new sfException('The database parameters seem wrong. Database snapshot failed.'));
  267.             return false;
  268.         }
  269.        
  270.         switch ($driver)
  271.         {
  272.             case 'mysql':
  273.                
  274.                 return sprintf('%smysqldump -h %s -u %s -p%s %s > %s',
  275.                     $this->pathToExecutable,
  276.                     $host,
  277.                     $user,
  278.                     $pass,
  279.                     $dbname,
  280.                     $this->getCurrentFilename()
  281.                 );
  282.                
  283.             break;
  284.            
  285.            
  286.             case 'pgsql':
  287.            
  288.                 return sprintf('%spg_dump -h %s -u %s %s > %s',
  289.                     $this->pathToExecutable,
  290.                     $host,
  291.                     $user,
  292.                     $dbname,
  293.                     $this->getCurrentFilename()
  294.                 );
  295.             break;
  296.            
  297.             default:
  298.            
  299.                 throw(new sfException('Your database management system seems to be unsupported for now.'));
  300.         }
  301.     }
  302.    
  303.    
  304.     /**
  305.      * Grabbing configuration from app.yml, checking and storing preferences.
  306.      *
  307.      *
  308.      */
  309.     protected function initializeConfig()
  310.     {
  311.         //-- Path to store the snapshot
  312.         $validPath       = sfConfig::get('app_database_backup_path', null);
  313.        
  314.         //-- Path to RDBMS executable
  315.         $pathToExec             = sfConfig::get('app_database_backup_path_pathToExec', '');
  316.         $this->pathToExecutable = ($pathToExec == '') ? '' : (preg_match('(\/|\\\)', $pathToExec) ? $pathToExec : $pathToExec . DIRECTORY_SEPARATOR);
  317.            
  318.            
  319.         if ($validPath !== null && file_exists($validPath))
  320.         {
  321.             $this->workingDirectory = $validPath;
  322.         }
  323.         else
  324.         {
  325.             throw(new sfException('You need to set a valid "path" key under a Database Backup section in your app.yml'));
  326.         }
  327.     }
  328.    
  329.    
  330.     /**
  331.      * Returns the name of the directory where we save everything
  332.      *
  333.      *
  334.      */
  335.     protected function getDirectory()
  336.     {
  337.         return $this->workingDirectory;
  338.     }
  339.    
  340.    
  341.     /**
  342.      * Deletes a file from its name
  343.      *
  344.      * @param string file - The name of the file to delete
  345.      *
  346.      */
  347.     protected function deleteFile($file)
  348.     {
  349.         $toDel = preg_replace('(\/|\\\)', $file) ? basename($file) : $file;
  350.         $this->logSection('deleting', $toDel);
  351.         unlink($this->getDirectory() . DIRECTORY_SEPARATOR . $toDel);
  352.     }
  353.    
  354.    
  355.     /**
  356.      * Gets the path to the log file
  357.      *
  358.      * @param int $timestamp
  359.      * @param string $type - 'weekly' or 'monthly'
  360.      *
  361.      */
  362.     protected function getBackupLog($timestamp = null, $type = 'weekly')
  363.     {
  364.         return sprintf('%s/%s.saved.%s', $this->getDirectory(), date('Y-m', $timestamp), $type);
  365.     }
  366.    
  367.    
  368.    
  369.     /**
  370.      * Fetches timestamp from file
  371.      *
  372.      * @param string $file - The name of the file to read the timestamp from.
  373.      *
  374.      */
  375.     protected function getTimestampFromFile($file = null)
  376.     {
  377.         if ($file === null)
  378.         {
  379.             return false;
  380.         }
  381.        
  382.         return strToTime(substr(basename($file), 0, strPos(basename($file), '_')));
  383.     }
  384.    
  385.    
  386.    
  387.     /**
  388.      * Converts a timestamped file to numeric value for comparaison
  389.      *
  390.      * @param string $file - The name of the file to get a numeric value from
  391.      *
  392.      */
  393.     protected function getNumericValue($file)
  394.     {
  395.         $toHandle = ereg('(\/|\\\)', $file) ? basename($file) : $file;
  396.         return (int)preg_replace('~[^\\pL\d]+~u', '', $toHandle);
  397.     }
  398. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement