move_uploaded_file() expects parameter 2 to be valid path, object given - php

I'm using Symfony 2.3 to save a file uploaded by a form POST.
This is the code I use in the controller:
$fileDir = '/home2/divine/Symfony/src/App/Bundle/Resources/public/files';
$form['my_file']->getData()->move($fileDir, 'book.pdf');
Under water, Symfony executes this code to move the file:
move_uploaded_file("/tmp/phpBM9kw8", "/home2/divine/Symfony/src/App/Bundle/Resources/public/files/book.pdf");
The public directory has 777 permissions.
This is the error I get:
"Could not move the file "/tmp/phpBM9kw8" to "/home2/divine/Symfony/src/App/Bundle/Resources/public/files/book.pdf"
(move_uploaded_file() expects parameter 2 to be valid path, object given)"
I'm using PHP 5.3.
Update:
This is the code snipped that executes the move_uploaded_file():
// Class: Symfony\Component\HttpFoundation\File\UploadedFile
$target = $this->getTargetFile($directory, $name);
if (!#move_uploaded_file($this->getPathname(), $target)) {
// etc...
The $target" variable is created here:
protected function getTargetFile($directory, $name = null) {
// Some error handling here...
$target = $directory.DIRECTORY_SEPARATOR.(null === $name ? $this->getBasename() : $this->getName($name));
return new File($target, false);
}
The $target variable is therefor a File class. It does have a __toString() method, inherited from SplFileInfo:
/**
* Returns the path to the file as a string
* #link http://php.net/manual/en/splfileinfo.tostring.php
* #return string the path to the file.
* #since 5.1.2
*/
public function __toString () {}
But somehow that __toString method is not working.

But somehow that __toString method is not working
It is one of the “magic methods”, it gets called automatically when the object is used in a string context – so for example if you had 'foo' . $object.
But I don’t think it is supposed to work in this situation here. Because PHP is loosely typed, you can pass anything into move_uploaded_file. No automatic conversion to string will happen at this point. And then internally, the function only checks if the parameter is a string, but doesn’t try to convert it into one – because that would make little sense, it could be any kind of object, and there is no way of telling if calling __toString would result in a valid file path.
You might wonder now, why in the error message we do get to see the path:
Could not move the file "/tmp/phpBM9kw8" to "/home2/divine/Symfony/src/App/Bundle/Resources/public/files/book.pdf"
My guess is, that when that error message is assembled, there is string concatenation going on, so that __toString does get called at this specific point.
If you are willing to modify the Symfony source code, I think this should work as an easy fix, if you just change this line
if (!#move_uploaded_file($this->getPathname(), $target)) {
to
if (!#move_uploaded_file($this->getPathname(), ''.$target)) {
– then you have the situation again, where __toString will be called, because the object is transferred into a string context by concatenating it with a string (an empty one, because we don’t want to tamper with the resulting value.)
Of course modifying a framework’s files directly is not the most recommendable way of dealing with this – after the next update, our change might be lost again. I’d recommend that you check the Symfony bugtracker (they should have something like that) to see if this is a known issue already and if maybe an official patch file exists; and otherwise report it as a bug, so that it can be fixed in a future version.

Related

UnitEnum cannot be cast to string

I have a variable declared in config/services.yaml
parameters:
login_url: '%env(string:APP_FRONTEND_DOMAIN)%'
I am accessing it in my controller like this:
$loginUrl = (string) ($this->getParameter('login_url') ?? "");
Everything works fine, but psalm is giving following error:
ERROR: PossiblyInvalidCast - src/Controller/MyController.php:57:31 - UnitEnum cannot be cast to string (see https://psalm.dev/190)
$loginUrl = (string) ($this->getParameter('login_url') ?? "");
Any suggestions, how to fix it, please?
Duplicated the question in the official github issue of the pslam-plugin-symfony: https://github.com/psalm/psalm-plugin-symfony/issues/272
Symfony started documenting that getParameter can return UnitEnum in specific cases. This throws up Psalm analysis because in most cases, this is not what happens and you just get a scalar in return.
Unfortunately, this is not something that can be easily handled on the user side (you'd have to make a proxy method to make sure you exclude the UnitEnum case). So ideally, it should be handled by the Psalm's Symfony plugin (even if I'm not sure how). I suggest creating an issue on the tracker on github as I don't see it yet.
Source: I'm a Psalm maintainer
As I mentioned, I duplicated the issue in github pages of the psalm and psalm-plugin and actually received the answer from one of them which solves my problem.
The answer is copied from: https://github.com/psalm/psalm-plugin-symfony/issues/272#issuecomment-1211802478
Here is the related part on Symfony side:
/**
* Gets a service container parameter.
*
* #return array|bool|string|int|float|\UnitEnum|null
*
* #throws ParameterNotFoundException if the parameter is not defined
*/
public function get(string $name);
According to the code, it means $login_url can be array, bool, etc. including \UnitEnum which cannot be casted to string. Thus the error is correct actually.
On the other hand, I know that you specified the type on parameters with environment variable which should be string. To be able to infer the type of parameter, the plugin needs to analyze compiled container XML (assuming that you already configured it) which is currently missing.
For now, you can rewrite it to tackle the error:
$loginUrl = $this->getParameter('login_url');
$loginUrl = is_string($loginUrl) ? $loginUrl : '';

CakePHP 4 - access private property to determine whether a file has successfully uploaded

In CakePHP 4.x the docs show how you can move an uploaded file as follows:
$files = $request->getUploadedFiles();
// Move the file.
$files[0]->moveTo($targetPath);
This works in terms of moving the uploaded file to wherever $targetPath is specified as.
But - how do you determine whether this was actually successful? After looking at the framework code this uses Laminas\Diactoros\UploadedFile which contains the moveTo() (https://github.com/laminas/laminas-diactoros/blob/2.10.x/src/UploadedFile.php#L162) function. That function can throw errors, e.g.
throw Exception\UploadedFileErrorException::dueToStreamUploadError(
self::ERROR_MESSAGES[$this->error]
);
The end of the function (if no errors get thrown) contains:
$this->moved = true;
The function's return type is void.
$moved is a private property of this class and there doesn't seem to be any function that allows you to "get" the value of $moved outside that class.
So how do you determine whether or not this actually succeed? Or do you just assume if it doesn't thrown an error it has succeeded?
It seems strange because PHP's native move_uploaded_file (https://www.php.net/manual/en/function.move-uploaded-file.php) returns boolean, which is what you'd expect since you'd probably want to know whether uploading a file has actually worked successfully or otherwise.
What's stranger is that when I debug $files in the CakePHP application:
$files[0]->moveTo($destinationFile);
debug($files);
die;
It shows a private property for "moved" which is true:
object(Laminas\Diactoros\UploadedFile) id:0 {
// ...
private moved => true
private size => (int) 45578
private stream => object(Laminas\Diactoros\Stream) id:1 { }
}
But because that's a private property I can't access it in my application, e.g.
debug($files[0]->moved);
results in:
Cannot access private property Laminas\Diactoros\UploadedFile::$moved
In the above case I have manually checked that the file got moved to the correct location on my filesystem. But I can't determine that via my code, it seems.

Testing method with no output

I have the following method I want to test:
class SomeObject {
public function actionFromSomeController() {
$obj = new OtherObject();
$obj -> setAttributes();
$obj -> doAction();
}
}
class OtherObject {
private $_attr;
public function setAttributes() {
$this -> _attr = 'something';
Database :: execute('INSERT INTO table VALUES (' . $this -> _attr . ')');
$fileObj = new FileObj();
$content = $fileObj -> getSomeFileContent();
// do something else
}
public function doAction() {
echo $this -> _attr;
}
}
Now I want to test this method, its output depends on database content and one file on the server. It does a lot of things on the way, and the output is just one ID and success => 1.
How should I test it properly?
Some ideas on how to test small code pieces like this:
Generate test-data and pass it to your methods (also, fake database return data or file contents)
Use echo / var_dump() / die() to check property and variable content at different positions in your methods
Also use these commands to check whether execution reaches a certain point (for example to see whether a function got called or not)
If something doesn't work as expected without an error message: Check line by line with the above methods until you find the problem
Consider using interfaces and dependency injection if your code gets bigger - this is a bit over-the-top for this amount of code, but can be a tremendous time-saver when your application becomes big
Testing is never an automatic process and you will always have to think about what makes sense to do and what not. These things to do are never magic but basic PHP.
You should consider letting your scripts throw errors/exceptions if something goes wrong. Writing "silent" applications is almost never good since you can, if you really need a silent execution for production environments, just turn off error reporting and have the same effect. Many PHP functions return something special on failure and/or success and you can check for this. Database handlers do so, too. Do yourself a favor and use these return values!

__PHP_Incomplete_Class_Name wrong

We're randomly getting some very strange error logs. They don't happen on every page hit, even with the same parameters/actions/etc, and they don't seem repeatable, each one is different in its crash location, and context. But almost all have incorrect __PHP_Incomplete_Class_Name as the cause.
One such error is:
main(): The script tried to execute a method or access a property of an incomplete object. Please ensure that the class definition "LoginLogging" of the object you are trying to operate on was loaded before unserialize() gets called or provide a __autoload() function to load the class definition
The problem being, there is no "LoginLogging" class. The object it's referring to was of type ScormElement when it was saved into the session.
Doing a dump of the variable gives:
__PHP_Incomplete_Class::__set_state(array(
'__PHP_Incomplete_Class_Name' => 'LoginLogging',
'intUserId' => '64576',
'__intUserId' => '64576',
'intScormId' => '665',
'__intScormId' => '665',
'intScoId' => '9255',
'__intScoId' => '9255',
'strElement' => 'cmi.core.lesson_location',
'__strElement' => 'cmi.core.lesson_location',
'strValue' => '1',
'dttTimeModified' => QDateTime::__set_state(array(
'blnDateNull' => false,
'blnTimeNull' => false,
'strSerializedData' => '2011-08-31T08:05:22-0600',
'date' => '2011-08-31 08:05:22',
'timezone_type' => 1,
'timezone' => '-06:00',
)),
'__strVirtualAttributeArray' => array (),
'__blnRestored' => true,
'objUser' => NULL,
'objScorm' => NULL,
)
All the properties are retained correctly, and match the class definition for ScormElement. But the class name is wrong. There is no class named LoginLogging.
What is causing this and how do we fix it???
Edit: This is just an example. Other errors are very similar in structure, but affect other class types, and have different incomplete names. However, ALL incomplete names have the same string length of the correct class name.
Edit 2011-10-27: I'm still seeing these error logs, and have had no success in finding a solution. Any help would be appreciated.
PHP 5.3.3, APC, default session handler.
As written in the quote in your question __PHP_Incomplete_Class_Name is a special class name in PHP that is used whenever the class definition could not be found when unserializing (unserialize()).
It's suggested to either ensure the class definition is available or to provide some autoloading for the missing class.
You commented that PHP is looking for the wrong classname here:
wrong: LoginLogging
right: ScormElement
It's hard to say with the information given why the classname is being changed on serialization/unserialization. The information you've given (especially the dump) in your question is incomplete.
So the options are limited to some general suggestions:
You could inspect the serialized string which classname is given in there so you could at least say if it happens with serialization or unserialization. You can easily inspect serialized data with the Serialized PHP Library which has a text-dumper for debug purposes.
Additionally there is the unserialize_callback_func Ini-Directive you can use to further trace the problem.
<?php
$serialized_object='O:1:"a":1:{s:5:"value";s:3:"100";}';
// unserialize_callback_func directive available as of PHP 4.2.0
ini_set('unserialize_callback_func', 'mycallback'); // set your callback_function
function mycallback($classname)
{
// just include a file containing your classdefinition
// you get $classname to figure out which classdefinition is required
}
As you write the problem does not occur any longer, I suspect that at some point the serialization on your site was broken storing invalid data. Which framework/libraries/application are you using? Which component takes care about serialization?
If your trying to access a property method of an object or serialized value you've stored in a $_SESSION variable and you included the class after calling session_start() try including the class before calling session_start();
This happens when we try to initialize the session before loading the class definitions for the object we are trying to save into the session.
you can use simply json methods in PHP
json_encode that array and again json_decode that array and save it in a variable and than print that variable. you will get a simple array.
eg.
$array = array(); // this is your array variable to whom you are trying to save in a session
$encode = json_encode($array);
$decode = json_decode($encode);
echo '<pre>';
print_r($decode);
it will work surely.
Are you, by any chance, using some opcode cache (e.g. APC, XCache) or some debugger? They sometimes cause weird things to happen. If there isn't and never was a LoginLogging class in your project, but yours is not the only project on the server, I would bid on the cache.
To include the class before the session_start() works fine for me. =)
Hakre's suggestion to look at session_write_close led me to what appears to be a reliable fix:
register_shutdown_function('session_write_close');
This forces the session to be written out before any memory cleanup and class unloading occurs. This is important due to a change in PHP that can result in a race condition between APC removing class references and PHP writing out the session data: http://news.php.net/php.internals/46999
When you try to unserialize a variable, php must know the structure of the object. Try to include your class before unserialize. Which class? the one that holds those properties shown in your code. include('pathtoyourclass/classname.php') or include_once('pathtoyourclass/classname.php').
Good luck!
I use this function to solve this problem
$user = $_SESSION['login'];
$auth_user= fixObject($user);
function fixObject(&$object) {
if (!is_object($object) && gettype($object) == 'object')
return ($object = unserialize(serialize($object)));
return $object;
}
It is the serialization issue in your code.It may be your code for some reason could not initialize the object and in next part of your code tries to convert that object to string or some other data type.
Just inspect your code, properly initialize your objects.
Check if you are using serialize()/unserialize() method.
Are you receiving this serialized object from a service/stream or reading it from a file? I've seen it happen in a client-server setting the client side object and the server side objects differ very slightly... maybe have a property that the other doesn't have. This also happens when you serialize an object, save it to file, then refactor code that removes a class and then try's to deserialize the object.
Then when it gets deserialized and tries to instantiate the needed classes they don't exist.
The error is clear and straightforward, you're creating a object for which the class has not been loaded.
I'd do a string search on your code, find where the needed class is located, and make a simple autoloader (or just a raw include/require) of the file. Just get the class it needs loaded somehow.
If the class really doesn't exist anywhere, try checking earlier revisions (I'm assuming you have version control of some type) for existence of the class. If you're deserializing an object generated by another system check that system out as well.

Why can't I store a PHP class instance as a SESSION variable

I have a PHP script that is called in 2 ways from a Dojo Ajax xhrGet call.
The first time it is called with an "init" argument which causes the script to create an instance of the StateList class and read in a file of state names.
session_start();
#include('StateList.php');
require_once('phplog.php');
//start executing here
$comd=$_GET['nexturl'];
if($comd=="init") {
$st = new StateList("../data/statestxt.txt");
$_SESSION['statefile'] = $st;
}
The second and further times, another xhrGet call passes a "getstate" argument and the following code tries to get the instance ofr the StateList class from the SESSION array.
if($comd =="getstate") {
$st= $_SESSION['statefile'];
phplog("size=".$st->getSize());
}
However, the getSize() method is never executed, nor can I call any other method
on the reconstituted StateList class instance.
Note that this is one PHP script that DOES include the class definition at the top
and thus the class methods should be known and avaialble.
What am I missing here?
You need to include the class definition before you call session_start(), otherwise the object will not be deserialized correctly and will be an instance of __PHP_Incomplete_Class. Otherwise what you have should work fine.
You may need to serialize the $st object/variable before you store it. This will ensure that everything is saved to the session. This is definitely the way to go for object oriented code. When you want to use the data again, you must unserialize it.
This is one of those things that's hard to debug in isolation. Storing instantiated objects in PHP Sessions is always a little tricky, and not 100% guaranteed to work. Here's some general debugging tips that may help you figure this out.
First, check your apache error log. Are you getting a "method called on non-object error"? If so, this means you're not getting an object back out of the session. If not, is there an error that indicated your method call is failing for another reason?
Second, check to see what you're really getting out of your session.
if($comd =="getstate") {
$st= $_SESSION['statefile'];
//get the class of st
phplog("instance=".get_class($st));
//get a reflection dump of st
$ref = new ReflectionClass($st);
$string = $ref->__toString();
phplog("reflection=".$string);
}
Third, look at the serialized string value that is being stored in the session itself. Are you actually storing a serialized object? In your dev environment, set the session.save_path ini value in php.ini to something like /tmp, (or use the ini_set method to do the same thing):
session.save_path = "/tmp"
and then examine the files created in /tmp (or whatever folder). You should see a string that starts with:
statefile:O:..........
The name of the class that instantiated the object will also be included in there, as well as values saved to properties.
If you are going to store an object in the session it must be link text.There are a LOT of problems with serializing objects in PHP, let alone storing them in the session. I recommend against doing this altogether, and finding a different solution for your problem. If you are going to do it though, you should look into the 'magic methods' link text which you should define in your class to facilitate it's reinstantiation when it is called from the session.
Do you have session.auto_start enabled? The manual's session page states that if you do, you have to load the class definition differently:
If you turn on session.auto_start then the only way to put objects into your sessions is to load its class definition using auto_prepend_file in which you load the class definition else you will have to serialize your object and unserialize it afterwards.
http://php.net/manual/en/intro.session.php
As that page says, the serialization/unserialization of the object will normally be done automatically by PHP, but having session.auto_start enabled will change this.
Try this:
include('StateList.php');
require_once('phplog.php');
// start your session after including your class file
session_start();
//start executing here
$comd=$_GET['nexturl'];
if($comd=="init") {
$st = new StateList("../data/statestxt.txt");
$_SESSION['statefile'] = $st;
}
if($comd =="getstate") {
// the ampersand creates a reference, preserving any further changes to your session data
$st = &$_SESSION['statefile'];
phplog("size=".$st->getSize());
}

Categories