We have a website that developed in symfony 1.4 framework. This website should be able to have multiple domains. Each domain has its special homepage and everything else. Actually the domain must be such a parameter for each action that according to it, the action gets the data from database and show it.
For example, we have a about us page. We save about us contents in about_us table. This table has a website_id. We keep website information in the website table. Suppose this:
website (id, title, domain)
about_us (id, content, website_id)
website contents:
(1, 'foo', 'http://www.foo.com') and (2, 'bar', 'http://www.bar.com')
about_us contents:
(1, 'some foo', 1) and (2, 'some bar', 2)
The question is, how should I configure my Symfony project, to be able to do like this? to get domain as a parameter and use that in Symfony actions?
You can create your own route class extending sfRoute. This route will add a 'domain' parameter to all requests:
//apps/frontend/lib/routing/myroute.class.php
class myRoute extends sfRoute
{
public function matchesUrl($url, $context = array())
{
// first check if it is a valid route:
if (false === $parameters = parent::matchesUrl($url, $context))
{
return false;
}
$domain = $context['host'];
// add the $domain parameter:
return array_merge(array(
'domain' => $domain
), $parameters);
}
}
Routing.yml (example):
default_module:
class: myRoute
url: /:module/:action/:id
...
In your action you get the domain with:
$request->getParameter('domain');
There are many ways for doing this.
You could extend the sfFrontWebController, and add extra code inside the dispatch() method.
# app/myapp/config/factories.yml
all:
controller:
class: myController
// lib/myController.class.php
class myController extends sfFrontWebController
{
public function dispatch()
{
$selectedSite = SiteTable::retrieveByDomain($_SERVER['HTTP_HOST']); // Example
if (!$selectedSite) {
throw new sfException('Website not found');
}
// Store any site value in parameter
$this->context->getRequest()->setParameter('site_id',$selectedSite->getId());
parent::dispatch();
}
}
Related
I am trying to mock the constructor returned by require('falcor'); I have two routes and one calls the other route using var dataModel = new falcor({source: this});
Code looks like so
var falcor = require('falcor');
module.exports = {
route: 'items',
get: function (pathSet) {
var dataModel = new falcor({source: this});
var ids = '1';
dataModel.get('itemIds', ids).then(function (response) {
// Code I can't get to in Jasmine 1.x tests
});
}
}
I want the constructor to return a spy so I can call Promise.resolve and send back mock data for testing purposes. I'm not sure how to do this without moving the call into another module that I can mock separately. I think some questions that may help me here are
Where do I find the constructor functions defined by modules like falcor? I have tried looking into the 'global' object but have had no luck. If I did find this constructor, could I just replace it with a spyOn(global, 'falcor').andReturn(/* object with a mocked get method*/); ?
Is there a better way that makes testing easier to call a route from inside another route?
Thanks for any help.
To start w/ question 2: yes, to get data from another route, return refs to that route. Don't instantiate another model w/i the route. E.g.
const itemsRoute = {
route: 'items[{keys:indices}]',
get(pathSet) {
// map indices to item ids, likely via DB call
// in this case, SomeDataModel handles network requests to your data store and returns a promise
return SomeDataModel.getItemsByIndices(pathSet.indices)
.then(ids => ids.map((id, idx) => ({
path: ['items', pathSet.indices[idx]],
value: {
$type: 'ref',
value: ['itemById', id]
}
})));
}
};
const itemByIdRoute = {
route: 'itemById[{keys:ids}].name',
get(pathSet) {
return new Promise((resolve) => {
resolve(pathSet.idx.map(id => ({
path: ['itemById', id, 'name'],
value: {
$type: 'atom',
value: `I am item w/ id ${id}`
}
})));
});
}
};
When a request comes in for (e.g.) [items, 2, name], it will hit the first items route, resolve [items, 2] to [itemById, someId], and resolve the remaining name key in the itemsById route.
As for question 1: rather than mocking falcor, just mock whatever you are using to make the remote call to your data source. In the above case, just mock SomeDataModel
I have a simple service which I'm using to POST data to a Rails controller.
My service looks something like this:
app.service('autoRulesService', function($http) {
return({
createRule: createRule
});
function createRule() {
var request = $http({
method: 'POST',
url: '/rules.json',
data: { one: 'two }
});
return request.then(handleSuccess, handleError);
}
function handleSuccess() {
// body omitted...
}
function handleError() {
// body omitted...
}
});
I use that service in my controller in a pretty standard way:
$scope.saveRule = function() {
rulesService.createRule().then(function() {
// do stuff...
});
}
The problem is that I get a weird unwanted key in my parameters when I inspect the sent data in the Rails log. Where is the "rule" parameter coming from?
Processing by AutoAnalysis::RulesController#create as JSON
Parameters: {"one"=>"two", "rule"=>{}}
It doesn't appear in the request payload (as inspected in Chrome Dev tools)
and my controller action is pretty standard (there's no before filters either):
class RulesController < ApplicationController
def create
# NOTE: I'm referencing an :auto_analysis_rule parameter here because
# that's my desired param key name. It doesn't exist in the request
# as shown here.
render json: Rule.create(params[:auto_analysis_rule])
end
end
and I can't find any mention of $http inferring a root JSON key from the URL or anything in the docs.
Where is the "rule" param key coming from?
Rails automatically wraps parameters that are attributes of the model. If one were an attribute of the Rule model, the payload would look like: {"rule" => {"one" => "two"}}
This functionality removes the need for a top-level key that contains all attributes. In other words, the following payloads would be treated the same if attr1 and attr2 are fields in the MyModel model:
{ "mymodel" : { "attr1" : "val1", "attr2" : "val2" } }
{ "attr1" : "val1", "attr2" : "val2" }
This functionality can be disabled per-controller or app-wide in an initializer. Check out this answer for more information: Rails 3 params unwanted wrapping
My MVC 4 web service has a new use case. I need to pass a list of arguments on the query string to a Web API, e.g.,
http://host/SomeWebApi?arg=x&arg=y
I previously did this simply and easily in my Web Site controller using ICollection<string> arg as a parameter in the controller. That is working now, but it is a Web page, as opposed to an API.
Now I am knocking my head against the wall trying to get a Web API version of the same thing to work. I've made a simple test interface, below, and the collection arg is always null. I've tried List<string> and string[] as types as well. What am I overlooking?
Route register:
config.Routes.MapHttpRoute(
name: "Experiment",
routeTemplate: "Args",
defaults: new
{
controller = "Storage",
action = "GetArgTest",
suite = UrlParameter.Optional,
},
constraints: new
{
httpMethod = new HttpMethodConstraint(new HttpMethod[] { new HttpMethod("GET") })
}
);
Web API controller code:
public string GetArgTest(ICollection<string> suite)
{
if (suite == null)
{
return "suite is NULL";
}
else
{
return "suite is NON-NULL";
}
}
Test query string that results in "suite is NULL":
http://localhost:5101/Args?suite=1&suite=2
I came across this answer ApiController Action Failing to parse array from querystring and discovered that to resolve this you need to put in the FromUriAttribute. So in your example:
public string GetArgTest([FromUri]ICollection<string> suite)
{
}
This makes sense I guess, typically with an API controller you'd expect to be doing more POST and PUT requests for which you'd typically include post data rather than a QueryString.
so, using urlManager i've been able to get to this point:
'people/<id:\d+>/<last:.*?>/<name:.*?>'=>'person/view',
which generates this: http://foo.com/people/3/smith/john
I want to eliminate the ID number, which in this case is "3"
I tried using this:
'people/<last:.*?>/<name:.*?>'=>'person/view', //makes sense right?
however, when i try to navigate to http://foo.com/people/smith/john , I get a server 400 error.
What gives? Should I try modifying the .htaccess file instead?
As requested here is my entire urlManager component:
'urlManager'=>array(
'urlFormat'=>'path',
'showScriptName'=>false,
'rules'=>array(
'about' => array('site/page', 'defaultParams' => array('view' => 'about')),
'contact'=>'site/contact',
'login' =>'site/login',
'/'=>'site/index',
'people/<last:.*?>/<name:.*?>'=>'person/view',
'<controller:\w+>/<action:\w+>/<id:\d+>'=>'<controller>/<action>',
'<controller:\w+>/<action:\w+>'=>'<controller>/<action>',
),
),
Here is my PersonController.php file:
public function actionView()
{
$person=$this->loadModel();
$this->render('view',array(
'model'=>$person,
));
}
private $_model;
public function loadModel()
{
if($this->_model===null)
{
if(isset($_GET['last']))
{
$this->_model=Person::model()->findByPk($_GET['id']);
}
if($this->_model===null)
throw new CHttpException(404,'The requested page does not exist.');
}
return $this->_model;
}
Your view action needs to accept the names and do a find by the attributes instead of using loadModel, which requires the ID:
public function actionView($last, $name)
{
$person = Person::model()->find(
'name=":name" AND last=":last"',
array(':name'=>$name, ':last'=>$last)
);
if($person === null) throw new CHttpException(404);
$this->render('view',array(
'model'=>$person,
));
}
Do note that your request will not support multiple people with same first and last names!
You probably need a rewrite condition for URLs so requests are still routed back to index.php.
check out this guide: http://www.yiiframework.com/doc/guide/1.1/en/topics.url#user-friendly-urls
look at the actionView of the PersonController
public function actionView($id,...)
{
}
So the view action must need an $id parameter for it to work. Otherwise it throws a 400 bad request error. In your modified url rule the only parameters specified are $last and $name. The solution is to remove the $id parameter to actionView() if it is not used inside actionView. Otherwise you have to change function logic for determining the required person from $last and $name instead of $id
I have a table, heading, that has an import_profile_id. import_profile has a bank_id.
On my headings list page in my admin, I'd like to add the ability to filter by bank_id. However, since heading doesn't have a bank_id - it needs to go through import_profile to get that - I can't just add a bank_id field and expect it to work.
Can anyone explain how to do this? The closest thing I've found is this post but I don't think it really addresses my issue.
This can be done by using virtual columns like the post you found. The virtual column is a way to add a new criteria to filter using the autogenerated filter provided by symfony. It works like this:
1 - Go to the generator.yml of the admin module and add the name of the virtual column that will create and add
<!-- apps/backend/modules/module_name/config/generator.yml -->
filter:
[virtual_column_name, and, other, filter, columns]
2 - In your lib/filter/{TableName}FormFilter.class.php (I think in your case must be HeadingFormFilter) you have to define that virtual column in the configure() method
public function configure()
{
//Type of widget (could be sfWidgetFormChoice with bank names)
$this->widgetSchema['virtual_column_name'] = new sfWidgetFormInputText(array(
'label' => 'Virtual Column Label'
));
//Type of validator for filter
$this->validatorSchema['virtual_column_name'] = new sfValidatorPass(array ('required' => false));
}
3 - Override the getFields() of that class to define it in the filter and set the filter function
public function getFields()
{
$fields = parent::getFields();
//the right 'virtual_column_name' is the method to filter
$fields['virtual_column_name'] = 'virtual_column_name';
return $fields;
}
4 - Finally you have to define the filter method. This method must be named after the add...ColumnQuery pattern, in our case must be addVirtualColumnNameColumnQuery(not a happy name choice :P), so
public function addVirtualColumnNameColumnQuery($query, $field, $value)
{
//add your filter query!
//for example in your case
$rootAlias = $query->getRootAlias();
$query->innerJoin($rootAlias . '.ImportProfile ip')
->andWhere('ip.BankId = ?', $value);
//remember to return the $query!
return $query;
}
Done! You can know filter by bank_id.