Defining integer parameter in parameters.yml in Symfony - validation

I have a numeric parameter in the symfony's parameters.yml file and I have a configuration class in my bundle where I validate that the parameter is an integer, but I get the following error in the validation proccess:
[Symfony\Component\Config\Definition\Exception\InvalidTypeException]
Invalid type for path "page_size". Expected int, but got string.
Is there a way to specify the parameter type in the parameters.yml file?
EDIT
parameters.yml:
parameters:
...
list_page_size: 15
...
config.yml:
...
example:
page_size: %list_page_size%
...
Configuration.php:
class Configuration implements ConfigurationInterface
{
/**
* {#inheritDoc}
*/
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder();
$rootNode = $treeBuilder->root('example');
$rootNode->children()
// Some code here
->integerNode('page_size')
->defaultValue(15)
->end()
// More code here
->end();
return $treeBuilder;
}
}
EDIT 2
I have discovered that the exception is thrown in the prepend() method in the bundle extension file (DependencyInjection/ExampleExtension.php), but the error is not thrown in the load method of the same file. It seems like if the parameters.yml file was not loaded at prepend method execution time.

The exception is simply because the is_int check fails.
A simple solution would be to add a normalization step like:
// Some code here
->integerNode('page_size')
->beforeNormalization()
->ifString()
->then(function ($val) { return strval(intval($val)) === $val ? intval($val) : $val; })
->end()
->defaultValue(15)
->end()
// More code here
Also take a look at http://symfony.com/doc/current/components/config/definition.html#normalization

If you entered something like :
page_size: "10"
Then it's a normal behavior.
You should not use " :
page_size: 10

Related

Understanding static methods inside the Validation class in angular4

i am starting to learn model driven forms in angular and when i was going through the documentation of the model driven forms i found this
component.ts
this.myForm= this.fb.group({
'contact':['',Validators.required]
});
now when i went to the definition of the validator class i found this
export declare class Validators {
...
static required(control: AbstractControl): ValidationErrors | null;
...
}
which explains required is a static method in the validator class and it requires a AbstractControl as a parameter. but why then i am allowed to use it without passing any parameter inside it.
The required method returns an error map with the 'required' property: {'required':true} if the value of control: AbstractControl is empty and null if its not.
.
From the angular source code: https://github.com/angular/angular/blob/6.1.9/packages/forms/src/validators.ts#L133-L154
static required(control: AbstractControl): ValidationErrors|null {
return isEmptyInputValue(control.value) ? {'required': true} : null;
}
.
The reason why you can pass Validators.required without parenthesis and parameters is because Typescript is a superset of Javascript, which can store functions as variables:
var Foo = function (control: AbstractControl)
{
return anyVal;
};
Is the same as:
Foo(control: AbstractControl): any
{
return anyVal;
};
So doing this is completely valid
var Bar = Foo;
And because a function is just a variable holding executable code, we can store the function in multiple variables or pass it as a parameter, which is what is done in the FormControl.
So basically, when you do
const control = new FormControl('', Validators.required);
You are not executing the required method, because a method is executed only when parenthesis and parameters are added. Instead, you are passing the Validator function itself.

Symfony custom filename for uploaded image files

