If multiple threads do the following:
if ( !is_dir($dir) )
mkdir($dir, 0, true);
what will happen if two threads will detect "at the same time" that the directory does not exist and then they both try and create it?
Is mkdir synchronized to prevent bad things from happening or is there a way to flock this to make sure that only one thread creates the directory and/or files ?
I know this an old question but something like this works really well, assuming you have an error handler that converts errors to exceptions (which is a good idea anyway):
/**
* Create a folder in a thread safe way
* Between 'is_dir' and 'mkdir' another thread could have created a folder.
* This can cause the system to raise an unwarrented error
*
* Returns TRUE if folder was created, NULL if folder already exists
*
* Throws exception on any other error
*
* #param array $array
* #param string $key
* #param mixed $value
* #return array
*/
function mkdir_thread_safe($dir, $permissions = 0777, $recursive = false)
{
if(is_dir($dir)) return;
try {
mkdir($dir, $permissions, $recursive);
}
catch (\ErrorException $e){
if(is_dir($dir)) return;
throw $e;
}
return true;
}
Here is the error to exception conversion handler - this is really good practice anyway, and should be added at the start of any application entry point. Some modern frameworks such as laravel, take care of this by default, so if you are using laravel, your errors will already be converted to exceptions.
set_error_handler(function ($level, $message, $file, $line, $context)
{
if (error_reporting() & $level)
{
throw new ErrorException($message, $level, 0, $file, $line);
}
});
Only one of them will manage to create the directory, the other mkdir will return false and throw a warning
You can also have a look at this bug in php, it's not exactly the case like with your question, but it is related
Related
In testing, I have been unable to add globals to Slim v4's TwigView. It used to be that you could do it like so:
$twigView->getEnvironment()->addGlobal('flash', $container->get('flash'));
$twigView->getEnvironment()->addGlobal('session', $_SESSION);
But that now throws the exception: Unable to add global 'flash' as the runtime or the extensions have already been initialized.
I took a look at the Environment class of Twig and found this bit of validation:
/**
* Registers a Global.
*
* New globals can be added before compiling or rendering a template;
* but after, you can only update existing globals.
*
* #param string $name The global name
* #param mixed $value The global value
*/
public function addGlobal($name, $value)
{
if ($this->extensionSet->isInitialized() && !array_key_exists($name, $this->getGlobals())) {
throw new \LogicException(sprintf('Unable to add global "%s" as the runtime or the extensions have already been initialized.', $name));
}
if (null !== $this->resolvedGlobals) {
$this->resolvedGlobals[$name] = $value;
} else {
$this->globals[$name] = $value;
}
}
Can anybody explain to me why we seem to be throwing the exception if the global DOESN'T exist, instead of if it DOES exist? That seems to be a logic error to me, but perhaps I'm misunderstanding it.
Thanks in advance.
It is not a logical error.
From the DocBlock you posted with your question:
New globals can be added before compiling or rendering a template; but after, you can only update existing globals.
Now having a look at the code:
if (
// if the extension is initialized, the right side of && operator will be evaluated and...
$this->extensionSet->isInitialized() &&
// ...this means, we are only allowed to UPDATE a global,
// so the global should already exist
// and its lack of existence is an error, hence, if it does not exists, we throw an exception
!array_key_exists($name, $this->getGlobals())
) {
throw new \LogicException(sprintf('Unable to add global "%s" as the runtime or the extensions have already been initialized.', $name));
}
The error text basically says:
A global with the name you're referring to does not exist, which means this is not an update but a creation operation,
and since the extension is already initialized, you're not allowed to do an update.
I'm stumped on a XenForo 1.5.7 / php7 issue. I've read that tempnam() was changed as of php7 (based on temp dir permissions), but I've chmod'd the directory as that link states, still to no avail.
I printed out $newTempFile which returns /var/www/forum/internal_data/temp/xfJ9FLyG (looks correct). It's the next line, the $image variable, that does not get set, and then throws the error in the if() below.
$newTempFile = tempnam(XenForo_Helper_File::getTempDir(), 'xf');
$image = XenForo_Image_Abstract::createFromFile($fileName, $imageType);
if (!$image)
{
throw new XenForo_Exception(new XenForo_Phrase('image_could_be_processed_try_another_contact_owner'), true);
}
Here is the code for createFromFile() in Image\Abstract.php:
/**
* Creates an image from an existing file.
*
* #param string $fileName
* #param integer $inputType IMAGETYPE_XYZ constant representing image type
*
* #return XenForo_Image_Abstract|false
*/
public static function createFromFileDirect($fileName, $inputType)
{
throw new XenForo_Exception('Must be overridden');
}
...
public static function createFromFile($fileName, $inputType)
{
$class = self::_getDefaultClassName();
return call_user_func(array($class, 'createFromFileDirect'), $fileName, $inputType);
}
Because it looks like createFromFileDirect() is called from createFromFile(), my thought was that a "Must be overriden" error would be thrown, but this doesn't appear to be the case.
Any ideas?
Is it possible to remove a script that was previously added like this?
$this->view->headScript()->appendFile("/js/my.js");
The situation here is that in my Bootstrap.php several JavaScript files are appended like in the above example but in a particular controller I want one particular JavaScript file not to be loaded. Is there a way to remove it during the initiation of the controller?
I am looking for something like this.
$this->view->headScript()->removeFile("/js/my.js");
This works, but really isn't a good practice but rather for exceptional purposes. I recommend trying to not load the unwanted scripts in the first place.
It is possible to remove scripts subsequently with a function like this one.
/**
* Removes a previously appended script file.
*
* #param string $src The source path of the script file.
* #return boolean Returns TRUE, if the removal has been a success.
*/
public function removeScript($src) {
$headScriptContainer = Zend_View_Helper_Placeholder_Registry::getRegistry()
->getContainer("Zend_View_Helper_HeadScript");
$iter = $headScriptContainer->getIterator();
$success = FALSE;
foreach ($iter as $k => $value) {
if(strpos($value->attributes["src"], $src) !== FALSE) {
$iter->offsetUnset($k);
$success = TRUE;
}
}
Zend_View_Helper_Placeholder_Registry::getRegistry()
->setContainer("Zend_View_Helper_HeadScript",$headScriptContainer);
return $success;
}
Note that the strpos function is used here. This will remove every script that has $src in its path. Of course you can change that to your needs.
It's an old question, but here is an alternative:
I used the minifyHeadScript and changed his helper adding this custom function:
/**
* Removes all script file.
*
*/
public function clearAllScripts(){
$this->getContainer()->exchangeArray(array());
$container = $this->getContainer();
Zend_View_Helper_Placeholder_Registry::getRegistry()->setContainer("Zend_View_Helper_HeadScript", $container);
}
I need a way to prevent multiple run on a process on symfony 1.4.
Something like: when a user is running this process, the other user who tries to run this process will get a warning message inform that the process is running.
Is there a way to implement it without using database?
kirugan's method will probably work in most cases, but it's susceptible to race conditions and can get stuck in the event of a crash.
Here's a more robust solution. It uses PHP's file locking so you know the lock is atomic, and if you forget to release the lock later or your process crashes, it gets released automatically. By default, getting a lock is non-blocking (i.e. if the lock is already held by another process, getLock() will instantly return FALSE). However, you can have the call block (i.e. wait until the lock becomes available) if you'd like. Finally, you can have different locks for different parts of your code. Just use a different name for the lock.
The only requirement is that the directory returned by getLockDir() must be server-writable. Feel free to change the location of the lock dir.
Note: I think flock() may behave differently on Windows (I use linux), so double check that if its an issue for you.
myLock.class.php
class myLock
{
/**
* Creates a lockfile and acquires an exclusive lock on it.
*
* #param string $filename The name of the lockfile.
* #param boolean $blocking Block until lock becomes available (default: don't block, just fail)
* #return mixed Returns the lockfile, or FALSE if a lock could not be acquired.
*/
public static function getLock($name, $blocking = false)
{
$filename = static::getLockDir() . '/' . $name;
if (!preg_match('/\.lo?ck$/', $filename))
{
$filename .= '.lck';
}
if (!file_exists($filename))
{
file_put_contents($filename, '');
chmod($filename, 0777); // if the file cant be opened for writing later, getting the lock will fail
}
$lockFile = fopen($filename, 'w+');
if (!flock($lockFile, $blocking ? LOCK_EX : LOCK_EX|LOCK_NB))
{
fclose($lockFile);
return false;
}
return $lockFile;
}
/**
* Free a lock.
*
* #param resource $lockFile
*/
public static function freeLock($lockFile)
{
if ($lockFile)
{
flock($lockFile, LOCK_UN);
fclose($lockFile);
}
}
public static function getLockDir()
{
return sfConfig::get('sf_root_dir') . '/data/lock';
}
}
How to use
$lockFile = myLock::getLock('lock-name');
if ($lockFile) {
// you have a lock here, do whatever you want
myLock::freeLock($lockFile);
}
else {
// you could not get the lock. show a message or throw an exception or whatever
}
Here is my functions for this:
function lock(){
$file = __DIR__ . '/file.lock';
if(file_exists($file)){
/* exit or whatever you want */
die('ALREADY LOCKED');
}
touch($file);
}
function unlock(){
$file = __DIR__ . '/parser.lock';
if(file_exists($file)){
unlink($file);
}else{
echoln("unlock function: LOCK FILE NOT FOUND");
}
}
function exitHandler(){
echoln('exitHandler function: called');
unlock();
}
Use lock function in the beginning, and set exitHandler function in register_shutdown_function(). You can save this snippets as class with the same methods or save it like helper file
I have a Logger interface that accepts a SplFileObject in the constructor to use as the file for that particular log. There is also a log($timestamp, $message) method available to actually do the logging. In my first implementation when instantiating a new object and passing a read-only SplFileObject an exception should be thrown. I wrote up an appropriate unit test:
<?php
class FileLoggerTest extends PHPUnit_Framework_TestCase {
/**
* #expectedException \InvalidArgumentException
*/
public function testReadOnlyFileObjectFailure() {
$file = '/Library/WebServer/Documents/sprayfire/tests/mockframework/logs/test-log.txt';
$LogFile = new \SplFileObject($file);
$Logger = new \libs\sprayfire\logger\FileLogger($LogFile);
$Logger->log('test', 'something');
}
}
?>
Normally I would have a method producing the directory name but when I started encountering problems I changed it to an absolute path to rule out that as the cause.
And here's the implementation:
namespace libs\sprayfire\logger;
use \SplFileObject as SplFileObject;
use \InvalidArgumentException as InvalidArgumentException;
use libs\sprayfire\logger\Logger as Logger;
/**
* #brief A framework implemented class that adds a timestamp log message to
* the end of an injected file.
*/
class FileLogger implements Logger {
/**
* #brief A SplFileObject that should be used to write log messages to.
*
* #property $LogFile
*/
protected $LogFile;
/**
* #param $LogFile SplFileObject that should have log messages written to
*/
public function __construct(SplFileObject $LogFile) {
$this->LogFile = $LogFile;
$this->throwExceptionIfFileNotWritable();
}
/**
* #throws InvalidArgumentException
*/
protected function throwExceptionIfFileNotWritable() {
$isWritable = $this->LogFile->isWritable();
if (!$isWritable) {
throw new InvalidArgumentException('The passed file, ' . $this->LogFile->getPathname() . ', is not writable.');
}
}
/**
* #param $timestamp A formatted timestamp string
* #param $message The message string to log
* #return boolean true if the message was logged, false if it wasn't
*/
public function log($timestamp, $message) {
if (!isset($timestamp) || empty($timestamp)) {
$timestamp = 'No timestamp given';
}
if (!isset($message) || empty($message)) {
$message = 'Attempting to log an empty message';
}
$separator = ' := ';
$message = $timestamp . $separator . $message;
$wasWritten = $this->LogFile->fwrite($message);
if (!isset($wasWritten)) {
return false;
}
return true;
}
}
// End FileLogger
The problem is that the test passes and I can tell by the code coverage generated by the test that isWritable() returns true and SplFileObject::fwrite() on a readonly object returns a non-null value as well.
The really, really weird part of this is that the very same code ran in a non-unit test example fails, just as it should.
$logFile = '/Library/WebServer/Documents/sprayfire/tests/mockframework/logs/test-log.txt';
$SplFile = new \SplFileObject($logFile);
$Logger = new \libs\sprayfire\logger\FileLogger($SplFile);
Running this from index.php results in xdebug showing an uncaught InvalidArgumentException from FileLogger with the expected message that the file passed is not writable. This is completely baffling, the same exact code is being ran in both situations, yet the code inside the unit test is "failing" and the non-unit tested code is performing as expected.
Yes, the file exists. SplFileObject would throw an exception if it didn't.
The very exact same code is being ran in both situations, other code that is being ran includes setting up 2 constants, a file directory and a shortcut to DIRECTORY_SEPARATOR, and setting up class autoloading. But, again, this is happening exactly the same in both situations and would result in a failure long before this unit test is actually ran.
Help!
Looking at it now the problem seems relatively simple. PHP is running under the _www user and phpunit is running as the user that installed it. These users have different permissions, which makes perfect sense. If you somehow are encountering this problem I suggest you look at edorian's answer and re-evaluate how you are writing your unit tests.
First off:
For unit testing there is SplTempFileObject extends SplFileObject.
You usually don't need to create real files on the disk for that as it's slow anyways ;)
For an isReadable / isWriteable check in your phpunit tests you generally create ether create non readable / writeable files on the disk or use vfsStreamWrapper where applicable. It also works with SplFileObject.
Our issue:
In the test the last line should be removed. You except the exception during construct so let's make away with that ;)
What I found strange is that you have an absolute path there. Does your root folder structure really start with '/Library/WebServer/Documents/ ? Mainly I'm confused because that would imply that your tests are in a "WebServer" directory. Anyways.. moving on:
"Works for me"
Attached is a standalone version of the test that works and throws the exception like expected.
Apart from telling you that the problem seem to be somewhere else in your setup I don't see much I can do here. Maybe try the InvalidArgumentException without the / or try the test in isolation / with a newly created file.
PHPUnit doesn't interfere with the file handling functions so I'm out of idea apart from that. Does the sample code below work for you? :)
phpunit mep.php
PHPUnit 3.6.5 by Sebastian Bergmann.
.
Time: 1 second, Memory: 5.00Mb
OK (1 test, 1 assertion)
<?php
class FileLoggerTest extends PHPUnit_Framework_TestCase {
/**
* #expectedException InvalidArgumentException
*/
public function testReadOnlyFileObjectFailure() {
$file = __DIR__."/_files/test-log.txt";
touch($file);
chmod($file, 0444);
$LogFile = new \SplFileObject($file);
$Logger = new FileLogger($LogFile);
}
}
class FileLogger {
protected $LogFile;
public function __construct(SplFileObject $LogFile) {
$this->LogFile = $LogFile;
$this->throwExceptionIfFileNotWritable();
}
/**
* #throws InvalidArgumentException
*/
protected function throwExceptionIfFileNotWritable() {
$isWritable = $this->LogFile->isWritable();
if (!$isWritable) {
throw new InvalidArgumentException('The passed file, ' . $this->LogFile->getPathname() . ', is not writable.');
}
}
/**
* #param $timestamp A formatted timestamp string
* #param $message The message string to log
* #return boolean true if the message was logged, false if it wasn't
*/
public function log($timestamp, $message) {
if (!isset($timestamp) || empty($timestamp)) {
$timestamp = 'No timestamp given';
}
if (!isset($message) || empty($message)) {
$message = 'Attempting to log an empty message';
}
$separator = ' := ';
$message = $timestamp . $separator . $message;
$wasWritten = $this->LogFile->fwrite($message);
if (!isset($wasWritten)) {
return false;
}
return true;
}
}