Symfony 5 reusable bundle config TreeBuilder: invalide type for path - bundle

I'm trying to create a reusable bundle and i get Invalid type for path "bayard_directus.authentication.email". Expected "array", but got "string"
the TreeBuilder looks like this:
$treeBuilder = new TreeBuilder('bayard_directus');
$treeBuilder->getRootNode()
->children()
->arrayNode('authentication')
->arrayPrototype()
->children()
->scalarNode('email')->defaultValue("%env(PEE_CMS_API_MAIL)%")->end()
->scalarNode('password')->defaultValue("%env(PEE_CMS_API_PWD)%")->end()
->scalarNode('cache')->defaultValue('PSR-6')->end()
->end()
->end()
->end()
->scalarNode('base_url')->defaultValue("%env(PEE_CMS_API_URL)%")->end()
->end()
->end()
;
return $treeBuilder;
the YML file :
bayard_directus:
authentication:
email: '%env(PEE_CMS_API_MAIL)%'
password: '%env(PEE_CMS_API_PWD)%'
cache: "PSR-6"
base_url: '%env(PEE_CMS_API_URL)%'

the solution :
public function getConfigTreeBuilder()
{
$treeBuilder = new TreeBuilder('bayard_directus');
$rootNode = $treeBuilder->getRootNode();
$rootNode->children()
->scalarNode('base_url')
->isRequired()
->end()
->arrayNode('authentication')
->isRequired()
->scalarPrototype()
->end()
->end()
->end();
return $treeBuilder;
}

Related

Generic TypeScript type referencing nested interface types dynamically