I'm aware of VichUpload and namers, but I have to face two different file uploads, and I need special naming conventions that I'm unable to get with the current VichUpload documentation.
First file upload is for an entity called "School", which manages every single school, and its logo. So, taking as web root '../web/files/schools/', then I'd take the school_id and then the 'logo' folder with the uploaded file name, so it could be '../web/files/schools/000001/logo.png'.
Then, the second entity is 'students' to store the photo, with a school_id foreign key from School entity. So, the resulting file name would depend on the school id, and the student id, being the root for student '../web/files/schools/<school_id>/students/<student_id>.[jpg|png]', having student_id a six zero padding on the left.
This is the section in config.yml about this (updated info):
school_image:
upload_destination: "../web/files/images/schools"
uri_prefix: "/files/images/schools"
directory_namer:
service: vich_uploader.directory_namer_subdir
options: { property: 'schoolDirectory', chars_per_dir: 6, dirs: 1 }
namer:
service: vich_uploader.namer_property
options: { property: 'idSlug' }
inject_on_load: true
delete_on_update: true
delete_on_remove: true
student_image:
upload_destination: "../web/files/images/schools"
uri_prefix: "/files/images/schools"
directory_namer:
service: vich_uploader.directory_namer_subdir
options: { property: 'userDirectory', chars_per_dir: 6, dirs: 1 }
namer:
service: vich_uploader.namer_property
options: { property: 'userImage'}
inject_on_load: true
delete_on_update: true
delete_on_remove: true
in school entity (might work with a dirty workaround):
/**
* NOTE: This is not a mapped field of entity metadata, just a simple property.
*
* #Vich\UploadableField(mapping="school_image", fileNameProperty="imageName")
*
* #Assert\Image()
*
* #var File
*/
private $imageFile;
public function getIdSlug()
{
$slug = sprintf("%06d", $this->getId());
return $slug;
}
public function getSchoolDirectory()
{
$slug = sprintf("%06d", $this->getId());
return $slug;
}
in student entity (not working, as explained below):
/**
* NOTE: This is not a mapped field of entity metadata, just a simple property.
*
* #Vich\UploadableField(mapping="student_image", fileNameProperty="imageName")
*
* #Assert\Image()
*
* #var File
*/
private $imageFile;
public function getUserDirectory()
{
$schoolDir = $this->getSchool()->getSchoolDirectory();
$dir = $schoolDir.'/students/'.sprintf("%06d", $this->getId());
return $dir;
}
public function getUserImage()
{
return $this->getUsername() . $this->getImageFile()->getExtension();
}
This setup with both "namer" and "directory_namer" seems to ignore the paths in directory_namer and use a path "<namer>/<namer>.ext" instead of "<directory_namer>/<namer>.ext". If I change the getUserImage() function and prepend the result of getUserDirectory() (i.e., the db stores "<directory_namer>/<namer>.ext" instead of just "<namer>.ext"), the "<directory_namer>" path is ignored and just "<namer>.ext" is created.
Since upload_prefix and uri_destination don't seem to handle variable data, how can I setup a namer or whatever to get this path for both cases?
BTW, I'm using Symfony 3.1 and composer hasn't allowed to update vich beyond "1.7.x-dev", according to bundled composer.json in vendor folder. If you find that it should work with this setup and the possible solution is to upgrade, I would thank to be pointed to the specific files which fix the problem, so I can manually paste whatever is wrong.
SOLUTION:
The problem was that, due to the "old" version, there was a missing directory_namer class, PropertyDirectoryNamer, with vich_uploader.namer_directory_property servicename, instead of vich_uploader.directory_namer_subdir, which was a wrong class for this purpose. I copied this class and registered it in the namer.xml file, and then I got the expected results. Now I'm going to try to mark this as solved.
You need to create custom directory namer class that implements
Vich\UploaderBundle\Naming\DirectoryNamerInterface
For example:
namespace App\Services;
use Vich\UploaderBundle\Mapping\PropertyMapping;
class SchoolDirectoryNamer implements DirectoryNamerInterface
{
public function directoryName($object, PropertyMapping $mapping): string
{
// do what you want with $object and $mapping
$dir = '';
return $dir;
}
}
Then, make it as a service.
services:
# ...
app.directory_namer.school:
class: App\Services\SchoolDirectoryNamer
Last step is config vich_uploader
vich_uploader:
# ...
mappings:
school:
upload_destination: school_image
directory_namer: app.directory_namer.school
student:
upload_destination: student_image
directory_namer: app.directory_namer.student
source : VichUploaderBundle - How To Create a Custom Directory Namer

Magento2: Argument 1 [...] must be an instance of Magento\Framework\App\Helper\Context

