Should all dependencies be loaded on every request? - php

Hello I am making a PHP application using Silex and I want to use Dependency Injection. For Dependency Injection I need to load the dependencies into the container. I have one file that contains all definitions of what to load into the container called di.php it looks like this.
/**
* DI.php
*/
$app['db.connection'] = function() use($app) {
return new Connection([
'dbname' => $app['dbname'],
'dbuser' => $app['dbuser'],
'dbpass' => $app['dbpass'],
'dbhost' => $app['dbhost'],
]);
};
$app['user.repository'] = function() use($app) {
return new UserRepository($app['db.connection']);
};
$app['post.repository'] = function() use($app) {
return new PostRepository($app['db.connection']);
};
$app['index.controller'] = function() use($app) {
return new IndexController($app['user.repository']);
};
$app['post.controller'] = function() use($app) {
return new PostController($app['post.repository']);
};
But this file is requested on every request and all dependencies are loaded into the container, my question is how can I load the dependencies that I only need for one request and not all.

As #deceze says in their comment (shoulda made it an answer!), all you are "loading" on each request is a bunch of function expression statements. You can basically look at your providers as "classes" wherein the function declarations are done as function expressions rather than function statements. As per a normal class, the functions don't run simply cos you define them, right? No. You need to actually call them before they do anything.
The code within the function expression assigned to the $app['db.connection'] service is only executed if you use $app['db.connection']. Otherwise all you've done is defined a variable holding a function.
Obviously in the case of $app['db.connection'] you are more than likely gonna be using it every request one way or another. But for argument's sake $app['post.repository'] will only have its callback executed if you actively use $app['post.repository'] in that request.
Make sense? I can elaborate further if not.

Related

Phalcon\Mvc\View\Exception: Macro 'baseImagesURL' does not exist

I have defined a shared service baseImagesURL in my configuration Class but when I try to use it in volt it throws this error Phalcon\Mvc\View\Exception: Macro 'baseImagesURL' does not exist
/**
* This service helps in the setting base images folder URL
*/
$di->setShared('baseImagesURL', function() use ($di) {
/** #var Config $config */
$config = $di->get('config');
$url = new Url();
$url->setStaticBaseUri( $config->path("environment.baseImagesUri"));
return $url;
});
Volt:
<img src="{{baseImagesURL('Sale-big.jpg')}}" >
Volt, by default, has already a function called url that can be used to handle what you want. I assume you are already familiar with the url function, so I imagine that you are using a different name (baseImagesURL) because you would like to have both functions simultaneously available inside the templating engine, with different base URI configurations.
To find out how to do what you whant, we can inspect the generated compiled code of a Volt template that uses the regular url function. We will see that the line {{url('foo.bar')}} gets translated to: <?= $this->url->get('foo.bar') ?> inside the generated PHP code (you can find this compiled file inside the cache/ dir of your Phalcon app).
Knowing that, we can do the same thing and create a new function called baseImagesURL to be used. First, we have to create a new service, like you already did in your question:
$di->setShared('baseImagesURLService', function () {
$url = new UrlResolver();
$url->setBaseUri('/tmp2/');
$url->setStaticBaseUri('/tmp2/');
return $url;
});
The above is similar to what you had in your question, but I've simplified a little bit to have the base URIs hardcoded.
After creating this service, you can add a new Volt function:
$volt->getCompiler()->addFunction(
'baseImagesURL',
function ($url) {
return '$this->baseImagesURLService->get(' . $url . ');';
}
);
Now, we are ready to use the new function inside a Volt template:
{{ url('foo.bar') }}
<br/>
{{ baseImagesURL('foo.bar') }}
The above will result in:
/tmp/foo.bar
/tmp2/foo.bar
As you can see, I have used both url() and baseImagesURL() inside the same template, to show you that both are working as expected. For this demo, I've configured the url service almost identical to baseImagesURLService, except for the hardcoded path:
$di->setShared('url', function () {
$url = new UrlResolver();
$url->setBaseUri('/tmp/');
$url->setStaticBaseUri('/tmp/');
return $url;
});
PS - I have only named the service baseImagesURLService (redundant name) to make a clear distinction between the service name, and the Volt function name (baseImagesURL). Of course, you could use the same name for both.
PS2 - Make sure that you have configured Volt to always recompile your template. If not, the function baseImagesURL will not be available and will trigger the same error you have already encountered (macro not found). Example:
$volt->setOptions([
'compiledPath' => $config->application->cacheDir,
'compiledSeparator' => '_',
'compileAlways' => true
]);