Sorry for the confusing title! I'll try to be as clear as possible here. Given the following interface (generated by openapi-typescript as an API definition):
TypeScript playground to see this in action
export interface paths {
'/v1/some-path/:id': {
get: {
parameters: {
query: {
id: number;
};
header: {};
};
responses: {
/** OK */
200: {
schema: {
id: number;
name: string;
};
};
};
};
post: {
parameters: {
body: {
name: string;
};
header: {};
};
responses: {
/** OK */
200: {
schema: {
id: number;
name: string;
};
};
};
};
};
}
The above interface paths will have many paths identified by a string, each having some methods available which then define the parameters and response type.
I am trying to write a generic apiCall function, such that given a path and a method knows the types of the parameters required, and the return type.
This is what I have so far:
type Path = keyof paths;
type PathMethods<P extends Path> = keyof paths[P];
type RequestParams<P extends Path, M extends PathMethods<P>> =
paths[P][M]['parameters'];
type ResponseType<P extends Path, M extends PathMethods<P>> =
paths[P][M]['responses'][200]['schema'];
export const apiCall = (
path: Path,
method: PathMethods<typeof path>,
params: RequestParams<typeof path, typeof method>
): Promise<ResponseType<typeof path, typeof method>> => {
const url = path;
console.log('params', params);
// method & url are
return fetch(url, { method }) as any;
};
However this won't work properly and I get the following errors:
paths[P][M]['parameters']['path'] -> Type '"parameters"' cannot be used to index type 'paths[P][M]'
Even though it does work (If I do type test = RequestParams<'/v1/some-path/:id', 'get'> then test shows the correct type)
Any idea how to achieve this?
Solution
After few trials, this is the solution I found.
First, I used a conditional type to define RequestParams:
type RequestParams<P extends Path, M extends PathMethods<P>> =
"parameters" extends keyof paths[P][M]
? paths[P][M]["parameters"]
: undefined;
Because typescript deduces the type of path on the fly, the key parameters may not exist so we cant use it. The conditional type checks this specific case.
The same can be done for ResponseType (which will be more verbose) to access the properties typescript is complaining about.
Then, I updated the signature of the function apiCall:
export const apiCall = <P extends Path, M extends PathMethods<P>>(
path: P,
method: M,
params: RequestParams<P, M>
): Promise<ResponseType<P, M>> => {
//...
};
So now the types P and M are tied together.
Bonus
Finally, in case there is no parameter needed, I made the parameter params optional using a conditional type once again:
export const apiCall = <P extends Path, M extends PathMethods<P>>(
path: P,
method: M,
...params: RequestParams<P, M> extends undefined ? []: [RequestParams<P, M>]
): Promise<ResponseType<P, M>> => {
//...
};
Here is a working typescript playground with the solution. I added a method delete with no params just to test the final use case.
Sources
https://www.typescriptlang.org/docs/handbook/2/conditional-types.html
https://stackoverflow.com/a/52318137/6325822
Edit
Here the updated typescript playground with on errors.
Also, I saw that Alessio's solution only works for one path which is a bit limitating. The one I suggest has no errors and works for any number of paths.
I checked Baboo's solution by following the link to his TypeScript playground.
At line 57, the ResponseType type gives the following error:
Type '"responses"' cannot be used to index type 'paths[P][M]'.(2536)
Type '200' cannot be used to index type 'paths[P][M]["responses"]'.(2536)
Type '"schema"' cannot be used to index type 'paths[P][M]["responses"][200]'.(2536)
I did some work starting from that solution, and obtained the functionality required without errors, and using slightly simpler type definitions, which require less type params.
In particular, my PathMethod type does not need any type param, and my RequestParams and ResponseType types need only 1 type param.
Here is a TypeScript playground with the full solution.
As requested in the comments by captain-yossarian, here is the full solution:
export interface paths {
'/v1/some-path/:id': {
get: {
parameters: {
query: {
id: number;
};
header: {};
};
responses: {
/** OK */
200: {
schema: {
id: number;
name: string;
};
};
};
};
post: {
parameters: {
body: {
name: string;
};
header: {};
};
responses: {
/** OK */
200: {
schema: {
id: number;
name: string;
};
};
};
};
delete: {
responses: {
/** OK */
200: {
schema: {
id: number;
name: string;
};
};
};
};
};
}
type Path = keyof paths;
type PathMethod = keyof paths[Path];
type RequestParams<T extends PathMethod> = paths[Path][T] extends {parameters: any} ? paths[Path][T]['parameters'] : undefined;
type ResponseType<T extends PathMethod> = paths[Path][T] extends {responses: {200: {schema: {[x: string]: any}}}} ? keyof paths[Path][T]['responses'][200]['schema'] : undefined;
export const apiCall = <P extends Path, M extends PathMethod>(
path: P,
method: M,
...params: RequestParams<M> extends undefined ? [] : [RequestParams<M>]
): Promise<ResponseType<M>> => {
const url = path;
console.log('params', params);
return fetch(url, { method }) as any;
};
UPDATE:
In the comments, aurbano noted that my solution only worked if paths has only 1 key. Here is an updated solution that works with 2 different paths.
export interface paths {
'/v1/some-path/:id': {
get: {
parameters: {
query: {
id: number;
};
header: {};
};
responses: {
/** OK */
200: {
schema: {
id: number;
name: string;
};
};
};
};
post: {
parameters: {
body: {
name: string;
};
header: {};
};
responses: {
/** OK */
200: {
schema: {
id: number;
name: string;
};
};
};
};
delete: {
responses: {
/** OK */
200: {
schema: {
id: number;
name: string;
};
};
};
};
};
'/v2/some-path/:id': {
patch: {
parameters: {
path: {
id: number;
};
header: {};
};
responses: {
/** OK */
200: {
schema: {
id: number;
name: string;
};
};
};
};
};
}
type Path = keyof paths;
type PathMethod<T extends Path> = keyof paths[T];
type RequestParams<P extends Path, M extends PathMethod<P>> = paths[P][M] extends {parameters: any} ? paths[P][M]['parameters'] : undefined;
type ResponseType<P extends Path, M extends PathMethod<P>> = paths[P][M] extends {responses: {200: {schema: {[x: string]: any}}}} ? keyof paths[P][M]['responses'][200]['schema'] : undefined;
export const apiCall = <P extends Path, M extends PathMethod<P>>(
path: P,
method: M,
...params: RequestParams<P, M> extends undefined ? [] : [RequestParams<P, M>]
): Promise<ResponseType<P, M>> => {
const url = path;
console.log('params', params);
return fetch(url, { method: method as string }) as any;
};
apiCall("/v1/some-path/:id", "get", {
header: {},
query: {
id: 1
}
}); // Passes -> OK
apiCall("/v2/some-path/:id", "get", {
header: {},
query: {
id: 1
}
}); // Type error -> OK
apiCall("/v2/some-path/:id", "patch", {
header: {},
query: {
id: 1
}
}); // Type error -> OK
apiCall("/v2/some-path/:id", "patch", {
header: {},
path: {
id: 1,
}
}); // Passes -> OK
apiCall("/v1/some-path/:id", "get", {
header: {},
query: {
id: 'ee'
}
}); // Type error -> OK
apiCall("/v1/some-path/:id", "get", {
query: {
id: 1
}
}); // Type error -> OK
apiCall("/v1/some-path/:id", "get"); // Type error -> OK
apiCall("/v1/some-path/:id", 'delete'); // Passes -> OK
apiCall("/v1/some-path/:id", "delete", {
header: {},
query: {
id: 1
}
}); // Type error -> OK
And here is an updated playground.

