UnitEnum cannot be cast to string - php

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 : '';

Related

ZF3: Read out url parameter in PhpRenderer

I want to upgrade from ZF2 to ZF3 right now and has the following problem with using URL parameter in my PhpRenderer.
In ZF2 I use the HelperPluginManager to get Application, then the MvcEvent and finally the routeMatch:
$routeMatch = $this
->getHelperPluginManager()
->getServiceLocator()
->get('Application')
->getMvcEvent()
->getRouteMatch();
$parameterAction = $routeMatch->getParam('action');
In ZF3 there is a deprecation warning with using the getServiceLocator() (which makes sense, because it only returns the creationContext from the ServiceManager). I want to find a way not trigger the warning.
Configure the Application as a factory-using class (using \Zend\Mvc\Service\ApplicationFactory::class) also not works, because:
Zend\View\HelperPluginManager can only create instances of Zend\View\Helper\HelperInterface and/or callables.
Is there any way to get the Application context in my template (or better even the parameters of the URL)?
Your question title is "ZF3: Read out url parameter in PhpRenderer" while inside your question you asked another one.
You can get this in any controller (get URL parameters in ZF3):
$URLparam = (string)$this->params()->fromQuery('parameter', '');
For routeMatch, if you have a MvcEvent just use;
$event->getRouteMatch()
If you have container;
$container->getApplication()->getMvcEvent()->getRouteMatch();
If you want to access routeMatch in view there's no way except view helper like tasmaniski/zend-current-route

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

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.

missing "#var annotation", Typo3

I've got a problem with an extension in Typo3. My problem: An uncaught Typo3-Exception:
There is no #var annotation for property "enigma" in class "In2\Femanager\Domain\Model\User"
The exception is an "InvalidArgumentException" thrown by "...__core/typo3_src-6.2.15/typo3/sysext/extbase/Classes/Validation/ValidatorResolver.php". Looking into the code of Femanager/Domain/Model/User.php, there is a line
protected $enigma;
but without any
/**
* #var $enigma Enigma
*/
the variable $enigma is later filled by an object of the type "Enigma". But if I add this part, nothing happens. Anyone got an idea on how to solve this?
As per PHP doc for var states you need to place the type prior to the variable name.
That is:
#var Enigma $engima
Not:
#var $engima Enigma
Also; You may need to reload the op-code cache. (OpCache or the like).
Another possible solution is to disable the validation somehow, until your dev can get back it. (Maybe comment it out, seems complicated to predict the full effect but maybe worth a try if the repercussions of being down outweigh the risk)

Typo3 4.5, PiBase Extension, Ajax Call with eID, how to access configuration?

So i have been assigned a task that would normally be trivial, but it has to work on a rather old Typo3-Website (4.5). I am very unexperienced with Typo3.
To make an AJAX call, i found out that i need an eID, my own class file, i found out how to call the main function and all that.
Now, i have a lot of configuration in many different locations, and i need to access that information.
In the class.tx_as_es_pi1.php the function main($content, $conf) has this very handy parameter $conf. It seems this is made available by some Typo3 magic. Trying to somehow mimick this behaviour, i have tried this answer, and it provides me with some of the configuration, using these lines:
$conf = $GLOBALS['TSFE']->tmpl->setup['plugin.']['tx_ases_pi1.'];
var_dump($conf);
I get this result:
'includeLibs' => string 'typo3conf/ext/as_es/pi1/class.tx_as_es_pi1.php' (length=46)
'userFunc' => string 'tx_ases_pi1->main' (length=17)
but the Typoscript Object Browser shows a lot more (including what i need):
[tx_ases_pi1] = USER_INT # TypoScript added by extension "as_es" # Setting as_es plugin TypoScript
[includeLibs] = typo3conf/ext/as_es/pi1/class.tx_as_es_pi1.php
[userFunc] = tx_ases_pi1->main
[config_template] = EXT:as_es/templates/results_elkwue.htm
[config_template_extended] = EXT:as_es/templates/extended_elkwue.htm
[config_searchaccesskey] = someAccessKey
[config_searchproxy] = someProxyUrl
[config_searchfilterurl] = soeSearchFilterUrl
[config_searchshowstat] = 1
[config_utf8decode] = 1
[config_maxtitlelength] = 50
[config_removefromtitle] = SomeString
[config_piwiktracking_host] = somePiwikHost
[config_piwiktracking_port] = 80
[config_piwiktracking_id] = SomeID
[config_fedebug_messages_search] = {$plugin.tx_ases_pi1.configuration.fedebug_messages_search}
So, obvously, there is something i do not really understand here. Could anyone point me in the right direction?
[EDIT] The answer in the related question only provides some of the configuration data, as shown above. I am looking for a hint on how to retrieve the rest of the data.
Okay. I found the answer to this, in this old post.
In the eID - class, add this method:
/**
* Initializes TSFE and sets $GLOBALS['TSFE'].
*
* #return void
*/
protected function initTSFE() {
$GLOBALS['TSFE'] = t3lib_div::makeInstance('tslib_fe',
$GLOBALS['TYPO3_CONF_VARS'], t3lib_div::_GP('id'), '');
$GLOBALS['TSFE']->connectToDB();
$GLOBALS['TSFE']->initFEuser();
$GLOBALS['TSFE']->checkAlternativeIdMethods();
$GLOBALS['TSFE']->determineId();
$GLOBALS['TSFE']->getCompressedTCarray();
$GLOBALS['TSFE']->initTemplate();
$GLOBALS['TSFE']->getConfigArray();
// Get linkVars, absRefPrefix, etc
TSpagegen::pagegenInit();
}
and in the main() method, call it: $this->initTSFE(); . Then this call:
$conf = $GLOBALS['TSFE']->tmpl->setup['plugin.']['tx_ases_pi1.'];
var_dump($conf);
will output the complete list.
I am not claiming i would really understand it... but since it might save others some trouble, i am posting it anyway.
[EDIT]
Apparently it was this line:
$GLOBALS['TSFE']->checkAlternativeIdMethods();
that made the difference. Removing it would result in the short output shown in the question.
As a side note: these lines:
$GLOBALS['TSFE']->initFEuser();
$GLOBALS['TSFE']->getCompressedTCarray();
TSpagegen::pagegenInit();
do not make a difference for me, so i assume that they can be omitted in my case to speed things up a little.I will leave them in here because they might help someone else in the future.

