CakePHP Router: how to pass the first parameter null? - cakephp

My action is defined like this:
function actionName($par1 = null, $par2 = null)
I need a special route looking like: example.com/r/ABCD which should call
SomeController->actionName(null, "ABCD")
How is it to achieve with Cake's Router::connect() ?

You can try reversing the params like
function actionName( $par2 = null, $par1 = null)

1.- arg1 optional, arg2 not optional
I would change the order of the parameters, so the first parameter is truly optional.
2.- arg1 optional, arg2 not optional, without reordering
(not the best idea, but I assumed business rules required it)
Make a wrapper function.
SomeController {
wrapperFun(arg2) {
actualFunction(null, arg2)
}
actualFun(arg1, arg2) {
// do something
}
}
Now you can have:
SomeController/wrapperFun/ABC
SomeController/actualFun/arg1/ABC
And then with the magic of routing, you can redirect actualFun/ABC to wrapperFun/ABC. So the user will only see pretty URLs.
3.- arg1 optional, arg2 optional
So you have 4 possible options, right?
somecontroller/action/arg1/arg2
somecontroller/action/arg1
somecontroller/action/arg2
somecontroller/action
I believe this is your actual question:
How do I deal with functions that have multiple optional arguments?
In this cases what you need to use are associative arrays. That is actually how CakePHP does it in many parts. It looks like this:
$arr = {"arg2" => "some val"};
// calling the function with $arr
// some_action($arr);
some_action($argArr) {
$arg1 = isset($argArr["arg1"]) ? $argArr["arg1"] : null;
$arg2 = isset($argArr["arg2"]) ? $argArr["arg2"] : null;
}
Now, about the URLs, that is for you to decide. You must have a mechanism that will tell you what value is represented by each section of the URL. What comes to mind are two options:
First, use place holders (ugly):
some_controller/some_action/arg1/non/arg3/non/arg4
Or, use wrapper actions, similar to what is seen before in my answer (ugly too).
some_controller/action5/arg5
// then in the wrapper (action5) you have:
action5(arg) {
$arr = {"arg5" => arg};
some_action($arr);
}
As you can see this can get out of hand easily. I don't know what you are actually trying to do, but it seems that you are sending data by the wrong channel.
You might want to use a POST and use an array directly, instead of building your own, check out the request object.

Related

Can I use same param name multiple times in the URL for codeigniter-restserver?

http://example.com/api/transfer/transfers/code/456/code/234
When using $this->get('code') on a url like above I expect the REST library to return an array or a list of the codes.
Instead it returns the last one.
Is there a way to return both values in a list, or is there another recommandation for formating the URL.
Thank you
I know it has been long since you posted the question. However it could help other people looking for the same thing.
Assuming that transfer is your controller and transfers is the function, another way to format your url could be:
http://example.com/api/transfer/transfers?code[]=456&code[]=234
This was you perform $this->get('code') you'll get an array back.
If you are creating the url via code then you may use http_build_query(). It handles the necessary escaping. It means it will replace [ for %5B and ] for %5D, in this case.
The code would be like:
$codes = array(456, 234);
$query = http_build_query(array('code' => $data));
$url = 'http://example.com/api/transfer/transfers?' . $query;

How do I create a Flow with a different input and output types for use inside of a graph?

I am making a custom sink by building a graph on the inside. Here is a broad simplification of my code to demonstrate my question:
def mySink: Sink[Int, Unit] = Sink() { implicit builder =>
val entrance = builder.add(Flow[Int].buffer(500, OverflowStrategy.backpressure))
val toString = builder.add(Flow[Int, String, Unit].map(_.toString))
val printSink = builder.add(Sink.foreach(elem => println(elem)))
builder.addEdge(entrance.out, toString.in)
builder.addEdge(toString.out, printSink.in)
entrance.in
}
The problem I am having is that while it is valid to create a Flow with the same input/output types with only a single type argument and no value argument like: Flow[Int] (which is all over the documentation) it is not valid to only supply two type parameters and zero value parameters.
According to the reference documentation for the Flow object the apply method I am looking for is defined as
def apply[I, O]()(block: (Builder[Unit]) ⇒ (Inlet[I], Outlet[O])): Flow[I, O, Unit]
and says
Creates a Flow by passing a FlowGraph.Builder to the given create function.
The create function is expected to return a pair of Inlet and Outlet which correspond to the created Flows input and output ports.
It seems like I need to deal with another level of graph builders when I am trying to make what I think is a very simple flow. Is there an easier and more concise way to create a Flow that changes the type of it's input and output that doesn't require messing with it's inside ports? If this is the right way to approach this problem, what would a solution look like?
BONUS: Why is it easy to make a Flow that doesn't change the type of its input from it's output?
If you want to specify both the input and the output type of a flow, you indeed need to use the apply method you found in the documentation. Using it, though, is done pretty much exactly the same as you already did.
Flow[String, Message]() { implicit b =>
import FlowGraph.Implicits._
val reverseString = b.add(Flow[String].map[String] { msg => msg.reverse })
val mapStringToMsg = b.add(Flow[String].map[Message]( x => TextMessage.Strict(x)))
// connect the graph
reverseString ~> mapStringToMsg
// expose ports
(reverseString.inlet, mapStringToMsg.outlet)
}
Instead of just returning the inlet, you return a tuple, with the inlet and the outlet. This flow can now we used (for instance inside another builder, or directly with runWith) with a specific Source or Sink.

