Symfony: How to load all paths from the same Index route (to use dynamic routing in a React SPA) - reactjs

I'm creating a SPA backed by Symfony and ApiPlatform so I want to always load my main route despite the real path of the URL.
I want something like this:
/**
* {#inheritdoc}
*/
class DefaultController extends Controller
{
/**
* #Route("/*", name="homepage")
*
* #return Response
*/
public function indexAction(): Response
{
// replace this example code with whatever you need
return $this->render('default/index.html.twig');
}
}
In my intentions, also if the URL is something like /path/to/the/spa/page I want to anyway load the DefaultController::indexAction()route.
How to do this? (obviously the provided example doesn't work).

Ok, I've found the solution after an "illumination".
I remembered that there is the possibility to rewrite all URL adding or removing the trailing slash
Reading that article I saw this:
class RedirectingController extends Controller
{
/**
* #Route("/{url}", name="remove_trailing_slash",
* requirements={"url" = ".*\/$"})
*/
public function removeTrailingSlash(Request $request)
{
// ...
}
}
So, to intercept all URL despite the path, my DefaultController::indexAction() becomes this:
class DefaultController extends Controller
{
/**
* #Route("/{url}",requirements={"url"=".*"}, name="homepage")
*
* #return Response
*/
public function indexAction(): Response
{
// replace this example code with whatever you need
return $this->render('default/index.html.twig');
}
}
Now all URL are all handled by DefaultController::indexAction() despite the URL path.

I would recommend you to use symfony's event system instead.
Subscribe to either kernel.request or kernel.router events.
In case of kernel.request you have to overtake the Symfony\Component\HttpKernel\EventListener\RouterListener::onKernelRequest()
which priotiry is 32 (use 33 at least).
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
class SpaSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents()
{
return [
KernelEvents::REQUEST => ['onKernelRequest', 33],
];
}
public function onKernelRequest(RequestEvent $event)
{
$request = $event->getRequest();
if (!$request->isXmlHttpRequest()) {
$html = $this->twig->render('spa.html.twig', [
'uri' => $request->getUri(),
]);
$response = new Response($html, Response::HTTP_OK);
$event->setResponse($response);
}
}
}
In case of kernel.router use priority 1 at least.
You can use the php bin/console debug:event-dispatcher command to find out which listeners are registered for events and their priorities.

Related

Laravel 5 Reseeding the Database for Unit Testing Between Tests