Download file to given absolute path in Firefox using Protractor

Im using Protractor for E2E testing. During automation, I need to download files to C:\Automation folder in my system. But below code is not working.
Note:During automation execution,The Save as popup opens(but i have to disable that in future) and I manually click "Save" option. It saves in default location ie Downloads folder.How do I make it save in my given path.
let profile = require('firefox-profile');
let firefoxProfile = new profile();
//_browser = 'chrome';
_browser = 'firefox';
// _browser = 'internet explorer';
firefoxProfile.setPreference("browser.download.folderList", 2);
firefoxProfile.setPreference('browser.download.dir', "C:\\Automation");
exports.config = {
framework: 'custom',
frameworkPath: require.resolve('protractor-cucumber-framework'),
capabilities: {
'browserName': _browser,
'shardTestFiles': false,
'maxInstances': 1,
'acceptInsecureCerts': true,
'moz:firefoxOptions': {
'profile': firefoxProfile
}},
beforeLaunch: function () {...}
}
It looks like you may just be missing a couple of preferences for it to work with firefox. Try adding these and see if that helps.
profile.setPreference( "browser.download.manager.showWhenStarting", false );
profile.setPreference( "browser.helperApps.neverAsk.saveToDisk",
/* A comma-separated list of MIME types to save to disk without asking goes here */ );
this will save to downloads folder inside your project. You can try to tweak it to save to desired folder. You have to specify which types of files are suppose to be downloaded without prompt. JSON and csv are already there.
var q = require('q');
var path = require('path');
var sh = require("shelljs");
var cwd = sh.pwd().toString();
var FirefoxProfile = require('selenium-webdriver/firefox').Profile;
var makeFirefoxProfile = function(preferenceMap) {
var profile = new FirefoxProfile();
for (var key in preferenceMap) {
profile.setPreference(key, preferenceMap[key]);
}
return q.resolve({
browserName: 'firefox',
marionette: true,
firefox_profile: profile
});
};
exports.config = {
seleniumAddress: 'http://localhost:4444/wd/hub',
framework: 'jasmine2',
getMultiCapabilities: function() {
return q.all([
makeFirefoxProfile(
{
'browser.download.folderList': 2,
'browser.download.dir': (path.join(cwd, 'downloads')).toString(),
'browser.download.manager.showWhenStarting': false,
'browser.helperApps.alwaysAsk.force': false,
'browser.download.manager.useWindow': false,
'browser.helperApps.neverAsk.saveToDisk': 'application/octet-stream, application/json, text/comma-separated-values, text/csv, application/csv, application/excel, application/vnd.ms-excel, application/vnd.msexcel, text/anytext, text/plaintext'
}
)
]);
},
allScriptsTimeout: 1000000,
specs: ['./tmp/**/*.spec.js'],
jasmineNodeOpts: {
defaultTimeoutInterval: 1000000,
showColors: true
},
onPrepare: function() {
browser.driver.getCapabilities().then(function(caps) {
browser.browserName = caps.get('browserName');
});
setTimeout(function() {
browser.driver.executeScript(function() {
return {
width: window.screen.availWidth,
height: window.screen.availHeight
};
}).then(function(result) {
browser.driver.manage().window().setPosition(0,0);
browser.driver.manage().window().setSize(result.width, result.height);
});
});
}
};