puppet hiera array, loop and hash

I have currently an issue between hiera/puppet:
In my hiera I have:
mysql_user_mgmt:
- mysql_user: 'toto#localhost'
mysql_hash_password: '*94BDCEBE19083CE2A1F959FD02F964C7AF4CFC29'
mysql_grant_user: 'toto#localhost/*.*'
mysql_user_table_privileges: '*.*'
- mysql_user: 'test#localhost'
mysql_hash_password: '*94BDCEBE19083CE2A1F959FD02F964C7AF4CFC29'
mysql_grant_user: 'test#localhost/*.*'
mysql_user_table_privileges: '*.*'
In my puppet, I'm trying to make a loop to get data from hiera:
$mysql_user_mgmt = hiera('mysql_user_mgmt',undef)
define mysql_loop () {
$mysql_hash_password = $name['mysql_hash_password']
notify { "mysql_hash_password: ${mysql_hash_password}": }
}
mysql_loop { $mysql_user_mgmt: }
but I'm getting some weird errors. Can someone help me to figure out how to make the loop?
Resource titles are strings. Always.
You are trying to use the the title of a mysql_loop resource to feed a hash to the type definition. That does not work. A stringified version of the hash will end up being used instead, and your later attempts to retrieve components by hash index will fail, likely with some kind of type error.
You have a few options:
You could restructure your definition and data a bit, and pass the aggregate data as a hash parameter. (Example below.)
You could restructure your definition and data a bit, and use the create_resources() function.
If you've moved up to Puppet 4, or if you are willing to enable the future parser in Puppet 3, then you could use one of the new(ish) looping functions such as each().
Example of alternative (1):
Reorganize the data to a hash of hashes, keyed on the user id:
mysql_user_mgmt:
'toto#localhost':
mysql_hash_password: '*94BDCEBE19083CE2A1F959FD02F964C7AF4CFC29'
mysql_grant_user: 'toto#localhost/*.*'
mysql_user_table_privileges: '*.*'
'test#localhost':
mysql_hash_password: '*94BDCEBE19083CE2A1F959FD02F964C7AF4CFC29'
mysql_grant_user: 'test#localhost/*.*'
mysql_user_table_privileges: '*.*'
Modify the definition:
define mysql_user ($all_user_info) {
$mysql_hash_password = $all_user_info[$title]['mysql_hash_password']
notify { "mysql_hash_password: ${mysql_hash_password}": }
}
Use it like so:
$mysql_user_mgmt = hiera('mysql_user_mgmt',undef)
$mysql_user_ids = keys($mysql_user_mgmt)
mysql_user { $mysql_user_ids: all_user_info => $mysql_user_mgmt }
(The keys() function is available from the puppetlabs-stdlib module.)

CakePHP 2.x: Is the $primary flag on Model::afterFind() actually useful?