I start with a seeded database and am trying to reseed the database between unit tests in Laravel 5. In Laravel 4 I understand you could simply use Illuminate\Support\Facades\Artisan and run the commands
Artisan::call('migrate');
Artisan::call('db:seed');
or you supposedly could do:
$this->seed('DatabaseSeeder');
before every test. In Laravel 5 this appears to have been replaced by
use DatabaseMigrations;
or
use DatabaseTransactions;
I have tried using these and have managed to get the tests to migrate the database; however, it doesn't actually reseed the data in the tables. I have read through several forums complaining about this and have tried several different approaches calling these from the TestCase and inside every Test...adding the
$this->beforeApplicationDestroyed(function () {
Artisan::call('migrate');
Artisan::call('migrate:reset');
Artisan::call('db:seed');
DB::disconnect();
});
to the TestCase.php tearDown()...
I have also tried adding
$this->createApplication();
to a method called in every test from TestCase.php
Sometimes it just wipes my tables out completely. Nothing I am finding on Laravel's site or in blogs seems to work. Part of it is probably because I'm probably trying Laravel 4 methods in Laravel 5. Is there any way to do this in Laravel 5?
My code for the testcase.php looks like:
<?php
use Illuminate\Support\Facades\Artisan as Artisan;
class TestCase extends Illuminate\Foundation\Testing\TestCase{
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
protected $baseUrl = 'http://localhost';
public function initializeTests(){
$this->createApplication();
Artisan::call('migrate');
$this->artisan('migrate');
Artisan::call('db:seed');
$this->artisan('db:seed');
$this->seed('DatabaseSeeder');
$this->session(['test' => 'session']);
$this->seed('DatabaseSeeder');
}
public function tearDown()
{
Mockery::close();
Artisan::call('migrate:reset');
$this->artisan('migrate:reset');
Artisan::call('migrate:rollback');
$this->artisan('migrate:rollback');
Artisan::call('migrate');
$this->artisan('migrate');
Artisan::call('db:seed');
$this->artisan('db:seed');
$this->seed('DatabaseSeeder');
DB::disconnect();
foreach (\DB::getConnections() as $connection) {
$connection->disconnect();
}
$this->beforeApplicationDestroyed(function () {
Artisan::call('migrate:reset');
$this->artisan('migrate:reset');
Artisan::call('migrate:rollback');
$this->artisan('migrate:rollback');
Artisan::call('migrate');
$this->artisan('migrate');
Artisan::call('db:seed');
$this->artisan('db:seed');
$this->seed('DatabaseSeeder');
DB::disconnect();
foreach (\DB::getConnections() as $connection) {
$connection->disconnect();
}
});
$this->flushSession();
parent::tearDown();
}
public function getConnection()
{
$Connection = mysqli_connect($GLOBALS['DB_DSN'], $GLOBALS['DB_USERNAME'], $GLOBALS['DB_PASSWORD'], $GLOBALS['DB_DATABASE']);
$this->createDefaultDBConnection();
return $this->Connection;
}
public function createApplication()
{
$app = require __DIR__.'/../bootstrap/app.php';
$app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();
return $app;
}
/**
* Magic helper method to make running requests simpler.
*
* #param $method
* #param $args
* #return \Illuminate\Http\Response
*/
public function __call($method, $args)
{
if (in_array($method, ['get', 'post', 'put', 'patch', 'delete']))
{
return $this->call($method, $args[0]);
}
throw new BadMethodCallException;
}
/**
* Create a mock of a class as well as an instance.
*
* #param $class
* #return \Mockery\MockInterface
*/
public function mock($class)
{
$mock = Mockery::mock($class);
$this->app->instance($class, $mock);
return $mock;
}
}
My Test looks something like
<?php
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Artisan;
class CustomerRegistrationControllerTest extends TestCase
{
use DatabaseMigrations;
protected static $db_inited = false;
protected static function initDB()
{
echo "\n---Customer Registration Controller Tests---\n"; // proof it only runs once per test TestCase class
Artisan::call('migrate');
Artisan::call('db:seed');
}
public function setUp()
{
parent::setUp();
if (!static::$db_inited) {
static::$db_inited = true;
static::initDB();
}
// $this->app->refreshApplication();
$this->artisan('migrate:refresh');
$this->seed();
$this->seed('DatabaseSeeder');
$this->initializeTests();
);
}
public function testSomething()
{
$this->Mock
->shouldReceive('destroy')
->with('1')
->andReturn();
$this->RegistrationController->postRegistration();
// $this->assertResponseStatus(200);
}
}
Just run this:
$this->artisan('migrate:refresh', [
'--seed' => '1'
]);
To avoid changes to the database persisting between tests add use DatabaseTransactions to your tests that hit the database.
Why not create your own command like db:reset.
This command either truncate all your tables or drop/create schema and then migrate.
In your test you then use: $this->call('db:reset') in between your tests

changing ZF2 form behavior when retrieving form