First of all, I'm quite new to Magento 2, but I've used Magento 1.x for some time.
I've read a lot about how to solve DI-related problems, but I'm stuck on this one:
Exception #0 (Exception): Recoverable Error: Argument 1 passed to Cefar\AO\Helper\Ao::__construct() must be an instance of Magento\Framework\App\Helper\Context, instance of Magento\Framework\ObjectManager\ObjectManager given, called in .../vendor/magento/framework/ObjectManager/Factory/AbstractFactory.php on line 93 and defined in .../Cefar/AO/Helper/Ao.php on line 11
Many other answers have suggested deleting the var/di and var/generation folders, sometimes var/cache also. While this solves the problem, it occurs again once bin/magento setup:di:compile is run, which means the code cannot be used in a production environment.
I've checked that the Ao class does not instantiate any objects. It also doesn't try to re-make any objects that could be provided by the context given. Here's the code:
namespace Cefar\AO\Helper;
class Ao extends \Magento\Framework\App\Helper\AbstractHelper
{
const DEFAULT_GRID_COLS = 4;
protected $_session;
public function __construct(
\Magento\Framework\App\Helper\Context $context,
\Magento\Customer\Model\Session $session
)
{
parent::__construct($context);
$this->_session = $session;
}
public function getConfig($path)
{
return $this->scopeConfig->getValue($path);
}
public function isActive($url = null, $print = true) {
$active = ($url && strstr($_SERVER['REQUEST_URI'], $url) !== false);
if ($active && $print) {
echo "active";
} else {
return $active;
}
}
public function isLoggedIn()
{
return $this->_session->isLoggedIn();
}
public function limitWords($text = '', $limit = 10, $showDots = true)
{
$words = explode(' ', $text);
$limited = array_slice($words, 0, $limit);
$newText = implode(' ', $limited);
if (count($words) > $limit && $showDots) {
$newText .= '...';
}
return $newText;
}
public function getCurrentGrid()
{
return ($this->_getRequest()->getParam('grid'))
? $this->_getRequest()->getParam('grid')
: self::DEFAULT_GRID_COLS;
}
}
There's nothing particularly special here. I'm confused as to how this is even happening; every other defined class in the extension is getting its DI parameters correctly. Why is the ObjectManager apparatus providing an unwanted argument? The relevant call is given in the error report as:
.../vendor/magento/framework/ObjectManager/Factory/AbstractFactory.php(93): Cefar\AO\Helper\Ao->__construct(Object(Magento\Framework\ObjectManager\ObjectManager))
So it isn't even providing two arguments!
I've also read about providing type hints in a di.xml, but it doesn't seem to be relevant here as both types are part of the Magento libraries? I note that there is an entry for Magento\Framework\App\Helper\Context but not one for Magento\Customer\Model\Session... but that there are framework classes that use ID to import Magento\Customer\Model\Session already which work.
Long story short, this was because of a typo.
Sometimes when the helper was being included, it was being referred to as Cefar\AO\Helper\Ao, and other times, Cefar\AO\Helper\AO. Essentially, the ObjectManager was resolving both of these references to the same class, but it only had type hints for one of the names so it didn't know what to provide to the incorrect one.
A little help would have been nice, Magento! Maybe an error report that the requested class wasn't found? Still, at least this is finally over with.

Doctrine is converting string to hex value