Symfony 2 - how to parse %parameter% in my own Yaml file loader?

I have a Yaml loader that loads additional config items for a "profile" (where one application can use different profiles, e.g. for different local editions of the same site).
My loader is very simple:
# YamlProfileLoader.php
use Symfony\Component\Config\Loader\FileLoader;
use Symfony\Component\Yaml\Yaml;
class YamlProfileLoader extends FileLoader
{
public function load($resource, $type = null)
{
$configValues = Yaml::parse($resource);
return $configValues;
}
public function supports($resource, $type = null)
{
return is_string($resource) && 'yml' === pathinfo(
$resource,
PATHINFO_EXTENSION
);
}
}
The loader is used more or less like this (simplified a bit, because there is caching too):
$loaderResolver = new LoaderResolver(array(new YamlProfileLoader($locator)));
$delegatingLoader = new DelegatingLoader($loaderResolver);
foreach ($yamlProfileFiles as $yamlProfileFile) {
$profileName = basename($yamlProfileFile, '.yml');
$profiles[$profileName] = $delegatingLoader->load($yamlProfileFile);
}
So is the Yaml file it's parsing:
# profiles/germany.yml
locale: de_DE
hostname: %profiles.germany.host_name%
At the moment, the resulting array contains literally '%profiles.germany.host_name%' for the 'hostname' array key.
So, how can I parse the % parameters to get the actual parameter values?
I've been trawling through the Symfony 2 code and docs (and this SO question and can't find where this is done within the framework itself. I could probably write my own parameter parser - get the parameters from the kernel, search for the %foo% strings and look-up/replace... but if there's a component ready to be used, I prefer to use this.
To give a bit more background, why I can't just include it into the main config.yml: I want to be able to load app/config/profiles/*.yml, where * is the profile name, and I am using my own Loader to accomplish this. If there's a way to wildcard import config files, then that might also work for me.
Note: currently using 2.4 but just about ready to upgrade to 2.5 if that helps.
I've been trawling through the Symfony 2 code and docs (and this SO question and can't find where this is done within the framework itself.
Symfony's dependency injection component uses a compiler pass to resolve parameter references during the optimisation phase.
The Compiler gets the registered compiler passes from its PassConfig instance. This class configures a few compiler passes by default, which includes the ResolveParameterPlaceHoldersPass.
During container compilation, the ResolveParameterPlaceHoldersPass uses the Container's ParameterBag to resolve strings containing %parameters%. The compiler pass then sets that resolved value back into the container.
So, how can I parse the % parameters to get the actual parameter values?
You'd need access to the container in your ProfileLoader (or wherever you see fit). Using the container, you can recursively iterate over your parsed yaml config and pass values to the container's parameter bag to be resolved via the resolveValue() method.
Seems to me like perhaps a cleaner approach would be for you to implement this in your bundle configuration. That way your config will be validated against a defined structure, which can catch configuration errors early. See the docs on bundle configuration for more information (that link is for v2.7, but hopefully will apply to your version also).
I realise this is an old question, but I have spent quite a while figuring this out for my own projects, so I'm posting the answer here for future reference.
I tried a lot of options to resolve %parameter% to parameters.yml but no luck at all. All I can think of is parsing %parameter% and fetch it from container, no innovation yet.
On the other hand I don't have enough information about your environment to see the big picture but I just come up with another idea. It can be quite handy if you declare your profiles in your parameters.yml file and load it as an array in your controller or service via container.
app/config/parameters.yml
parameters:
profiles:
germany:
locale: de_DE
host_name: http://de.example.com
uk:
locale: en_EN
host_name: http://uk.example.com
turkey:
locale: tr_TR
host_name: http://tr.example.com
You can have all your profiles as an array in your controller.
<?php
namespace Acme\DemoBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class DefaultController extends Controller
{
public function indexAction()
{
$profiles = $this->container->getParameter('profiles');
var_dump($profiles);
return $this->render('AcmeDemoBundle:Default:index.html.twig');
}
}
With this approach
you don't have to code a custom YamlLoader
you don't have to worry about importing parameters into other yml files
you can have your profiles as an array anytime you have the $container in your hand
you don't have to load/cache profile files one by one
you don't have to find a wildcard file loading solution
If I got your question correctly, this approach can help you.

Categories