I'm wondering if there is a way to either pass additional parameters to the constructor (preferred) or retrieve the Request object to check the headers from within the Form constructor so that when I do a getForm in a controller, the form will be customized depending on how it is called?
I'm working on integrating AngularJs bindings and model tags into my form elements but I will need to modify how the submit button works whenever a form is called from Ajax vs being pulled into a Zend template via the framework.
Thus I would like to throw conditional parameters around where the submit button is added to the form, but I need to know if the rendered form is being viewed in zend or is being sent via an ajax call. I can detect the ajax call in the controller by looking at the request headers with isXmlHttpRequest(), but I'm not sure how to let the form know what the controller saw when it's retrieving the form with $this->getForm()
You can inject any options you like using a factory class.
use MyModule\Form\MyForm;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\ServiceManager\FactoryInterface;
class MyFormFactory implements FactoryInterface
{
public function createService(ServiceLocatorInterface $formElementManager)
{
$serviceManager = $formElementManager->getServiceLocator();
$request = $serviceManager->get('Request');
// I would recommend assigning the data
// from the request to the options array
$options = [
'is_ajax' => $request->isXmlHttpRequest(),
];
// Although you could also pass in the request instance into the form
return new MyForm('my_form', $options, $request);
}
}
If you inject the request you will need modify MyForm::__construct.
namespace MyModule\Form;
use Zend\Form\Form;
use Zend\Http\Request as HttpRequest;
class MyForm extends Form
{
protected $request;
public function __construct($name, $options, HttpRequest $request)
{
$this->request = $request;
parent::__construct($name, $options);
}
}
Update your module.config.php to use the factory
return [
'form_elements' => [
'factories' => [
'MyModule\Form\MyForm' => 'MyModule\Form\MyFormFactory'
]
]
]
Then ensure you request the form from the service manager (in a controller factory)
$myForm = $serviceManager->get('FormElementManager')->get('MyModule\Form\MyForm');
My AbstractForm with helper functions (I just added the getRequest() to the bottom). Of course in a wider scale application I'd probably add error checking to make sure these were not called from the constructor (when the service manager would not yet be available)
namespace Application\Form;
use Zend\Form\Form as ZendForm;
use Zend\Http\Request as HttpRequest;
use Zend\Stdlib\RequestInterface as Request;
use Zend\Form\FormElementManager as ZendFormElementManager;
use Zend\ServiceManager\ServiceLocatorAwareInterface as ZendServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorInterface as ZendServiceLocatorInterface;
use Doctrine\ORM\EntityManager as DoctrineEntityManager
class AbstractForm extends ZendForm implements ZendServiceLocatorAwareInterface {
/**
* #var Request
*/
protected $request;
/**
* in form context this turns out to be Zend\Form\FormElementManager
*
* #var ZendFormElementManager $service_manager
*/
protected static $service_manager;
/**
* #var DoctrineEntityManager $entity_manager
*/
protected $entity_manager;
/**
* #var ZendServiceLocatorInterface $service_locator_interface
*/
protected $service_locator_interface;
public function __construct($name = null)
{
parent::__construct($name);
}
/**
* in form context this turns out to be Zend\Form\FormElementManager
*
* #param ZendFormElementManager $serviceLocator
*/
public function setServiceLocator(FormElementManager $serviceLocator)
{
self::$service_manager = $serviceLocator;
}
/**
* in form context this turns out to be Zend\Form\FormElementManager
*
* #return ZendFormElementManager
*/
public function getServiceLocator()
{
return self::$service_manager;
}
/**
* wrapper for getServiceLocator
* #return ZendFormElementManager
*/
protected function getFormElementManager() {
return $this->getServiceLocator();
}
/**
* this returns an actual service aware interface
*
* #return ZendServiceLocatorInterface
*/
protected function getServiceManager() {
if(!($this->service_locator_interface instanceof ZendServiceLocatorInterface)) {
$this->service_locator_interface = $this->getFormElementManager()->getServiceLocator();
}
return $this->service_locator_interface;
}
/**
* #return DoctrineEntityManager
*/
protected function getEntityManager() {
if(!($this->entity_manager instanceof \DoctrineEntityManager)) {
$this->entity_manager = $this->getServiceLocator()->getServiceLocator()->get('Doctrine\ORM\EntityManager');
}
return $this->entity_manager;
}
/**
* Get request object
*
* #return Request
*/
public function getRequest()
{
if (!$this->request) {
$this->request = $this->getServiceManager()->get('Request');
}
return $this->request;
}
}

Error: Cannot be accessed directly CakePHP