Symfony 3 Ajax call routing issue

I'm setting up a simple Ajax call in one of my forms. When a user enters characters in a field, the following Ajax call is activated:
self.modify = function (input_field) {
if ($(input_field).val().length > 5) {
$.post("{{path('get_bio_control_sample')}}", {sample_number: $(input_field).val()},
function (response) {
if (response.code == 100 && response.success) {
alert(response.sample_number);
}
}, "json");
}
};
Which is meant to access the following controller action:
class BioControlController extends Controller {
/**
* #Route("/bio_control/sample", name="get_bio_control_sample")
*/
public function getBioControlSampleAction(Request $request){
$sample_number = $request->query->get('sample_number');
$response = array("code" => 100, "success" => true, "sample_number" => $sample_number, "sample_data" => "test");
return new JsonResponse($response);
}
}
However, when the call is activated JS returns the error:
http://127.0.0.1:8000/omics_experiment/%7B%7Bpath('get_bio_control_sample')%7D%7D 404 (Not Found)
I'm accessing the Ajax call from omics_experiment/new (which is in the OmicsExperimentController) and using the route /bio_control/sample (as shown by the annotation), but it's not working. Can someone explain what I'm doing wrong?
I used this question as a template, the fact I'm using Symfony 3 might mean there are syntactic errors.
I just had to do this recently. I'm no expert on Symfony either, but since I just did this I may be able to help. Using Symfony is not really much different than doing it with a static URL. The main thing is to make sure that your controller and route are set up properly and working without AJAX, then you just need to use the path set in your route for the .post call.
And what makes it worse, is that it's really hard to test this type of interaction. Even your twig includes can cause it to fail if they are set up wrong.
Looking at your code again I think this may be the problem. Change this
$.post("{{path('get_bio_control_sample')}}", {sample_number:
to this
$.post("/bio_control/sample", {sample_number:
Because I think the way you have it is only good for twig templates, so if Symfony is not looking at your JQuery file like it does a twig template, then, it's not going to understand how to get the route.

Proper way to inject dynamic configuration into configuration array

I'm wondering what is the best way to inject dynamic configuration(retrieved from db for instance) into configuration array in Zend Framework 2? In Module.php I have:
public function onBootstrap(MvcEvent $e) {
$eventManager = $e->getApplication()->getEventManager();
$moduleRouteListener = new ModuleRouteListener();
$moduleRouteListener->attach($eventManager);
$eventManager->attach('route', array($this, 'mergeDynamicConfig'));
}
public function mergeDynamicConfig(EventInterface $e) {
$application = $e->getApplication();
$sm = $application->getServiceManager();
$configurationTable = $sm->get('DynamicConfiguration\Model\Table\ConfigurationTable');
$dynamicConfig = $configurationTable->fetchAllConfig();
//Configuration array from db
//Array
//(
// [config] => 'Test1',
// [config2] => 'Test2',
// [config3] => 'Test3',
//)
//What to do here?
//I want to use the configurations above like $sm->get('Config')['dynamic_config']['config3'];
}
There is a section in the documentation that explains how to manipulate the merged configuration using the specific event ModuleEvent::EVENT_MERGE_CONFIG
Zend\ModuleManager\Listener\ConfigListener triggers a special event, Zend\ModuleManager\ModuleEvent::EVENT_MERGE_CONFIG, after merging all configuration, but prior to it being passed to the ServiceManager. By listening to this event, you can inspect the merged configuration and manipulate it.
The problem with this is that the service manager is not available at this point as the listener's event is one of the first events triggered by the module manager at priority 1000).
This means that you cannot execute your query and merge the config prior to the configuration being passed to the service manager, you would need to do so after.
Perhaps I have misunderstood your requirements, however I would approach this differently.
You could replace any calls where you need config $serviceManager->get('config') with $serviceManager->get('MyApplicationConfig'); which would be you own configuration service that uses the merged application config and then adds to it.
For example, you could register this configuration service in module.config.php.
return [
'service_manager' => [
'factories' => [
'MyApplicationConfig' => 'MyApplicationConfig\Factory\MyApplicationConfigFactory',
]
],
];
And create a factory to do the loading of merged module configuration, making any database calls or caching etc.
class MyApplicationConfigFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $sm)
{
$config = $sm->get('config');
$dbConfig = $this->getDatabaseConfigAsArray($sm);
return array_replace_recursive($config, $dbConfig);
}
protected function getDatabaseConfigAsArray(ServiceLocatorInterface $sm)
{}
}
You also have the added benefit that the service is lazy loaded.
I would not use this approuch, for a few reasons.
Putting SQL queries in your Module.php means that they will get executed on EVERY request for every user thus making your application slow, very slow.
If your database is compromised all the config will be stolen as well.
Solution would be to move all the config in your config/autoload/my_custom_config.local.php via array with keys. From there you can always load it without making a single database request. It will be way faster and secure, because the file will be outside your root folder and hacking a server is always alot harder than hacking a database.
If you still want to allow users to eit the options you can simply include the file in an action and show it with a foreach for example. To save the information you can do this:
file_put_contents("my_custom_config.local.php", '<?php return ' . var_export($config, true).';');
One other plus is that if you load your config the way discribe above you can also retrive the config like you want via $sm->get('Config')['dynamic_config']['config3']

Silex FormServiceProvider and form.secret parameter

I am using the FormServiceProvider from Silex and reading through the documentation it explains how it has one parameter called form.secret which I assumed meant constructing the provider with this:
$app->register(new Silex\Provider\FormServiceProvider(), [
'form.secret' => 'SECRET HERE'
]);
The problem is however when I look through the source code for this file I cannot see a constructor where this parameter would get used. Only seeing it set internally within the container to md5(__DIR__).
https://github.com/silexphp/Silex/blob/master/src/Silex/Provider/FormServiceProvider.php#L48
Or would it be simply a case of not providing form.secret when constructing and simply setting $app['form.secret'] = 'SECRET HERE' after the provider has been registered?
Am I right in this assumption or I am I missing something?
You can see it being used in line 100, when $app["form.csrf_provider"] is first accessed:
$app['form.csrf_provider'] = function ($app) {
if (isset($app['session'])) {
return new SessionCsrfProvider($app['session'], $app['form.secret']);
}
return new DefaultCsrfProvider($app['form.secret']);
};
Since whatever you pass is ignored and overwritten with the md5 call you mention, correct usage would be:
$app->register(new FormServiceProvider());
$app["form.secret"] = "foo";

How to organize server side ajax scripts

Much of my work lately has involved expanding and bug fixing ajax actions. But the action lists are fairly unmanageable in size, and since I was not the original author and the comments are sparse, I spend a great deal of time tracing the code paths and trying to figure out which jquery event triggered the action and if it sent the proper data with the request.
Right now the ajax request scripts are basically just about a hundred if-else blocks split up into different files based loosely on their function.
Is there a relevant design pattern or a php idiom to help me better organize the php part of the ajax requests?
I was thinking of maybe making some sort of dispatch interface. (Don't know if it is a good or workable idea.) where I can register actions and somehow indicate what data they require. The dispatcher would then call the function from the appropriate place. Then I could route all ajax requests through a single script and organize my functions however I want. And I can have an overview of what data is required to call a certain action without reading its implementation line by line. I would potentially have access to my server-side class heirarchy from the client side.
Does this sound workable? Is this safe? Are there other ways that might work better? The inspiration for this was basically smalltalk style message passing. My major worry is that I am going to introduce a cross-side request forgery vulnerability or that there is already one present in the code and due to it being difficult to read, I missed it.
I use a RPC-style mechanism to achieve what I think you want.
Disclaimer: I've successfully implemented this scheme in JS+PHP and JS+Python, so it is workable. But it might not be secure. You have to take all appropriate verification steps to make sure it is secure (especially w.r.t. to code/SQL injection and XSS attacks)
The idea is to have a single PHP script that processes the RPC requests, receiving the method name and its argument through both GET and POST, and outputs JSON back to the Javascript side.
For instance, on the client side:
API.rpc('getItemById', 1532, function(item) { console.log(item); });
would write
Object(id=1532,name="foo",whatever="bar")
on the console.
The communication protocol I use is the following:
the client sends an HTTP request to the RPC handler script, using
either GET or POST. The restrictions are that the 'method' must
always be provided in the GET, and that all arguments must be
URL-encoded. Otherwise, all arguments are given as key=value pairs and can be part of the request (GET) or the payload (POST)
the server always responds with an HTTP 200 (otherwise it means that a very nasty thing happened). It responds only with JSON data. The returned object has at least 2 members.
the 'success' member is always there, and indicates if the call succeeded - i.e. that no exception was thrown
if successful, the 'ret' members contains the return value of the function
if an exception was thrown, the 'message' member contains the exception message (I prefer sending the whole backtrace here, but that's certainly not good for sensitive environments)
(1) On the javascript side (assuming jQuery, coding as I think, so this may be buggy):
API = function() {
this.rpc = function(method, args, callback) {
return $.ajax({
url: 'rpcscript.php?method='+encodeURIComponent(args.method),
data: args,
type: 'post', //only the method name is sent as a GET arg
dataType: 'json'
error: function() {
alert('HTTP error !'); // This is e.g. an HTTP 500, or 404
},
success: function(data) {
if (data.success) {
callback(data.ret);
} else {
alert('Server-side error:\n'+data.message);
}
},
});
}
}
You can then add shortcut functions such as syncRPC() to perform synchronous calls, etc.
(2) On the PHP side (slightly modified running code):
class MyAPI
{
function getItemById($id)
{
// Assuming the $db is a database connection returning e.g. an associative array with the result of the SQL query. Note the (int) typecast to secure the query - all defensive measures should be used as usual.
return $db->query("SELECT * FROM item WHERE id = ".(int)$id.";");
}
}
class RemoteProcedureCall
{
function __construct()
{
$this->api = new MyAPI();
}
function serve()
{
header("Content-Type: application/json; charset=utf-8");
try
{
if (!isset($_GET['method']))
throw new Exception("Invalid parameters");
$methodDesc = array($this->api, $_GET['method']);
if (!method_exists($methodDesc[0], $methodDesc[1]) || !is_callable($methodDesc))
throw new Exception("Invalid parameters");
$method = new ReflectionMethod($methodDesc[0], $methodDesc[1]);
$params = array();
foreach ($method->getParameters() as $param)
{
// The arguments of the method must be passed as $_POST, or $_GET
if (isset($_POST[$param->getName()]))
// OK, arg is in $_POST
$paramSrc = $_POST[$param->getName()];
elseif (!in_array($param->getName(),array('action','method'))
&& isset($_GET[$param->getName()])
&& !isset($paramSrc[$param->getName()]))
// 'action' and 'method' are reserved $_GET arguments. Arguments for the RPC method
// can be any other args in the query string, unless they are already in $_POST.
$paramSrc = $_GET[$param->getName()];
if (!isset($paramSrc))
{
// If the argument has a default value (as specified per the PHP declaration
// of the method), we allow the caller to use it - that is, not sending the
// corresponding parameter.
if ($param->isDefaultValueAvailable())
$p = $param->getDefaultValue();
else
throw new Exception("Invalid parameters");
}
else
{
$p = $paramSrc;
}
$params[$param->getName()] = $p;
unset($paramSrc);
}
$ret = $method->invokeArgs($db, $params);
echo json_encode(array('success' => true, 'ret' => $ret));
}
catch (Exception $e)
{
echo json_encode(array('success' => false, 'message' => $e->getMessage()."\n".$e->getBacktrace()));
}
}
};
$rpc = RemoteProcedureCall();
$rpc->serve();
There are many application-specific assumptions here, including the kind of exceptions that may be thrown, the reserved keywords, etc ...
Anyway I hope this provides a good starting point for your problem.
You can have a look here: http://www.phpapi.org/
From description:
"This is the skeleton upon which you can develop a web-system from a simple Web Calculator to the most sofisticated CRM/ERP/CMS/ETC. What PHP-API provides is: a general structure of the code, a very simple extendable API code structure,JavaScript connectivity with the API ( with an easy way of adding new modules/method handlers ), ...."

Categories