I am converting one database to another database through a script.
While running the script I am getting the following error:
SQLSTATE[HY000]: General error: 1366 Incorrect string value: '\xBFbangl...' for column 'country' at row 1
An exception is thrown where it shows that it tries to set country as "\xcf\xbb\xbf\x62\x61\x6e\x67\x6c\x61\x64\x65\x73\x68".
When I var_dump the value, it just shows as "Bangladesh".
Is there something I have to adjust in my php code?
I have tried this, but phpmyadmin is throwing an #1064 error stating a syntax error in the query.
UPDATE
The scripts is a command in Symfony3:
<?php
namespace My\Bundle\Command;
use My\AccommodationBundle\Entity\Visit;
use My\NewAccommodationBundleV2\Entity\Visit as Visit2;
use My\OtherBundle\Entity\Person;
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;
class VisitConvertToNewFormatCommand extends ContainerAwareCommand
{
private $em;
protected function configure()
{
$this
->setName('accommodation:visit:convert')
->setDescription("Converting old structure to new structure'")
;
}
protected function getTimeStamp() {
$currentTime = new \DateTime('now');
return '['.$currentTime->format('Y-m-d H:i:s').'] ';
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$output->writeln($this->getTimeStamp().$this->getDescription());
$this->em = $this->getContainer()->get('doctrine')->getManager();
$visits = $this->em->getRepository('MyOldAccommodationBundle:Visit')->findAll();
foreach ($visits as $visit) {
$visit2 = new Visit2();
$visit2->setArrivalDate($visit->getArrivalDate());
$visit2->setArrivalStatus($visit->getArrivalStatus());
$visit2->setArrivalTime($visit->getArrivalTime());
$visit2->setBookingType($visit->getBookingType());
$visit2->setCountry($visit->getCountry()); // <----
...
$this->em->persist($visit2);
$user = $visit->getUser();
if ($user != null) {
$person = new Person();
...
$person->setFirstName(trim(ucfirst(strtolower($user->getFirstName()))));
$person->setLastName(trim(ucfirst(strtolower($user->getLastName()))));
$person->setEmail(preg_replace('/\s+/', '', $user->getEmail()));
...
$this->em->persist($person);
}
}
}
}
\x62\x61\x6e\x67\x6c\x61\x64\x65\x73\x68 is Bangladesh; the \xcf\xbb\xbf before it makes no sense. In latin1 it is Ï»¿. It does not validly convert to utf8. CFBB is ϻ (GREEK SMALL LETTER SAN), but then bf is broken utf8.
I suggest it is coming from the source of the data.
var_dump probably showed nothing because of what device you were displaying it on.
Ok after days of trying this did the trick:
$country = iconv("UTF-8", "ISO-8859-1//IGNORE", $country);
I have no idea why it went wrong in the first place. if anybody knows, please share.

Passing parameters to i18n model within XML view