CakePHP's Model::afterFind() callback looks like:
afterFind(array $results, boolean $primary = false)
According to the documentation:
The $primary parameter indicates whether or not the current model was the model that the query originated on or whether or not this model was queried as an association. If a model is queried as an association the format of $results can differ.
They can differ, but experimentation shows that they don't always differ. As far as I can tell, the $primary parameter isn't actually all that useful. If it's set to false you may or may not get a flattened data structure, so you may or may not wind up with the dreaded "cannot use string offset as an array" error message.
Although I haven't tried it yet, my thought based on the documentation was to ignore the $primary flag altogether and just check the data:
public function afterFind($results, $primary = false) {
if (array_key_exists(0, $results) {
// operate on $results[0]['User']['fieldname']
} else {
// operate on $results['fieldname']
}
return $results;
}
This is hackish and I don't like it, but it seems likely to be more useful than $primary.
Explicitly stated, my questions are:
What is the $primary flag actually useful for?
Am I correct that it is not useful for determining the structure of the $results array, or have I missed something there?
Indeed the $primary parameter seems to only be useful in warning you of cases where the format of $results is unpredictable. It is not useful in determining the format of $results.
More information here: https://groups.google.com/forum/?fromgroups=#!topic/cake-php/Mqufi67UoFo
The solution offered there is to check !isset($results[$this->primaryKey]) to see what format $results is. This is also a bit of a hack, but arguably better than checking for a key '0'.
The solution I ultimately came up with is to do something like this:
public function afterFind($results, $useless) {
// check for the primaryKey field
if(!isset($results[$this->primaryKey])) {
// standard format, use the array directly
$resultsArray =& $results;
} else {
// stupid format, create a dummy array
$resultsArray = array(array());
// and push a reference to the single value into our array
$resultsArray[0][$this->alias] =& $results;
}
// iterate through $resultsArray
foreach($resultsArray as &$result) {
// operate on $result[$this->alias]['fieldname']
// one piece of code for both cases. yay!
}
// return $results in whichever format it came in
// as but with the values modified by reference
return parent::afterFind($results, $useless);
}
This reduces code duplication because you don't have to write your field alteration logic twice (once for an array and once for non-array).
You may be able to avoid the references stuff altogether by just returning $resultsArray at the end of the method, but I wasn't sure what issues that might cause if CakePHP (or some other parent class) expects $results in the way it was passed in. Plus this way doesn't have the overhead of copying the $results array.
If you can't always rely on primaryKey being in the fields list AND you know the key you are looking for, you can get away with something a bit more simple. Here is an example:
/**
* Decrypt password
*
* #see Model::afterFind()
*/
public function afterFind($results, $primary = false) {
if (!empty($results['password'])) {
$results['password'] = Security::rijndael($results['password'], Configure::read('encrypt.key'), 'decrypt');
return $results;
}
foreach ($results as &$r) {
if (!empty($r[$this->alias]['password'])) {
$r[$this->alias]['password'] = Security::rijndael($r[$this->alias]['password'], Configure::read('encrypt.key'), 'decrypt');
}
}
return $results;
}
I ran into this issue. The accepted answer works good. However, I had to make a minor adjustment. If you're looking to modify a field for example construct a fully qualified file name from logo, it's better to create a new field, as "return parent::afterFind($results, $useless);" will do it twice if the model find is called from some other model.
foreach($resultsArray as &$result) {
// operate on $result[$this->alias]['fieldname']
// one piece of code for both cases. yay!
// Added logoFull instead of modifying logo
if(isset($result[$this->alias]['logo'])){
$result[$this->alias]['logoFull'] = Configure::read('urlImg') . 'logos' . DIRECTORY_SEPARATOR .
'carrier' . DIRECTORY_SEPARATOR . $result[$this->alias]['logo'];
}
}
Answers in the book...
The $primary parameter indicates whether or not the current model was
the model that the query originated on or whether or not this model
was queried as an association. If a model is queried as an association
the format of $results can differ;
Code expecting $primary to be true will probably get a "Cannot use
string offset as an array" fatal error from PHP if a recursive find is
used.
Thus it could be useful in certain situations for logic processing and may could be used to have a knock on effect into your $results

how to force drupal function to not use DB cache?

i have a module and i am using node_load(array('nid' => arg(1)));
now the problem is that this function keep getting its data for node_load from DB cache.
how can i force this function to not use DB cache?
Example
my link is http://mydomain.com/node/344983
now:
$node=node_load(array('nid'=>arg(1)),null,true);
echo $node->nid . " -- " arg(1);
output
435632 -- 435632
which is a randomly node id (available on the system)
and everytime i ctrl+F5 my browser i get new nid!!
Thanks for your help
Where are you calling this? For example, are you using it as part of your template.php file, as part of a page, or as an external module?
Unless you have this wrapped in a function with its own namespace, try naming the variable differently than $node -- for example, name it $my_node. Depending on the context, the 'node' name is very likely to be accessed and modified by Drupal core and other modules.
If this is happening inside of a function, try the following and let me know what the output is:
$test_node_1 = node_load(344983); // Any hard-coded $nid that actually exists
echo $test_node_1->nid;
$test_node_2 = node_load(arg(1)); // Consider using hook_menu loaders instead of arg() in the future, but that's another discussion
echo $test_node_2->nid;
$test_node_3 = menu_get_object(); // Another method that is better than arg()
echo $test_node_3->nid;
Edit:
Since you're using hook_block, I think I see your problem -- the block itself is being cached, not the node.
Try setting BLOCK_NO_CACHE or BLOCK_CACHE_PER_PAGE in hook_block, per the documentation at http://api.drupal.org/api/drupal/developer--hooks--core.php/function/hook_block/6
You should also try to avoid arg() whenever possible -- it's a little bit of a security risk, and there are better ways to accomplish just about anything arg() would do in a module environment.
Edit:*
Some sample code that shows what I'm referring to:
function foo_block ($op = 'list', $delta = 0, $edit = array()) {
switch ($op) {
case 'list':
$blocks[0] = array(
'info' => 'I am a block!',
'status' => 1,
'cache' => BLOCK_NO_CACHE // Add this line
);
return $block;
case 'view':
.....
}
}
node_load uses db_query, which uses mysql_query -- so there's no way to easily change the database's cache through that function.
But, node_load does use Drupal's static $nodes cache -- It's possible that this is your problem instead of the database's cache. You can have node_load clear that cache by calling node_load with $reset = TRUE (node_load($nid, NULL, TRUE).
Full documentation is on the node_load manual page at http://api.drupal.org/api/drupal/modules--node--node.module/function/node_load/6
I have had luck passing in the node id to node_load not in an array.
node_load(1);
According to Druapl's api this is acceptable and it looks like if you pass in an array as the first variable it's loaded as an array of conditions to match against in the database query.
The issue is not with arg(), your issue is that you have caching enabled for anonymous users.
You can switch off caching, or you can exclude your module's menu items from the cache with the cache exclude module.
edit: As you've now explained that this is a block, you can use BLOCK_NO_CACHE in hook_block to exclude your block from the block cache.

Resources