Salut tout le monde !
Suite au tutoriel de Grafikart sur le conteneur d'injection de dépendances, je me suis penché sur le problème de réflexivité en JavaScript. Après de courtes recherches, on trouve que celle-ci n'existe tout simplement pas. Pour programmer mon DIC, je me suis donc mis en tête de programmer moi même la réflexivité du langage JavaScript. ça n'a pas été une partie de plaisir mais laissez moi vous présenter le résultat du script, fortement inspiré du code du langage PHP qui pourra vous être fortemment utile.
Si l'on prend la classe suivante :
function test(param1, param2)
{
const param = 10;
const para2 = param1;
var name = 10;
var nam2 = param2;
this.propery = "value";
this.proper2 = "value";
this.method = function(anotherParam)
{
};
}
On peut avec mon script utiliser la réflexivité comme suit :
var reflected = new ReflectionClass("test");
reflected.getConstant("param"); // Return 10
reflected.getConstant("para2"); // Return false and throw a warning in browser's console
reflected.getConstants(); // Return { param1 : 10, param2 : "DEPENDS_OF_CODE" }
reflected.getDefaultProperties(); // Return {name: 10, propery: ""value"", proper2: ""value"", param: 10 }
reflected.getMethod("method"); // Return function (anotherParam) { }
reflected.getMethods(); // Return ["method"]
reflected.getName(); // Return 'test'
reflected.hasConstant("param"); // Return true
reflected.hasMethod("method"); // Return true
reflected.isInstantiable(); // Return true
var t = reflected.newInstanceArgs(['a', 2]); // Return a new Instance of test, param1 = 'a' and param2 = 2
Voici le script. Il n'est pas exhaustif (il doit me manquer une méthode pour accéder aux attributs et gérer la réflexivité des classes parentes) mais il permet déja de vous pencher sur le problème d'injecteurs de dépendance :
/**
* ReflectionClass give informations about a class
* @class ReflectionClass
* @param className {String} the class' name
* @constructor
**/
var ReflectionClass = (function(className)
{
/**
* The class' definition
* @properyy classScope
* @default window[className]
* @type {Function}
**/
this.classScope = window[className];
/**
* The class' definition String
* @property classString
* @default this.classScope.toString()
* @type {String}
**/
this.classString = this.classScope.toString();
/**
* The class' constants
* @property classConstants
* @default empty object
* @type {Object}
**/
this.classConstants = {};
/**
* The class' default properties
* @property classDefaultProperties
* @default empty object
* @type {Object}
**/
this.classDefaultProperties = {};
/**
* If a property value can't be evaluated outside code context
* @property DEPENDS_OF_CODE
* @constant
* @default "DEPENDS_OF_CODE"
* @type {String}
**/
const DEPENDS_OF_CODE = "DEPENDS_OF_CODE";
/**
* Get the value of a gived constant. If value can't be find (depends of code context) warning will be throw in browser's console and false returned
* @property getConstant
* @param name {String} the constant's name
* @return value {Mixed}
**/
this.getConstant = function(name)
{
if(this.classConstants[name] == "DEPENDS_OF_CODE")
{
console.warn("Can't get constant's value because it depends of code context");
return false;
}
else
return this.classConstants[name];
};
/**
* Get all constants value. If constant's value can't be find, value will be set to "DEPENDS_OF_CODE"
* @property getConstants
* @return {Object}
**/
this.getConstants = function()
{
return this.classConstants;
};
/**
* getter to classDefaultProperties, see _parseDefaultProperties method
* @property getDefaultProperties
* @return {Object}
**/
this.getDefaultProperties = function()
{
return this.classDefaultProperties;
};
/**
* get a class' method
* @param name {String} method's name
* @property getMethod
* @return {Object}
**/
this.getMethod = function(name)
{
return new this.classScope()[name];
};
/**
* get a class' name
* @property getName
* @return {String}
**/
this.getName = function()
{
var obj = new this.classScope();
return obj.constructor.name;
};
/**
* get a class' methods
* @property getMethods
* @return {Array}
**/
this.getMethods = function()
{
var obj = new this.classScope();
var methods = [];
for(var method in obj)
if (typeof obj[method] == "function")
methods.push(method)
return methods;
};
/**
* Get all default properties (ie. which are explictelly defined)
* @property getDefaultProperties
* @private
* @return {Object}
**/
this._parseDefaultProperties = function()
{
var result = {};
var string = this.classString;
var indices = this._getIndicesOf("var", string, true);
// parse all default private attributes
for(var j = 0; j < indices.length; j++)
{
var indice = indices[j];
var str = "";
for(var i = 0; i < 10E4242; i++)
{
if(string[indice + i] == ";")
break;
str += string[indice + i];
}
var tmp = str.slice(4).split("=");
for(var i = 0; i < tmp.length; i++) {
tmp[i] = tmp[i].trim(" ");
}
try {
result[tmp[0]] = eval(tmp[1]);
result[tmp[0]].visibility = "private";
}
catch(e) {}
}
// Parse all default public attributes
var indices = this._getIndicesOf("this.", string, true);
for(var j = 0; j < indices.length; j++)
{
var indice = indices[j];
var str = "";
for(var i = 0; i < 10E4242; i++)
{
if(string[indice + i] == ";")
break;
str += string[indice + i];
}
var isFunction = this._getIndicesOf("function", str, false);
if(isFunction.length == 0)
{
var res = str.slice(5).split('=');
for(var k = 0; k < res.length; k++)
res[k] = res[k].trim(" ");
try {
result[res[0]] = res[1];
}
catch(e) {}
}
}
// Parse all default constants value
var consts = this.getConstants();
for(var key in consts)
if(consts[key] !== "DEPENDS_OF_CODE")
result[key] = consts[key];
return result;
};
/**
* Know if a class has a gived constant
* @method hasConstant
* @property name {String} constant's name
* @return boolean
**/
this.hasConstant = function(name)
{
var constants = this.getConstants();
return constants[name] ? true : false;
};
/**
* Know if a class has a gived method
* @method hasMethod
* @propery name {String} method's name
* @return boolean
**/
this.hasMethod = function(name)
{
var methods = this.getMethods();
return methods.indexOf(name) >= 0 ? true : false;
};
/**
* Get all indices of a gived string in an another gived string
* @method _getIndicesOf
* @param searchStr {String} string which will be find
* @param str {String} string in which we will search
* @param caseSensitive {Boolean} if case must be sensitive or not
* @private
* @return {Array}
**/
this._getIndicesOf = function(searchStr, str, caseSensitive) {
var startIndex = 0, searchStrLen = searchStr.length;
var index, indices = [];
if (!caseSensitive) {
str = str.toLowerCase();
searchStr = searchStr.toLowerCase();
}
while ((index = str.indexOf(searchStr, startIndex)) > -1) {
indices.push(index);
startIndex = index + searchStrLen;
}
return indices;
};
/**
* To know if you can create an instance of a gived class
* @method isInstantiable
* @return boolean
**/
this.isInstantiable = function()
{
return window[this.getName()] ? true : false;
};
/**
* Instanciate a new
*
*
**/
this.newInstanceArgs = function(arr)
{
var parameters = this.classString.split("(")[1].split(")")[0].split(",");
for(var i = 0; i < parameters.length; i++)
parameters[i] = parameters[i].trim(" ");
for(var i = 0; i < arr.length; i++)
if(typeof arr[i] == "string")
arr[i] = "'" + arr[i] + "'";
var obj = eval("new this.classScope(" + arr.join() + ")");
return obj;
};
/**
* Parse all code constants between 'const' and ';' characters
* eg const variable = value; will be parsed as { variable : value }
* @method _parseConstants
* @private
* @return {Object}
**/
this._parseConstants = function()
{
var string = this.classString;
var indices = this._getIndicesOf("const", string, true);
var constants = {};
for(var j = 0; j < indices.length; j++)
{
var indice = indices[j];
var str = "";
for(var i = 0; i < 10E4242; i++)
{
if(string[indice + i] == ";")
break;
str += string[indice + i]
}
var result = str.slice(6).split("=");
for(var i = 0; i < result.length; i++)
result[i] = result[i].trim(" ");
try {
constants[result[0]] = eval(result[1]);
}
catch(e) {
constants[result[0]] = DEPENDS_OF_CODE;
}
}
return constants;
};
this.classConstants = this._parseConstants();
this.classDefaultProperties = this._parseDefaultProperties();
return this;
});
J'espère que ce script vous sera utile !
Justement cette classe va me permettre de retravailler mon DIC en JS pour m'affranchir des limitations du script. Le but c'est vraiment d'avoir un truc avec 0 contraintes et très facile à utiliser. Une fois que ce sera bien factorisé je mettrai sur github pour partager à tout le monde.