SAPUI5/OpenUI5 view not rendered after router navTo

I'm creating SAPUI5 sample app with simple routing (SAPUI5/OpenUI5 v.1.22).
My main problem, which I'm trying to understand, why URL pattern changes and the onInit of target view controller is fired, but after nothing happens (onAfterRendering not fired), and I'm able to go to another page only after page reload.
Routing setup. Compontent.js, where router is initialized, is structured in following way:
sap.ui.define([
"sap/ui/core/UIComponent"
], function (UIComponent) {
return UIComponent.extend("sge.apps.app.Component", {
metadata:{
name : "Sample App",
version : "1.0",
includes : [],
dependencies : {
libs : ["sap.m", "sap.ui.layout"],
components : []
},
rootView: "sge.apps.app.view.App",
config: {
resourceBundle: "i18n/i18n.properties"
},
routing : {
config : {
routerClass : sap.ui.core.routing.Router,
viewType : "XML",
viewPath : "sge.apps.app.view",
targetControl: "app",
targetAggregation: "pages",
transition: "slide",
clearTarget : false,
bypassed: {
target: "notFound"
}
},
routes: [{
pattern: "",
name: "appHome",
view: "Home"
},{
pattern : ":all*:",
name : "catchallDetail",
view : "NotFound",
transition : "show"
},{
pattern: "notFound",
name: "appNotFound",
view: "NotFound",
transition : "show"
}]
}
},
init : function() {
UIComponent.prototype.init.apply(this, arguments);
var mConfig = this.getMetadata().getConfig();
// always use absolute paths relative to our own component
// (relative paths will fail if running in the Fiori Launchpad)
var rootPath = jQuery.sap.getModulePath("sge.apps.app");
// set i18n model
var i18nModel = new sap.ui.model.resource.ResourceModel({
bundleUrl : [rootPath, mConfig.resourceBundle].join("/")
});
this.setModel(i18nModel, "i18n");
// set device model
var deviceModel = new sap.ui.model.json.JSONModel({
isTouch : sap.ui.Device.support.touch,
isNoTouch : !sap.ui.Device.support.touch,
isPhone : sap.ui.Device.system.phone,
isNoPhone : !sap.ui.Device.system.phone,
listMode : sap.ui.Device.system.phone ? "None" : "SingleSelectMaster",
listItemType : sap.ui.Device.system.phone ? "Active" : "Inactive"
});
deviceModel.setDefaultBindingMode("OneWay");
this.setModel(deviceModel, "device");
this.getRouter().initialize();
}
});
});
I have Home.controller.js of the Home.view.xml from where I try to navigate to another view, by pressing the button with event onDisplayNotFound:
sap.ui.define([
"sge/apps/app/controller/BaseController"
], function (BaseController) {
"use strict";
return BaseController.extend("sge.apps.app.controller.Home", {
onDisplayNotFound : function (oEvent) {
this.getRouter().navTo("appNotFound");
}
});
});
BaseController.js
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/ui/core/routing/History"
], function (Controller, History) {
"use strict";
return Controller.extend("sge.apps.app.controller.BaseController", {
getRouter: function () {
return sap.ui.core.UIComponent.getRouterFor(this);
},
onNavBack: function (oEvent) {
var oHistory, sPreviousHash;
oHistory = History.getInstance();
sPreviousHash = oHistory.getPreviousHash();
if(sPreviousHash !== undefined) {
window.history.go(-1);
} else {
this.getRouter().navTo("appHome", {}, true /*no history*/);
}
}
});
});
NotFound.controller.js of target view NotFound.view.xml:
sap.ui.define([
"sge/apps/app/controller/BaseController"
], function (BaseController) {
"use strict";
return BaseController.extend("sge.apps.app.controller.NotFound", {
onInit: function () {
console.log("onInit NotFound.view.xml");
},
onAfterRendering: function () {
console.log("onAfterRendering NotFound.view.xml");
}
});
});
I had the same problem and I solved by adding this line in the configuration of the routing :
"routerClass" : "sap.m.routing.Router",
And it has worked perfectly navigation.
"routing": {
"config": {
"routerClass" : "sap.m.routing.Router",
"viewPath": "es.seidor.view",
"controlId": "App",
"async" : "true",
"clearTarget" : "true"
}
sap.ui.define is a feature of UI5 v1.30
Either update the version you're using to 1.30.x or remove the sap.ui.define code and replace it with code that works with the earlier versions.
Pre-sap.ui.define code looks something like this:
jQuery.sap.require("sap.m.Button");
//use jQuery.sap.require to require any controls or other files
sap.ui.controller("my.controller", {
onInit: function(){
//your code here
//doing something with sap.m.Button, won't work without the require
//var oBtn = new sap.m.Button("myBtn", {text: "Click me"});
},
onAfterRendering: function(){
//more code
}
});
Try that.
The solution is simple, just use some part of TDG best practice:
Create the file MyRouter.js
sap.ui.define([
"sap/ui/core/routing/Router",
"sap/m/routing/RouteMatchedHandler"
], function (Router, RouteMatchedHandler) {
"use strict";
return Router.extend("sge.apps.notespese.MyRouter", {
constructor : function() {
sap.ui.core.routing.Router.apply(this, arguments);
this._oRouteMatchedHandler = new sap.m.routing.RouteMatchedHandler(this);
},
destroy : function() {
sap.ui.core.routing.Router.prototype.destroy.apply(this, arguments);
this._oRouteMatchedHandler.destroy();
}
});
});
Inject it in your Component.js, as follows:
sap.ui.define([
"sap/ui/core/UIComponent",
"sge/apps/notespese/MyRouter"
], function (UIComponent, MyRouter) {
"use strict";
return UIComponent.extend("sge.apps.notespese.Component", {
...
In Component metadata section replace
routing : {
config : {
routerClass : sap.ui.core.routing.Router,
with
routing : {
config : {
routerClass : sge.apps.notespese.MyRouter,
Hope to do not forget other this question related things.

trying to create a simple plugin with ckeditor

I have created plugins before but i must be missing something simple.
I have gone back to the tutorial and trying to add a very simple plugin. code below:
I do not get an error on the console and i do not see the icon in the editor
Any help would be greatly appreciated
In my PHP i have
CKEDITOR.replace( 'editor1',
{
toolbar : 'MyToolbar',
customConfig : '/admin/ckeditor/my_config.js?v=5.1',
height : '400px',
});
my_config.js looks like ::
CKEDITOR.editorConfig = function( config ){
config.enterMode = CKEDITOR.ENTER_BR;
fullPage : true;
extraPlugins : 'pdf';
config.toolbar = 'MyToolbar';
config.toolbar_MyToolbar =
[
{ name: 'document', items : [ 'Source' ] },
{ name: 'tools', items : [ 'Maximize', 'ShowBlocks','-','About','Pdf' ] }
];
};
and the dir structure in the plugins dir is:
ckeditor
-->plugins
------>pdf
--------->images
------------>pdf.png
--------->plugin.js
and plugin.js looks like:
CKEDITOR.plugins.add( 'pdf',
{
init: function( editor )
{
editor.addCommand( 'insertPdf',
{
exec : function( editor )
{
var timestamp = new Date();
editor.insertHtml( 'The current date and time is: <em>' + timestamp.toString() + '</em>' );
}
});
editor.ui.addButton( 'Pdf',
{
label: 'Insert PDF',
command: 'insertPdf',
icon: this.path + 'images/pdf.png'
} );
}
} );
found the problem.
in the my_config.js i had
extraPlugins : 'pdf';
instead of
config.extraPlugins = 'pdfs';

Using Backbone-relational with CoffeeScript

I'm trying to use a Backbone-relational and CoffeeScript in a project.The following is an example in CoffeeScript of the type of things I'm trying to model :
class NestedModel extends Backbone.RelationalModel
defaults:
Description: 'A nested model'
NestedModel.setup()
class MainModel extends Backbone.RelationalModel
defaults:
Description: 'A MainModel description'
StartDate: null
relations: [
type: Backbone.HasOne
key: 'nestedmodel'
relatedModel: 'NestedModel'
includeInJSON: '_id'
reverseRelation:
type: Backbone.HasOne
includeInJSON: '_id'
key: 'mainmodel'
]
MainModel.setup()
nm = new NestedModel()
mm = new MainModel(nestedmodel: nm)
console.log mm.get("nestedmodel").get("mainmodel").get("Description")
return
That CoffeeScript produces the following JavaScript:
var MainModel, NestedModel, mm, nm;
var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) {
for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; }
function ctor() { this.constructor = child; }
ctor.prototype = parent.prototype;
child.prototype = new ctor;
child.__super__ = parent.prototype;
return child;
};
NestedModel = (function() {
__extends(NestedModel, Backbone.RelationalModel);
function NestedModel() {
NestedModel.__super__.constructor.apply(this, arguments);
}
NestedModel.prototype.defaults = {
Description: 'A nested model'
};
return NestedModel;
})();
NestedModel.setup();
MainModel = (function() {
__extends(MainModel, Backbone.RelationalModel);
function MainModel() {
MainModel.__super__.constructor.apply(this, arguments);
}
MainModel.prototype.defaults = {
Description: 'A MainModel description',
StartDate: null
};
MainModel.prototype.relations = [
{
type: Backbone.HasOne,
key: 'nestedmodel',
relatedModel: 'NestedModel',
includeInJSON: '_id',
reverseRelation: {
type: Backbone.HasOne,
includeInJSON: '_id',
key: 'mainmodel'
}
}
];
return MainModel;
})();
MainModel.setup();
nm = new NestedModel();
mm = new MainModel({
nestedmodel: nm
});
console.log(mm.get("nestedmodel").get("mainmodel").get("Description"));
return;
Which produces the following warning and error
Warning:
Relation= child
; no model, key or relatedModel (function MainModel() {
MainModel.__super__.constructor.apply(this, arguments);
}, "nestedmodel", undefined)
Error:
Uncaught TypeError: Cannot call method 'get' of undefined
Simply removing the 'NestedModel' variable from the 1st line of generated JavaScript
var MainModel, NestedModel, mm, nm;
Causes the correct behaviour. Obviously I can't keep removing the variable definition from the generated JavaScript. What am I doing wrong?
Ok, it appears to be a scoping issue. See the following jsFiddle example. But why can't I just refer to the classes in the local function scope?
But why can't I just refer to the classes in the local function scope?
Classes are implemented as Immediately Invoked Function Expressions
The key to understanding design patterns such as immediately-invoked function expressions is to realize JavaScript has function scope (but not block scope) and passes values by reference inside a closure.
References
Immediately Invoked Function Expressions

Resources