Element file I am calling:
$brand = $this->requestAction('brands/buyer_getnames/');
Action file I am calling:
public function buyer_getnames(){
$newid=$this->Auth->User('brand_id');
$name=$this->Brand->find('first',array('conditions'=>array('Brand.id'=>$newid),'recursive'=>-1));
return $name['Brand']['name'];
}
Getting error below..!!
Private Method in BrandsController
Error: BrandsController::buyer_getnames() cannot be accessed directly.
Please help
Request action respects normal url routing rules
If you're using prefix routing then you can't access function prefix_foo() via a url of the form /controller/prefix_foo - it needs to be the corresponding prefix url: /prefix/controller/foo.
As such your request action call should be:
$brand = $this->requestAction('/prefix/brands/getnames/');
Note that if the only thing that method does is call a model method, you're better off simply doing:
$model = ClassRegistry::init('Brand');
$brand = $model->someMethod();
You can allow unauthorized access to action if your action is requested with requestAction method.
For example:
public function beforeFilter() {
parent::beforeFilter();
if ($this->request->is('requested') && $this->request->params['action'] == 'index') {
$this->Auth->allow(array('index'));
}
}
This may also work (haven't tested):
public function index() {
if ($this->request->is('requested')) {
$this->Auth->allow(array('index'));
}
}
let me know if i can help you more.

Working with namespaced payloads in Backbone.js

I'm working on a project where the request and response payloads are all namespaced. For example:
{
'SourceMachine':{
'Host':'some value',
'Description':'some value',
'UserName':'some value',
'Password':'some value'
}
}
To be able to do get() and set() on the individual fields, I overrode the parse method in my "source_model" like so:
parse : function(response, xhr) {
return response.SourceMachine;
}
That's all well and good. But the issue is this: When I want to do a POST or PUT operation, I have to get the Host, Description, UserName and Password attributes into the SourceMachine namespace. Basically what I've been doing is copying the model attributes to a temporary object, clearing out the model, then saving as follows:
var tempAttributes = this.model.attributes;
this.model.clear();
this.model.save({SourceMachine: tempAttributes});
This works, but it screams of KLUGE! There's got to be a better way of working with namespaced data. Thanks!
Update
I've now created a require.js module for a base model that I'll be using for all the models in my app, since they all rely on namespaced data:
define(function() {
return Backbone.Model.extend({
/**
* Adding namespace checking at the constructor level as opposed to
* initialize so that subclasses needn't invoke the upstream initialize.
* #param attributes
* #param options
*/
constructor : function(attributes, options) {
//Need to account for when a model is instantiated with
//no attributes. In this case, we have to take namespace from
//attributes.
this._namespace = options.namespace || attributes.namespace;
//invoke the default constructor so the model goes through
//its normal Backbone setup and initialize() is invoked as normal.
Backbone.Model.apply(this, arguments);
},
/**
* This parse override checks to see if a namespace was provided, and if so,
* it will strip it out and return response[this._namespace
* #param response
* #param xhr
* #return {*}
*/
parse : function(response, xhr) {
//If a namespace is defined you have to make sure that
//it exists in the response; otherwise, an error will be
//thrown.
return (this._namespace && response[this._namespace]) ? response[this._namespace]
: response;
},
/**
* In overriding toJSON, we check to see if a namespace was defined. If so,
* then create a namespace node and assign the attributes to it. Otherwise,
* just call the "super" toJSON.
* #return {Object}
*/
toJSON : function() {
var respObj = {};
var attr = Backbone.Model.prototype.toJSON.apply(this);
if (this._namespace) {
respObj[this._namespace] = attr;
} else {
respObj = attr;
}
return respObj;
}
})
});
Create and update operations on models call toJSON to produce the data for the server:
toJSON model.toJSON()
Return a copy of the model's attributes for JSON stringification. This can be used for persistence, serialization, or for augmentation before being handed off to a view.
You could provide your own toJSON:
toJSON: function() {
return { SourceMachine: _(this.attributes).clone() };
}
That should make your server happy. Unfortunately, toJSON is also commonly used to provide data for your templates and you probably don't want the SourceMachine noise in there; if so, then add another method to prepare data for your templates:
// Or whatever you want to call it...
valuesForTemplate: function() {
return _(this.attributes).clone();
}
That's what the standard toJSON method does internally.

Add a subdomain to all urls in Cakephp

I want all Cakephp urls to use a subdomain if it is present in the users session... so if there is an entry 'subdomain:user' in the session of the user all pages will have 'user' as a prefix: Eg user.example.com, user.example.com/settings.
Is there an easy way to do this?
thanks,
kSeudo
There are custom route classes you could use. Create a file in APP/Lib/Route called UserSubdomainRoute.php and put this in it.
<?php
App::uses('AuthComponent', 'Controller/Component');
class UserSubdomainRoute extends CakeRoute {
/**
* #var CakeRequest
*/
private $Request;
/**
* Length of domain's TLD
*
* #var int
*/
public static $_tldLength = 1;
public function __construct($template, $defaults = array(), $options = array()) {
parent::__construct($template, $defaults, $options);
$this->Request = new CakeRequest();
$this->options = array_merge(array('protocol' => 'http'), $options);
}
/**
* Sets tld length
*
* #param $length
* #return mixed void|int
*/
public static function tldLength($length = null) {
if (is_null($length)) {
return self::$_tldLength;
}
self::$_tldLength = $length;
}
/**
* Writes out full url with protocol and subdomain
*
* #param $params
* #return string
*/
protected function _writeUrl($params) {
$protocol = $this->options['protocol'];
$subdomain = AuthComponent::user('subdomain');
if (empty($subdomain)) {
$subdomain = 'www';
}
$domain = $this->_getDomain();
$url = parent::_writeUrl($params);
return "{$protocol}://{$subdomain}.{$domain}{$url}";
}
/**
* Get domain name
*
* #return string
*/
protected function _getDomain() {
return $this->Request->domain(self::$_tldLength);
}
}
One improvement to the class would probably be to make the $Request static.
Unfortunately in Cake 2.0 there is no way to set a defaultRotueClass, however, I added that feature in 2.1+ and I don't want to tell you to upgrade so you are going to have to manually specify it for all your routes in the third param like so:
Router::connect(..., ..., array('routeClass' => 'UserSubdomainRoute');
Be sure to add at the top of routes.php
App::uses('UserSubdomainRoute', 'Lib/Route');
If you do upgrade to 2.1+ you can just add this at the top of your routes.php
Router::defaultRouteClass('UserSubdomainRoute');
Then any routs specified after will use that route class.
The main part of the route class is the _writeUrl method. It checks to see if there is a subdomain key set in the session otherwise uses www and builds the full url to return.
Heads Up: Haven't tested the class, still at school just wanted to give you a jump start. It's just a modifed version of my SubdomainRoute which works a bit differently (it used to only match routes to when a certain subdomain is set, for ex in my app matches clients subdomain to my ClientsAdminPanel plugin. You can grab that here: http://bin.cakephp.org/view/50699397 So you can see how that's done as well if you need a combination of UserSubdomainRoute and my SubdomainRoute (in the link).
Hope this helps for now. Let me know if there are any problems.
Edit:
Here's how to force a redirection - something tells me there's a better way. I'll update if I can think of it.
public function beforeFilter() {
$subdomains = $this->request->subdomains();
$subdomain = $this->Auth->user('subdomain');
if (!empty($subdomain) && !in_array($subdomain, $subdomains)) {
$this->redirect('http://' . $subdomain . '.site.com' . $this->request->here);
}
}
well if you wish to do it for all your links probably you use the same way to show all your url on your site, do you use something like $this->html->link('/blah/asd/','blah'); ?
IN Cake/Routing/Route
class SubdomainRoute extends CakeRoute {
public function match($params) {
$subdomain = isset($params['subdomain']) ? $params['subdomain'] : null;
unset($params['subdomain']);
$path = parent::match($params);
if ($subdomain) {
$path = 'http://' . $subdomain . '.localhost' . $path;
}
return $path;
}
}
When creating links you could do the following to make links pointing at other subdomains.
echo $this->Html->link(
'Other domain',
array('subdomain' => 'test', 'controller' => 'posts', 'action' => 'add')
);

Resources