How can we pass parameters to the i18n model from within a XML view?
Without parameters
<Label text="{i18n>myKey}"/>
works but how can we pass a parameter in that expression?
The only piece of information I've found so far is http://scn.sap.com/thread/3586754. I really hope that this is not the proper way to do it since this looks more like a (ugly) hack to me.
The trick is to use the formatter jQuery.sap.formatMessage like this
<Label text="{parts:['i18n>myKey', 'someModel>/someProperty'],
formatter: 'jQuery.sap.formatMessage'}"/>
This will take the value /someProperty in the model someModel and just stick it in myKey of your i18n resource bundle.
Edit 2020-05-19:
jQuery.sap.formatMessage is deprecated as of UI5 version 1.58. Please use sap/base/strings/formatMessage. See this answer on usage instructions.
At the moment this is not possible. But you can use this simple workaround, that works for me.
Preparations
First of all we create a general i18n handler in our Component.js. We also create a JSONModel with a simple modification, so that immediatly the requested path is returned.
sap.ui.define([
"sap/ui/core/UIComponent",
"sap/ui/model/json/JSONModel"
], function(UIComponent, JSONModel) {
"use strict";
return UIComponent.extend("your namespace", {
/**
* Add a simple "StringReturnModel" to the components' models
*/
init: function() {
// [...] your other code in the init method
// String Return Model
var stringModel = new JSONModel({});
stringModel.getProperty = function(sPath) {
return sPath;
};
this.setModel(stringModel, "string");
},
/**
* Reads out a string from our text domain.
* The model i18n is defined in your manifest.json
*
* #param param text parameter
* #param arr array for parameters
* #return string
*/
i18n: function(param, arr) {
var oBundle = this.getModel("i18n").getResourceBundle();
return oBundle.getText(param, arr);
},
});
});
Now, a model with the context {string>} exists. To use the i18n function in the XML view, we create a formatter function. This function parses the parts of the binding and returns the localized string.
sap.ui.define([
], function() {
"use strict";
var formatter = {
/**
* First argument must be the property key. The other
* one are the parameters. If there are no parameters, the
* function returns an empty string.
*
* #return string The localized text
*/
i18n: function() {
var args = [].slice.call(arguments);
if (args.length > 1) {
var key = args.shift();
// Get the component and execute the i18n function
return this.getOwnerComponent().i18n(key, args);
}
return "";
}
};
return formatter;
});
How To Use:
Together with the string-model you can use the formatter to pass paramaters to your i18n:
<Text text="{ parts: ['string>yourProperty', 'string/yourFirstParamter', 'anotherModel/yourSecondParamter'], formatter: '.model.formatter.i18n' }" />
You can pass how many paramaters as you want, but be sure that the first "part" is the property key.
What is written at the link is correct for complex formatting case.
But if you want to combine two strings you can just write
<Label text="{i18n>myKey} Whatever"/>
or
<Label text="{i18n>myKey1} {i18n>myKey2}"/>
create file formatter.js
sap.ui.define([
"sap/base/strings/formatMessage"
], function (formatMessage) {
"use strict";
return {
formatMessage: formatMessage
};
});
View
<headerContent>
<m:MessageStrip
text="{
parts: [
'i18n>systemSettingsLastLoginTitle',
'view>/currentUser',
'view>/lastLogin'
],
formatter: '.formatter.formatMessage'
}"
type="Information"
showIcon="true">
</m:MessageStrip>
</headerContent>
Controller
var oBundle = this.getModel("i18n").getResourceBundle();
MessageToast.show(this.formatter.formatMessage(oBundle.getText("systemSettingsLastLoginTitle"), "sInfo1", "sInfo2"));
i18n
systemSettingsLastLoginTitle=You are logged in as: {0}\nLast Login: {1}
As ugly as it may seem, the answer given in the link that you mentioned is the way to go. However it may seem complicated(read ugly), so let's break it down..
Hence, you can use the following for passing a single parameter,
<Label text="{path: 'someParameter', formatter: '.myOwnFormatter'}"/>
Here, the someParameter is a binding of a OData model attribute that has been bound to the whole page/control, as it is obvious that you wouldn't bind a "hardcoded" value in a productive scenario. However it does end with this, as you see there isn't a place for your i18n text. This is taken care in the controller.js
In your controller, add a controller method with the same formatter name,
myOwnFormatter : function(someParameter)
{
/* the 'someParameter' will be received in this function */
var i18n = this.i18nModel; /* However you can access the i18n model here*/
var sCompleteText = someParameter + " " + i18n.getText("myKey")
/* Concatenate the way you need */
}
For passing multiple parameters,
Use,
<Label text="{parts:[{path : 'parameter1'}, {path :'parameter2'}], formatter : '.myOwnFormatter'}" />
And in your controller, receive these parameters,
myOwnFormatter : function(parameter1, parameter2) { } /* and so on.. */
When all this is done, the label's text would be displayed with the parameter and your i18n text.
In principle it is exactly as described in the above mentioned SCN-Link. You need a binding to the key of the resource bundle, and additional bindings to the values which should go into the parameters of the corresponding text. Finally all values found by these bindings must be somehow combined, for which you need to specify a formatter.
It can be a bit shortened, by omitting the path-prefix inside the array of bindings. Using the example from SCN, it also works as follows:
<Text text="{parts: ['i18n>PEC_to',
'promoprocsteps>RetailPromotionSalesFromDate_E',
'promoprocsteps>RetailPromotionSalesToDate_E'}],
formatter: 'retail.promn.promotioncockpit.utils.Formatter.formatDatesString'}"/>
Under the assumption, that you are using {0},{1} etc. as placeholders, a formatting function could look like the following (without any error handling and without special handling of Dates, as may be necessary in the SCN example):
formatTextWithParams : function(textWithPlaceholders, any_placeholders /*Just as marker*/) {
var finalText = textWithPlaceholders;
for (var i = 1; i < arguments.length; i++) {
var argument = arguments[i];
var placeholder = '{' + (i - 1) + '}';
finalText = finalText.replace(placeholder, arguments[i]);
}
return finalText;
},

Resources