I am trying to refactor an older codebase to use Silex - my thinking was that I could have Silex handle all actions it understands - and defer everything it doesn't to the legacy controller architecture:
$app = new Silex\Application();
$app['debug'] = true;
// TODO: Refactor old actions into Silex incrementally
//$app->get('/hello/{name}', function ($name) use ($app) {
// return 'Hello ' . $app->escape($name);
//});
// NOTE: Defer handling to legacy controllers:actions
$app->error(function (\Symfony\Component\Routing\Exception\ResourceNotFoundException $e, $code) {
echo 'Silex not provided URL - default to legacy';
try {
$response = new Spectra_Application_Environment_Http_Response();
$request = new Spectra_Application_Environment_Http_Request();
$router = new Spectra_Application_Controller_Router_Adapter(
$_SERVER['PATH_INFO'], $request, new Common_Controller_Router($GLOBALS['AQUARIUS']['ROUTES'], key($GLOBALS['AQUARIUS']['ROUTES']))
);
$front = Spectra_Application_Controller_Front::getInstance();
$front->setResponseObject($response)->setRequestObject($request);
$front->addPrePluginFilter(new Common_Controller_Filter_ActionCheck());
echo cadorath_main($router, $front)->sendResponse();
}
catch (Exception $e) {
$result['success'] = false;
$result['errors']['reason'] = $e->getMessage();
header('Content-Type: application/json');
echo json_encode($result);
}
});
$app->run();
This isn't working. What am I not understanding of how Silex exception handling works?
Sorry for this, but before I give you an answer I feel I have to point out a few things and this is too long for the comment field. Please take them as a friendly advice and not as a criticism.
First, Silex is being discontinued very soon. Since you are just starting out with migrating the codebase I suggest either a different microkernel-framework like SlimPHP or Lumen or switching to Symfony 4 using Flex. The latter is probably closest to the experience you get with Silex as it uses the same components it just gives you an easier path to extend your application with features from Symfony without having to manually wire everything up using Silex' provider logic.
The second point I want to make is your use of echo: It might be in there for debugging purposes, but if not, please be aware that this "breaks" most frameworks as they usually have some event system that is triggered during the runtime and just printing out stuff bypasses and in some cases even breaks those. When you are using Silex/Symfony you should use the Request and Response objects to handle the incoming and outgoing data and use return if you want to pass them on. So in your case instead of doing
header('Content-Type: application/json');
echo json_encode($result);
You could do:
return new JsonResponse($result);
When you use the JsonResponse object it will set the headers for you and also do the JSON-encoding. So it's even easier and shorter than before.
As to your actual question, please provide more information on the errors you get. Maybe check your application or server logs (apache, nginx, ...).
My initial assumption is that the Request you are using for your legacy application does not contain all the necessary information. You might have to transfer over some data from Symfony's Request object or "fix" some of the values as they point to your new application, not the legacy application (e.g. SCRIPT_PATH) which might mess up some assumptions in your old code. Generally speaking try to identify which parts of the legacy code base use globals and check if those values are correctly set or refactor your application to not require those globals but instead read them from a config file for example.
Related
Let's say I have these classes:
Old_Class
New_Class
If this exists ->something(new Old_Class()) or Old_Class::staticMethod() or $oldClass->methodCall() I want a code sniff to warn "Old_Class usage found, recommend using New_Class instead".
I found this sniff Generic.PHP.ForbiddenFunctions but it only seems to catch built-in php functions is_array, is_null, etc.
Do I need to write a custom sniff for this?
If so, what token should I added to the register() function to catch on?
I couldn't use a built-in one. I had to write one using T_STRING.
public function register()
{
return [
T_STRING,
];
}
public function process(PHP_CodeSniffer_File $phpcsFile, $stackPtr)
{
$tokens = $phpcsFile->getTokens();
if ($tokens[$stackPtr]['content'] === 'Old_Class') {
$error = 'Old_Class usage found, consider using New_Class instead.';
$phpcsFile->addWarning($error, $stackPtr);
}
}
I know this is a fairly old question, but there are two possible solutions I'm aware of:
The Slevomat coding standard for Codesniffer includes a sniff to report usage of any of an array of forbidden classes
Static analysis tools are another approach for this. If you mark a class with the #deprecated annotation, I know from personal experience that Psalm will flag it with the DeprecatedClass issue, and if you can't fix them all at once, you can add them to the baseline, which will suppress the issue, but keep track of it, so you can still use Psalm in continuous integration and not break on existing issues. I believe PHPStan has similar functionality.
I have a dotnet core application.
My Startup.cs registers types/implementations in Autofac.
One of my registrations needs previous access to a service.
var containerBuilder = new ContainerBuilder();
containerBuilder.RegisterSettingsReaders(); // this makes available a ISettingsReader<string> that I can use to read my appsettings.json
containerBuilder.RegisterMyInfrastructureService(options =>
{
options.Username = "foo" //this should come from appsettings
});
containerBuilder.Populate(services);
var applicationContainer = containerBuilder.Build();
The dilemma is, by the time I have to .RegisterMyInfrastructureService I need to have available the ISettingsReader<string> that was registered just before (Autofac container hasn't been built yet).
I was reading about registering with callback to execute something after the autofac container has been built. So I could do something like this:
builder.RegisterBuildCallback(c =>
{
var stringReader = c.Resolve<ISettingsReader<string>>();
var usernameValue = stringReader.GetValue("Username");
//now I have my username "foo", but I want to continue registering things! Like the following:
containerBuilder.RegisterMyInfrastructureService(options =>
{
options.Username = usernameValue
});
//now what? again build?
});
but the problem is that after I want to use the service not to do something like starting a service or similar but to continue registering things that required the settings I am now able to provide.
Can I simply call again builder.Build() at the end of my callback so that the container is simply rebuilt without any issue? This seems a bit strange because the builder was already built (that's why the callback was executed).
What's the best way to deal with this dilemma with autofac?
UPDATE 1: I read that things like builder.Update() are now obsolete because containers should be immutable. Which confirms my suspicion that building a container, adding more registrations and building again is not a good practice.
In other words, I can understand that using a register build callback should not be used to register additional things. But then, the question remain: how to deal with these issues?
This discussion issue explains a lot including ways to work around having to update the container. I'll summarize here, but there is a lot of information in that issue that doesn't make sense to try and replicate all over.
Be familiar with all the ways you can register components and pass parameters. Don't forget about things like resolved parameters, modules that can dynamically put parameters in place, and so on.
Lambda registrations solve almost every one of these issues we've seen. If you need to register something that provides configuration and then, later, use that configuration as part of a different registration - lambdas will be huge.
Consider intermediate interfaces like creating an IUsernameProvider that is backed by ISettingsReader<string>. The IUsernameProvider could be the lambda (resolve some settings, read a particular one, etc.) and then the downstream components could take an IUsernameProvider directly.
These sorts of questions are hard to answer because there are a lot of ways to work around having to build/rebuild/re-rebuild the container if you take advantage of things like lambdas and parameters - there's no "best practice" because it always depends on your app and your needs.
Me, personally, I will usually start with the lambda approach.
i am unit testing in laravel with Phpunit. The situation is i have to return a model instance from the controller back to the testing class. There i will use the attributes of that object to test an assertion. How can i achieve that?
Currently i am json encoding that instance into the response. And using it in a way that works but is ugly. Need a clearer way.
This is my test class:
/** #test
*/
function authenticated_user_can_create_thread()
{
//Given an authenticated user
$this->actingAs(factory('App\User')->create());
//and a thread
$thread = factory('App\Thread')->make();
//when user submits a form to create a thread
$created_thread = $this->post(route('thread.create'),$thread->toArray());
//the thread can be seen
$this->get(route('threads.show',['channel'=>$created_thread->original->channel->slug,'thread'=>$created_thread->original->id]))
->assertSee($thread->body);
}
and this is the controller method:
public function store(Request $request)
{
$thread = Thread::create([
'user_id'=>auth()->id(),
'title'=>$request->title,
'body'=>$request->body,
'channel_id'=>$request->channel_id,
]);
if(app()->environment() === 'testing')
{
return response()->json($thread); //if request is coming from phpunit/test environment then send back the creted thread object as part of json response
}
else
return redirect()->route('threads.show',['channel'=>$thread->channel->slug,'thread'=>$thread->id]);
}
As you can see in the test class, i am receiving the object returned from controller in the $created_thread variable. However, controller is returning an instance of Illuminate\Foundation\Testing\TestResponse, so the THREAD that is embedded in this response is not easy to extract. You can see i am doing
--> $created_thread->original->channel->slug,'thread'=>$created_thread->original->id]. But i am sure there is a better way of achieving the same thing.
Can anyone please guide me to the right direction?
PHPUnit is a unit testing suite, hence the name. Unit testing is, by
definition, writing tests for each unit -- that is, each class, each
method -- as separately as possible from every other part of the
system. Each thing users could use, you want to try to test that it --
and only it, apart from everything else -- functions as specified.
Your problem is, there is nothing to test. You haven't created any method with logic which could be tested. Testing controllers action is pointless, as it only proves that controllers are working, which is a Laravel creators thing to check.
we're working on PHPunitizing our application with two scopes in mind: reduce regressions and bugs in releases.
The application apply an MVC pattern but with no Open Source framework: we're actually use a proprietary one with lots of components coming from symfony (goal is to port all to symfony sometime in the future).
Application has some design flaws so doesn't fit perfectly for Unit Testing. Anyway we've got a point where we can start and here comes some strage behaviours i can't solve.
All of our business methods rely on router object passed by Dependency injection at creation time.
Router exposes some methods to get and set variables like _SERVER, _GET, _POST that are cloned inside router itself (Use of global variable is prohibited by design).
In the following test i set SERVER and GET just before tested class instancing.. so i was expect to find their values correctly during test.
Instead they are fully empty and, of course, test fail.
Any clue of what i'm doing wrong?
Thanks.
/**
* Test external class with valid UUID
*
*/
public function testGetClassPermissionExternal() {
$TEST_SERVER_DATA = array(
'REQUEST_URI' => 'https://sviluppo.pardgroup.com/test',
'SERVER_PORT' => 443,
'SERVER_NAME' => 'sviluppo.pardgroup.com'
);
// Due to bad design superglobal $_SERVER need to be fullfilled.
$_SERVER = $TEST_SERVER_DATA;
$this->router->setSERVER($TEST_SERVER_DATA);
$TEST_POST_DATA = array('uuid' => self::$UUID);
$this->router->setGET($TEST_POST_DATA);
$this->class = \permission::getInstace($this->router);
$test_class = array("permissionTest/external");
$this->assertFalse($this->invokeMethod($this->class, "getClassPermission", $test_class));
}
I have two issues here, the second one is irrelevant if the first one got answered, but still technically interesting in my opinion... I will try to be as clear as possible:
1st question: my goal is to fake a Server.Transfer in MVC, is there any descent way to do that, I found quite a few articles about it, but most where about redirecting / rerouting, which is not possible in my case (not that I can think of at least).
Here is the context, we have two versions of our website, a "desktop" one and a mobile one. Our marketing guy wants both versions of the home page to be served on the same url (because the SEO expert said so).
This sounds trivial and simple, and it kind of is in most cases, except... Our desktop site is a .NET 4.0 ASPX site, and our mobile site is MVC, both run in the same site (same project, same apppool, same app).
Because the desktop version represents about 95% of our traffic, this should be the default, and we want to "transfer" (hence same url) from the ASPX code behind to the MVC view only if user is on a mobile device or really wants to see the mobile version. As far as I saw so far, there is no easy way to do that (Server.Transfer only executes a new handler - hence page - if there is a physical file for it). Hence question has any one done that in a proper way so far?
And which brings me to:
2nd question: I did build my own transfer to MVC mechanism, but then figured out that a Response.End() does not actually ends the running thread anymore, does anyone have a clue why?
Obviously, I don't expect any answer out of the blue, so here is what I am doing:
in the page(s) which needs transfering to mobile, I do something like:
protected override void OnPreInit(EventArgs e) {
base.OnPreInit(e);
MobileUri = "/auto/intro/index"; // the MVC url to transfer to
//Identifies correct flow based on certain conditions 1-Desktop 2-Mobile
BrowserCheck.RedirectToMobileIfRequired(MobileUri);
}
and my actual TransferToMobile method called by RedirectToMobileIfRequired (I skipped the detection part as it is quite irrelevant) looks like:
/// <summary>
/// Does a transfer to the mobile (MVC) action. While keeping the same url.
/// </summary>
private static void TransferToMobile(string uri) {
var cUrl = HttpContext.Current.Request.Url;
// build an absolute url from relative uri passed as parameter
string url = String.Format("{0}://{1}/{2}", cUrl.Scheme, cUrl.Authority, uri.TrimStart('/'));
// fake a context for the mvc redirect (in order to read the routeData).
var fakeContext = new HttpContextWrapper(new HttpContext(new HttpRequest("", url, ""), HttpContext.Current.Response));
var routeData = RouteTable.Routes.GetRouteData(fakeContext);
// get the proper controller
IController ctrl = ControllerBuilder.Current.GetControllerFactory().CreateController(fakeContext.Request.RequestContext, (string)routeData.Values["controller"]);
// We still need to set routeData in the request context, as execute does not seem to use the passed route data.
HttpContext.Current.Request.RequestContext.RouteData.DataTokens["Area"] = routeData.DataTokens["Area"];
HttpContext.Current.Request.RequestContext.RouteData.Values["controller"] = routeData.Values["controller"];
HttpContext.Current.Request.RequestContext.RouteData.Values["action"] = routeData.Values["action"];
// Execute the MVC controller action
ctrl.Execute(new RequestContext(new HttpContextWrapper(HttpContext.Current), routeData));
if (ctrl is IDisposable) {
((IDisposable)ctrl).Dispose(); // does not help
}
// end the request.
HttpContext.Current.Response.End();
// fakeContext.Response.End(); // does not add anything
// HttpContext.Current.Response.Close(); // does not help
// fakeContext.Response.Close(); // does not help
// Thread.CurrentThread.Abort(); // causes infinite loading in FF
}
At this point, I would expect the Response.End() call to end the thread as well (and it does if I skip the whole faking the controller execution bit) but it doesn't.
I therefore suspect that either my faked context (was the only way I found to be able to passed my current context with a new url) or the controller prevents the thread to be killed.
fakeContext.Response is same as CurrentContext.Response, and the few attempts at ending the fake context's response or killing the thread didn't really help me.
Whatever code is running after the Response.End() will NOT actually be rendered to the client (which is a small victory), as the Response stream (and the connection, no "infinite loading" in the client) is being closed. But code is still running and that is no good (also obviously generates loads of errors when trying to write the ASPX page, write headers, etc.).
So any new lead would be more than welcome!
To sum it up:
- does anyone have a less hacky way to achieve sharing a ASPX page and a MVC view on the same url?
- if not, does anyone have a clue how I can ensure that my Response is really being ended?
Many thanks in advance!
Well,
for whoever is interested, I at least have answer to question 1 :).
When I first worked on that feature, I looked at the following (and very close) question:
How to simulate Server.Transfer in ASP.NET MVC?
And tried both the Transfer Method created by Stan (using httpHandler.ProcessRequest) and Server.TransferRequest methods. Both had desadvantages for me:
the first one does not work in IIS, (because I need to call that in a page, and that seems too late already).
the second one makes it terribly annoying for developers who all need to run their site in IIS (no biggy, but still...).
Seeing that my solution obviously wasn't optimal, I had to come back to the IIS solution, which seems to be the neatest for production environment.
This solution worked for a page and triggered an infinite loop on another one...
That's when I got pointed to what I had lazily discarded as not being the cause: our url redirect module. It uses Request.RawUrl to match a rule, and oh surprise, Server.TransferRequest keeps the original Request.RawUrl, while app.Request.Url.AbsolutePath will contain the transfered-to url. So basically our url rewrite module was always redirecting to the original requested which was trying to transfer to the new one, etc.
Changed that in the url rewriting module, and will hope that everything still works like a charm (obviously a lot of testing will follow such a change)...
In order to fix the developers issue, I chose to combine both solutions, which might make it a bit more of a risk for different behaviors between development and production, but that's what we have test servers for...
so here is my transfer method looks like in the end:
Once again this is meant to transfer from an ASPX page to a MVC action, from MVC to MVC you probably don't need anything that complex, as you can use a TransferResult or just return a different view, call another action, etc.
private static void Transfer(string url) {
if (HttpRuntime.UsingIntegratedPipeline) {
// IIS 7 integrated pipeline, does not work in VS dev server.
HttpContext.Current.Server.TransferRequest(url, true);
}
// for VS dev server, does not work in IIS
var cUrl = HttpContext.Current.Request.Url;
// Create URI builder
var uriBuilder = new UriBuilder(cUrl.Scheme, cUrl.Host, cUrl.Port, HttpContext.Current.Request.ApplicationPath);
// Add destination URI
uriBuilder.Path += url;
// Because UriBuilder escapes URI decode before passing as an argument
string path = HttpContext.Current.Server.UrlDecode(uriBuilder.Uri.PathAndQuery);
// Rewrite path
HttpContext.Current.RewritePath(path, true);
IHttpHandler httpHandler = new MvcHttpHandler();
// Process request
httpHandler.ProcessRequest(HttpContext.Current);
}
I haven't done much research, but here's what seems to be happening upon Response.End():
public void End()
{
if (this._context.IsInCancellablePeriod)
{
InternalSecurityPermissions.ControlThread.Assert();
Thread.CurrentThread.Abort(new HttpApplication.CancelModuleException(false));
}
else if (!this._flushing)
{
this.Flush();
this._ended = true;
if (this._context.ApplicationInstance != null)
{
this._context.ApplicationInstance.CompleteRequest();
}
}
}
That could at least provide the "Why" (_context.IsInCancellablePeriod). You could try to trace that using your favourite CLR decompiler.