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
Related
I have the following URL on my webpage upon pagination
http://localhost:9000/employee?p=2
I need to prompt to not found page whenever the parameter "p" is change. example:
http://localhost:9000/employee?b=2
It need the controller to input a notFound. what kind of condition will i do to do this?
Reference:
Controller:
#Transactional(readOnly=true)
public static Result list(int pageNum, int listSize) {
employeeMap.clear();
Page page = appModel.page(pageNum, listSize);
employeeMap = ListUtil.getEmpMap(employeeMap, page.getEmpList());
AppModel employees = new AppModel(employeeMap, searchMap);
/* if statement initiate a notFound page if pageNum us not the expected value */
if (pageNum < 0 || pageNum > page.getPage().get("intLastPage")) {
return notFound("<h1>Page not found</h1>").as("text/html");
}
/* if statement that put a not search found message if no employee is found */
if (page.getEmpList().size() == 0) {
flash("success", "There is no search results for the specified conditions");
}
return ok(index.render(appModelForm.fill(employees),page));
}
Routes:
# Employee list (look at the default values for pagination parameters)
GET /employee controllers.Application.list(p:Int ?= 1,l:Int ?= 125)
You could prevent people from switching that name of the parameter overall by changing your routing. But to achieve all the possibilities outlined by what you want to do, you could do the following:
GET /employee/:p/:l controllers.Application.list(p:Int ?= 1,l:Int ?= 125)
GET /employee/p/:p controllers.Application.list(p:Int, 125)
GET /employee/l/:l controllers.Application.list(1, l:Int)
It depends on how you handle the URL calling in the template, but if you can have that auto-generate the default parameters into the URL if the user does not put them in, you could just keep the first one by itself.
The URL to summon your controller will now instead be:
http://localhost:9000/employee/p/2
http://localhost:9000/employee/l/4
http://localhost:9000/employee/2/4
And then you can route anything else to a not found controller method:
GET /employee/--String, empty or whatever else--- controllers.Application.returnNotFound
#Transactional(readOnly=true)
public static Result returnNotFound() {
return notFound("<h1>Page not found</h1>").as("text/html");
}
I'm new at phpspec (coming from phpunit) and I have problems setting the behavior of a mock returned by another mock.
I'm creating a wrapper class around the Guzzle client and I want to check the output of the response.
Here's the spec:
function it_gets_response_status_code(Client $client, Url $url, Response $response)
{
$this->beConstructedWith($client);
$url->__toString()->willReturn('http://example.com');
$data = ['foo' => 'bar'];
$response->getStatusCode()->willReturn(200);
$client->request('POST', $url, ['form_params' => $data])->willReturn($response);
$this->post($url, $data);
assert($this->getResponseStatusCode() === 200); // Failing! :(
}
and the corresponding functions in my class:
public function post(Url $url, array $data)
{
$this->response = $this->client->request('POST', (string) $url, ['form_params' => $data]);
}
public function getResponseStatusCode()
{
return $this->response->getStatusCode();
}
The assertion is failing and when I check what is this status code, I see that instead of the integer 200, it's an instance of PhpSpec\Wrapper\Subject. What am I missing here?
I've searched and googled but cannot find resources about using the mock returned by another mock in phpspec. I'm wondering if the reason for this is that it's a code smell? If so I'd be glad to see how I could do this differently (currently I cannot see how I could keep the code simple and doing differently).
try:
assert($this->getResponseStatusCode()->getWrappedObject() === 200);
this:
$response->getStatusCode()->willReturn(200)
returns a '200' string wrapped in an Subject object, on which you can then make mock/stub calls if needed. To get the real value of the subject you need to call getWrappedObject
So far, I have figured out how to return a typical JSON response in Zend Framework 2. First, I added the ViewJsonStrategy to the strategies section of the view_manager configuration. Then, instead of returning a ViewModel instance from the controller action, I return a JsonModel instance with all my variables set.
Now that I've figured that piece out, I need to understand how to render a view and return it within that JSON response. In ZF1, I was able to use $this->view->render($scriptName), which returned the HTML as a string. In ZF2, the Zend\View\View::render(...) method returns void.
So... how can I render an HTML view script and return it in a JSON response in one request?
This is what I have right now:
if ($this->getRequest()->isXmlHttpRequest()) {
$jsonModel = new JsonModel(...);
/* #todo Render HTML script into `$html` variable, and add to `JsonModel` */
return $jsonModel;
} else {
return new ViewModel(...);
}
OK, i think i finally understood what you're doing. I've found a solution that i think matches your criteria. Though i am sure that there is room for improvement, as there's some nasty handwork to be done...
public function indexAction()
{
if (!$this->getRequest()->isXmlHttpRequest()) {
return array();
}
$htmlViewPart = new ViewModel();
$htmlViewPart->setTerminal(true)
->setTemplate('module/controller/action')
->setVariables(array(
'key' => 'value'
));
$htmlOutput = $this->getServiceLocator()
->get('viewrenderer')
->render($htmlViewPart);
$jsonModel = new JsonModel();
$jsonModel->setVariables(array(
'html' => $htmlOutput,
'jsonVar1' => 'jsonVal2',
'jsonArray' => array(1,2,3,4,5,6)
));
return $jsonModel;
}
As you can see, the templateMap i create is ... nasty ... it's annoying and i'm sure it can be improved by quite a bit. It's a working solution but just not a clean one. Maybe somehow one would be able to grab the, probably already instantiated, default PhpRenderer from the ServiceLocator with it's template- and path-mapping and then it should be cleaner.
Thanks to the comment ot #DrBeza the work needed to be done could be reduced by a fair amount. Now, as I'd initially wanted, we will grab the viewrenderer with all the template mapping intact and simply render the ViewModel directly. The only important factor is that you need to specify the fully qualified template to render (e.g.: "$module/$controller/$action")
I hope this will get you started though ;)
PS: Response looks like this:
Object:
html: "<h1>Hello World</h1>"
jsonArray: Array[6]
jsonVar1: "jsonVal2"
You can use more easy way to render view for your JSON response.
public function indexAction() {
$partial = $this->getServiceLocator()->get('viewhelpermanager')->get('partial');
$data = array(
'html' => $partial('MyModule/MyPartView.phtml', array("key" => "value")),
'jsonVar1' => 'jsonVal2',
'jsonArray' => array(1, 2, 3, 4, 5, 6));
$isAjax = $this->getRequest()->isXmlHttpRequest());
return isAjax?new JsonModel($data):new ViewModel($data);
}
Please note before use JsonModel class you need to config View Manager in module.config.php file of your module.
'view_manager' => array(
.................
'strategies' => array(
'ViewJsonStrategy',
),
.................
),
it is work for me and hope it help you.
In ZF 3 you can achieve the same result with this code
MyControllerFactory.php
public function __invoke(ContainerInterface $container, $requestedName, array $options = null)
{
$renderer = $container->get('ViewRenderer');
return new MyController(
$renderer
);
}
MyController.php
private $renderer;
public function __construct($renderer) {
$this->renderer = $renderer;
}
public function indexAction() {
$htmlViewPart = new ViewModel();
$htmlViewPart
->setTerminal(true)
->setTemplate('module/controller/action')
->setVariables(array('key' => 'value'));
$htmlOutput = $this->renderer->render($htmlViewPart);
$json = \Zend\Json\Json::encode(
array(
'html' => $htmlOutput,
'jsonVar1' => 'jsonVal2',
'jsonArray' => array(1, 2, 3, 4, 5, 6)
)
);
$response = $this->getResponse();
$response->setContent($json);
$response->getHeaders()->addHeaders(array(
'Content-Type' => 'application/json',
));
return $this->response;
}
As usual framework developer mess thing about AJAX following the rule why simple if might be complex Here is simple solution
in controller script
public function checkloginAction()
{
// some hosts need to this some not
//header ("Content-type: application/json"); // this work
// prepare json aray ....
$arr = $array("some" => .....);
echo json_encode($arr); // this works
exit;
}
This works in ZF1 and ZF2 as well
No need of view scrpt at all
If you use advise of ZF2 creator
use Zend\View\Model\JsonModel;
....
$result = new JsonModel($arr);
return $result;
AJAX got null as response at least in zf 2.0.0
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();
}
}
I want to give admin the option to change the URL identifier of MyCustomModule from backend.
E.g.: www.mydomain.com/identifier
What I did is the following:
In etc/system.xml
<identifier translate="label">
<label>SELF URL Identifier</label>
<frontend_type>text</frontend_type>
**<backend_model>press/config_identifier</backend_model>** // edited after answer
<sort_order>1</sort_order>
<show_in_default>1</show_in_default>
<show_in_website>1</show_in_website>
<show_in_store>1</show_in_store>
<comment>(eg: domain.com/identifier)</comment>
</identifier>
In helper/data.php
public function getUrl($identifier = null)
{
if (is_null($identifier)) {
$url = Mage::getUrl('').self::getListIdentifier();
} else {
//$url = Mage::getUrl(self::getListIdentifier()).$identifier;
**$url = Mage::getUrl(self::getListIdentifier(), array('identifier' => $identifier,'_use_rewrites'=>true)); //edited
}**
return $url;
}
after that i created a model file identifier.php :
class FME_Press_Model_Config_Identifier extends Mage_Core_Model_Config_Data
{
protected function _afterSave()
{
if ($this->isValueChanged()) {
$path = $this->getValue();
// for each $store and $id combination...
Mage::getModel('core/url_rewrite')
->loadByIdPath('press/'.$store.'/'.$identifier)
->setRequestPath($path.'/'.$identifier)
->save();
}
}
}
in config.xml i wrote this:
<events>
<controller_front_init_routers>
<observers>
<press>
<type>singleton</type>
<class>FME_Pres_Controller_Router</class>
<method>initControllerRouters</method>
</press>
</observers>
</controller_front_init_routers>
</events>
and also this is present in my file, m not sure whether it is relevant :
<adminhtml>
<args>
<modules>
<FME_Press_Override before="Mage_Adminhtml">FME_Press_Override_Admin</FME_Press_Override>
</modules>
</args>
</adminhtml>
NOTE: I had been told to make some changes in Controller/Router.php but I don't know what changes to make.
If you want I can add that code also?
Now, what else should I do?
I feel changing the application's router is entirely the wrong approach to take. It is messy and can be easily broken if another module overrode it for a similar purpose. The clean way is with URL rewrites.
You want it to be alterable so you cannot use a fixed XML based rewrite. Instead let's look at the built in rewrite system.
First in your module's etc/config.xml file set up a normal controller.
<frontend>
<routers>
<MyCustomModule>
<use>standard</use>
<args>
<module>Example_MyCustomModule</module>
<frontName>customlist</frontName>
</args>
</MyCustomModule>
</routers>
</frontend>
Here the front name used is customlist, that will always work and shouldn't conflict with any other front name, the rewritten name shall be in addition to this. Now whenever you generate an URL (perhaps in a helper function) you do so to this apparently fixed front name.
$url = Mage::getUrl('customlist', array(
'id' => $id, // 'id' will get used in the "target path" later
'_use_rewrites' => true
));
Note that the variable identifier ($id) is passed to the getUrl function rather than simply appending to it's result. If the function returns an URL with a query (&) or fragment (#) the identifier could have been appended to the wrong part.
The next step is to create rewrite records for every possible combination of identifier and store. You probably have a finite number of lists so this is possible, perhaps identifiers are particular to stores so only need to be defined once each. Either loop through all your lists in an installer script or have each list create rewrites when it is saved.
$path = Mage::getStoreConfig('custom/config/identifier', $storeId);
// Change 'custom/config/identifier' to match the path used in system.xml
$rewrite = Mage::getModel('core/url_rewrite')
->loadByIdPath('customlist/'.$store.'/'.$id);
if ($rewrite->getId()) {
// A rewrite already exists, you might want to skip creating another
continue;
}
Mage::getModel('core/url_rewrite')
->setStoreId($storeId)
->setIsSystem(true) // set to false to allow admin to edit directly
->setOptions('RP') // Redirect Permanent 301
->setIdPath('customlist/'$storeId.'/'.$id) // should never change
->setTargetPath('customlist/index/index/id/'.$id) // what gets used
->setRequestPath($path.'/'.$id) // the path used in the browser
->save();
So now if the admin sets the URL path to be "foo/bar" and requests the page "www.mydomain.com/foo/bar/3" it will be rewritten to "customlist/index/index/id/3" and the method Example_MyCustomModule_IndexController::indexAction() will be called. The file containing that will of course be app/code/local/Example/MyCustomModule/controllers/IndexController.php and the 3 value is retrieved there:
public function indexAction()
{
$id = $this->getRequest()->getParam('id'); // 'id' was specified in getUrl()
// use $id here...
}
It should work by now but what if a list is removed? The rewrites need to be updated for each store. Models have a _beforeDelete method, override it for your list objects.
protected function _beforeDelete()
{
Mage::getModel('core/url_rewrite')
->loadByIdPath('customlist/'.$storeId.'/'.$this->getId())
->delete();
return parent::_beforeDelete();
}
Similarly they need to be updated to match changes in configuration.
etc/system.xml
<identifier translate="label">
<label>SELF URL Identifier</label>
<frontend_type>text</frontend_type>
<backend_model>myCustomModule/config_identifier</backend_model>
...
</identifier>
Model/Config/Identifier.php
class Example_MyCustomModule_Model_Config_Identifier
extends Mage_Core_Model_Config_Data
{
protected function _afterSave()
{
if ($this->isValueChanged()) {
$path = $this->getValue();
// for each $store and $id combination...
Mage::getModel('core/url_rewrite')
->loadByIdPath('customlist/'.$store.'/'.$id)
->setRequestPath($path.'/'.$id)
->save();
}
}
}