I am fairly new to php and drupal. I am wanting to create a module that will insert the registration data into a second database. From what I have been able to read there is two ways to do this (http://drupal.org/node/18429), I am leaning towards doing this on the fly for not everyone who installs a drupal module will have access or permissions to edit there sites/default/settings.php file. What I have is a settings page that requests information about the second database (name of database, username, password, host and port) that is saved into a table of the drupal database. What I have so far is a call to the database for each bit of information (5 calls to the database) what I am wondering is there a way I could make one call to the database and save each bit of information into a variable?
Also is there a way that you could give a message at the top of the admin/build/modules page once someone enables the module informing them to go to the settings page for the module and setting database information? I was looking at hook_load but I don't think that is what I need.
Comments
Yes and yes... there is a way
Yes and yes... there is a way to do everything! But, it sure helps to see what code you already have. It's just too hard to speculate on what you're working with and much easier for us to get it if we see what's going on. : )
But to answer your second
But to answer your second question which is easier, create a MYMODULE.install file. In that file do something like this:
'status' refers to the type of message that is set.
I have not yet created a
I have not yet created a submit button or validate function to write the information to the database, the way I have it now there is 5 functions and 5 calls to the database, if I can I would like only one call to the database to get the 5 pieces of information required, don't matter if it in 1 function or 5 or more.
freeradius.module file
freeradius.install, the freeradius_schema was generated from the schema module, and checked if uninstall and install works, still need to edit description fields in the schema. Just recently added the database port and have not yet added it to the schema.
Okay, thanks. So, generally
Okay, thanks.
So, generally when working with databases you want to have a Primary Key field that always has a unique value so you can reference a single row and get all the data from that row. Normally primary keys are integers and auto-increment. In your schema you can set it up as type 'serial' and that will auto-increment the field. Basically, any time you're planning to fetch specific data from a table you want to have a primary key or a single field with which you can retrieve all the records you need in one query. And even in cases where you need data from two tables you can use JOIN queries, though they're a little tough to pick up on at first. Anyways, with a primary key it would look something like:
This simple function will pass you an array of the setting object where the Primary Key is the array key and the object with all of the fields is the value. If you know you're only fetching one row then you can use:
...instead of the while loop. Then, to use the data you just use the object like $settings->db_user, $settings->db_pass, etc. If you populate the array of several rows from the database as in the first example then you can use a foreach loop to output the data:
etc.
Lastly, if you are using this form as a settings form have you consider system_settings_form()? It automatically stores variables based on field names, but considering what you are doing with the module it might not work for your purpose.
I hope this helps and I didn't miss what you were asking about! : ) If so then set me straight.
Here is a sample from the module I'm working on now...
...
...and here is a system_settings_form...
...
You can see that I use some functions to grab form data to populate the form. autolink_link_load is used to grab a single row from the database (and the query in that statement is a JOIN that grabs data from two tables simultaneously, so if we can do that five queries is definitely too much!), while autolink_node_types is used to get an array of node types from the nodes table. The system settings form, as shown, is very easy to use and will automatically set your 'variables' in the variables table which can be retrieved through variable_get() as I did in the first function.
Edit: Of course, this isn't all the code from that file so there may be some pieces missing but it's 700 lines and contains some irrelevant code so let me know if you have any questions.
I may be over thinking this
I may be over thinking this (I tend to do this quite often), but to make sure I understanding this I will go through a few things. This started out to be a small project but the longer I work on this (and get to thinking) the more I think it be better if I did this.. how about adding this.. but this is getting dangerous for everything I think about (other then the simple stuff like adding it to a form (it added to form but the functions may not be written to make it work)) sends me either to google or drupal to figure out how to do it in php/drupal because of the inexperience I have in php and drupal, but I am getting off subject here. Things didn't go as planed and didn't get to work on this today but went through the post and making sure I understanding this before I continue.
Basically I have right under the opening php tags the following:
this basically gets read when the module gets loaded and because the six $db* variables have not been set or saved on the settings page they all would return blank or NULL. So basically what I would need to do is create a variable (using your settings example) and add this above the other variables that would retrieve the whole table row for the freeradius table located in my drupal database
The table information is one table I don't see a use for adding more (so the auto increment would have only incremented to 1) doing this I could take out the $dbtype, $dbname, $dbuser, $dbpass, $dbhost, $dbport variables above then when I needed the information like in the form '#default_value I would do:
This would be called from the form for the appropriate setting.
By the way, you'll see my
By the way, you'll see my code and commenting are different than yours. I know you're probably not concerned about this now, but just an FYI, Drupal has strict coding standards and the more you practice them the easier they come. Check out the coder module (http://drupal.org/project/coder) it will review your code for standards compliance and help you learn a lot.
Had family over and didn't
Had family over and didn't have much time to look at it, but I did change the database schema to now have an ID field also added a database type field so it can be selected between mysql and postgres, as well as manually added information to the database (from mysql command line) so there as at least some information that can be retrieved. Will be working on the function to call the database and retrieve information in the morning. I did download and enable coder (using drush). I need to more carefully go through the posts and make sure I am understanding how to do this before I get too involved. Most of the comments were only added to the code after it was requested to see what I had.I know I had to go back and better comment it, but it is better that I use the drupal coding standards, should help as I progress. I Thank You for your help thus far and will report back any progress/pitfalls when I work on this in the morning.
Sounds good! When I first
Sounds good! When I first started working with Drupal and even now I live on the Drupal API site. You'd be amazed at the type of information you can get by referencing the API. I just look up function names, see which core modules call them, and then look at those files to get a better understanding of parameters and what not. Drupal core generally follows the standards better than any contrib module, so learning there is best. But I guess I'm a bit of a self learner. Make good use of Drupal functions you can do a lot with them and the code is already there!
It seems I was basically
It seems I was basically putting the chicken before the egg. I wanted to have a way that I could switch to the freeradius database on the fly, using the values of the form fields. At this point in development I don't need to switch databases on the fly (at least not until I can validate, submit and have the values filled in after the submit function has executed). I removed the code required to switch on the fly, reduced the form so it is only dealing with things related to the database (id, dbtype, dbname, dbuser, dbpass, dbhost, dbport), and am working on the validate and submit functions and need a way to pass information between the two without adding all six fields as global variables. Right now if I don't validate it, it works fine but if I validate it when it moves to the submit function which writes to the database it loses some of the values. Once I fix this I be moving back to making single call to the database to retrieve all the table information required. I thank you for you he;p and responses thus far.
Sounds like you're making progress!
Sounds like you're making good progress and learning a lot!
Generally you want to avoid setting global variables wherever you can. They should definitely only be used in certain situations. If possible, you need to pass variables between functions using parameters.
Here are some simple but common form, validation, and submit functions that look like what I might use.
...and validation...
...and submit...
...and another function called by the submit function to update the database...
I wouldn't set a global
I wouldn't set a global variable, unless it was a last resort and researched other options FIRST. This module has got me stuck I believe it is the submit function, I have tried it three different ways. My current validate funtion is now like the following:
This all seems to work, but with problems with the submit function included for completeness. The submit function I have tried three different ways the first would be:
submit function #1
Submit #1 produces NO ERROR either in the admin page or error log. But does not produce the required result in the freeradius table of the drupal database.
Submit #2
Submit #2 produces NO ERROR either in the admin page or error log. But does not produce the required result in the freeradius table of the drupal database.
Submit #3
Submit #3 works, does produce errors on the page but it basically because I am manually inserting the information into the module filling out the page and submitting it. This one is UPDATING the database as I expected the other 2 submit functions to do, it is almost as if it is not getting the $form_state['values']['whatever'] from the form.
Using a dsm($dbtype, $dbname,
Using a dsm($dbtype, $dbname, $dbuser, $dbpass, $dbhost, $dbport); both before and after the $result = db_query (for the update) only produces a mysql value so it is losing the rest of the values prior to the submit function being run. Is it my code or something I am doing that is loosing the values. I am working in Komodo IDE remotely I make changes save to the server then use devel module's empty cache before I enter my values and submit it. But still values are gone before it updates/inserts the values into the database.
The problem that I see
The problem that I see with this is setting form values in your validate function.
Like here:
Generally, I would recommend moving the if statements that set form values to your submit function and indeed would do that myself. Alternatively, if you want to set form values from your validate function you have to use form_set_value() (http://api.drupal.org/api/drupal/includes--form.inc/function/form_set_va...). Basically, the form_set_value() function is specifically made for setting values during the validate function because altered data in $form_state['values'] does not normally get passed on to the submit function, but form_set_value() is what gives you that option.
So you could try:
... or just move that check to the submit function.
If form_set_value() doesn't work then I have seen that some people say you have to set the #parent property or something, but I haven't used form_set_value() enough myself. Check out the API that I linked for more information.
If I'm understanding you right, then I think this could be he solution to your problem!
I tried form_set_value in the
I tried form_set_value in the validate function and gave me an error in the log:
I tried putting it in the submit function and then everything after the dbtype is blank. The way I was able to get it to work was comment out the whole validate function without the the checking in the submit function. The only thing is that it is put in database as 0 and 1 (because there only 2 values in the select list). But it works this way, BUT there is no checking that we are receiving information that we want. It is an admin page so only TRUSTED users are given this role, but I would still rather check. What I could try is leave it in the database as 0 and 1 and then convert it to either mysql or postgres once it is retrieved from the database? I am now at a point that I am being ordered to put this away for the night and move on to something else, so if I can't sneak back before I will pick it up in the morning. I do believe with your guidance I am making extreme progress as well as learning a lot.
That's great! With select
That's great!
With select lists, which I think you are probably using for the options between postgres and MySQL right?
...with select lists or checkboxes or any non textfield, the insert function will store the KEY and not the VALUE of that field. So, if I have select options like:
'0' => 'postgres',
'1' => 'mysql',
... It will store either 0 or 1. Similarly, to set the default value of that field it would be based on the key, '0' or '1', not the value.
Alternatively you can make the key/value pair like 'postgres' => 'POSTGRES', 'mysql' => 'MySQL', and it will store 'postgres' or 'mysql', not the capital lettered versions. This allows you to store the unique keys for the selection rather than the proper values (which may have spaces and not underscores), making it often easier to work with the data later in your application.
So, if you pulled those zeros and ones out of the database, you could use them just like that as the default value for the database type field and it would work properly. Only when you want to display the data 'Postgres' or 'MySQL' will you need to convert it from zeros and ones to proper values.
This is similar to how it is done with content types. Usually the actually node type is the key, and the proper name is the value. However, the KEY or TYPE is what is unique in content types, NAMES are not properly formatted for PHP (with underscores) and so the TYPE is used to identify the content type while the NAME makes it easier for the user to make a selection. The TYPE is stored in the database table and used for programmatically identifying the content type. Only When data needs to be displayed to the user is the proper NAME of the content TYPE retrieved. Hope that makes sense I ramble too much.
About the form_set_value thing, I think that's the same thing that happened to me last time I tried to use it! I saw something about needing to set a #parent value on the form to get it to work properly, but I still have not tried it and generally have avoided it for now. The major Drupal module development book appears to have it wrong and it has caused some confusion.
As for a check of the three submits
Submit #1
Submit #2
First, in submit #2 in your INSERT query, the placeholder values have not been defined. I don't know if it got cut off, but these variables:
were not defined that I could see. That would definitely cause data loss.
Anyways, I rewrote submit #2 to how it might look if I were using it. This is much simpler and it only performs one query and supports the form being edited or being saved new.
And this would go at the top of the form for editing:
On top of this, you would need to set up a different hook_menu() entry that would load the data and populate the form if the settings are being edited and write a function to load the form values of the current edit.
However, this is only best if your database table is going to have multiple records. Is that the case? Or is this just a single settings form that will only insert one record in the database? If that is the case then it would be best to use variable_set()/variable_get() in my opinion.
I can change my form or
I can change my form or database schema and php code to anything that would allow this to work. What my plan is that once I have the id (only needed for inserting and updating information to the database) dbtype, dbname, dbuser, dbpass (will be encrypted once I have this working), dbhost (localhost or external database), dbport (want to default to 3306 if none given). There is only the need for 1 row on the freeradius table of the drupal database. Once I have this information written into the freeradius table I can retrieve it to set up the second database on the fly. So this information will be used to set $db_uri. I don't even mind if I have to start over from scratch as long as I am using drupal/php best practices, learn something new on the way to final achievement and I eventually get there. I am sure there is many things I will learn doing this because of what I want it to do once the sections I am working are are complete and working properly, some of what I don't even know where to begin to look (like having an add button to add a set of fields and once the add has been pressed you then have an add and a delete). Not sure if this be considered a multistep form or multiple submit buttons but at this point that is over my head, I having enough trouble getting a single validate submit button to work and do as I expected it to do.
I have a few thoughts. I have
I have a few thoughts. I have been looking at the stalker module (basically because it is the module used as an example in the Lullabot Module Development video) and this module as well as some of your examples use variable_get/variable_set. This module uses variable_set values in the submit function and it gets or sends it's information in two functions either stalker_set_count or stalker_get_count which has the SELECT and INSERT/UPDATE sql commands. The problem I am seeing with this is they are setting a count on a page view (hook_user), with mine it would be when the submit button was pressed. With that said it seems silly (to me) to have the submit function call a freeradius_save_settings which would have the sql commands currently in the submit function and then when that done continue on using variable_set. The only thing I see as doing it this way I could set a '#default_value' => variable_get('dbname', '$dbname');. I might have to play with it so I get the desired results but that would get all my results with 1 database call wouldn't it or is there a database call for every variable_get('value1', 'value2')?
An advantage to setting
An advantage to setting Drupal variables is that variable_get() and variable_set() are not actually database queries from what I have read. The values are indeed stored in the database but I think they are cached or something, so calling variable_get() several times in a single form is not a performance issue. Indeed, it is often standard in dealing with variables to call the function numerous times on each page load and I have not seen any performance issues from it.
Alternatively, you can set default values in the same way by loading the data from the database when your form is loaded. This requires a separate menu callback that would need to call a function like freeradius_record_load() or something similar to retrieve the data from the database. I could provide an example of how this would be done if you need it. But, in my opinion if your table will only have one record in it then it would be much better to go the variable route. Anything more than one record and you will need to have a callback like this.
Edit: remember that you can create a form that will automatically set variables with system_setting_form(). This type of form needs no submit function or anything and it will set variables with the name of the fields. So if your field is named 'dbuser' then it will variable_set('dbuser', $form_state['values']['dbuser']) automatically. Considering this, you have to prefix your field names with your module name like 'freeradius_dbuser' so your variable does not conflict with another variable. In fact, anything that has a chance to conflict with other modules should be prefixed in this way, which I know you understand from looking at your code.
Another great website I've seen for Drupal tutorials is gotdrupal.com. I've recommended it to a lot of people for learning to use advanced Drupal modules, but I'm not sure how much into development it gets.
I have pretty much got this
I have pretty much got this section completed. I used my own validate and submit functions switched to variable_get and variable_set, no idea what database table drupal uses when you use this but it wasn't the one I set up, but got it working so it is using mine. Everything seems to work even uninstalled the module and reinstalled it (using devel's reinstall module) and everything I am suppose to have in the database table is there. I even have the values filled in when you are returned to the form (other then the password (figured I would get the password characters) but this could be a security issue). Only minor thing left to do is have some text displayed to the user about what happened when they hit the save button and go back and heavily comment the module. The finished validate, submit and load functions follows:
That's great!
That's great! I think I can say you've learned a lot on just this thread. It becomes so much easier once you learn the Drupal API too.
Just as an FYI, the data stored with variable_set() is serialized and stored in the 'variable' table. Serializing means you can store arrays with variable_set() as well, but it also means you can't just pull data straight from the variable table and display it. Besides, variable_get() is faster than a query like I said.
What you'll want to do to display a message is in your submit handler after data is done doing its thing it is normal to do something like:
So, first you set the message that will be displayed to the user, "Did something with 'something'." This message also uses the % placeholder to display one of the form values in the message as you can see. That doesn't have to be done though.
Secondly, it updates the system records by telling watchdog what your module did. "Mymodule" should have the proper name of your module so it can record which module performed the action, and then this message similarly uses a placeholder to use some form data.
Lastly, it redirects the user after the form has been submitted and the records have been updated. Even with this redirection after the drupal_set_message() the user will still see the message on the page that they are redirected to.
I'm curious, in your validate function are the values that you set being saved through the submit function?
i.e.:
I'm interested to better know the limitations on setting form values in validate functions. Originally, I would have thought that using an ampersand (&) in front of $form_state would allow you to set values (&$variable_name normally means the values set on that variable should be usable outside that function), but it didn't work for me.
It looks like you're doing great overall though! I'm glad I got to help.
Remember, with setting variables you can set default values in your form with those variables as well.
Considering that, you don't need a load function to populate form defaults. But you likely will need it for other tasks.
What I was thinking on doing
What I was thinking on doing to display a message to user after they submitted the information. Like an if statement on the $result, if true set drupal_set_message('save_msg', t('Your configuration settings have been saved')); if false do same thing except say there was a problem saving the information and use drupal_set_error instead of drupal_set_message.
As far as this, I would think it is being saved, but the only proof I have is the data is being saved to the drupal freeradius table. With default_value I basically set it to be blank if it first time on the form otherwise set to your previous value (with the load function).
I even got the database switching to work on the fly, I had to alter it but it working.
What I should also mention here is both now and previously all forms were in freeradius.admin.inc and the other functions are in freeradius.module. Basically what is happening with this it is just basically showing the dsm($db_url) with 2 elements and a fieldset. I know the tablesort_sql is not working, that is what I am attempting to tackle to learn next. I needed to set up the database that it will use to be able to attempt it. I have an example link and a pdf file on my system that goes through this.
I'd say using a conditional
I'd say using a conditional to determine if the update was successful and displaying a message based on that is the right thing to do. Much better than just displaying success regardless because that may not be the case.
What I do to accomplish this is call another function from my submit like:
This is a good way to separate your functions into single purposes as they should be. If my_submit_function fails to insert records it returns 'failed' and the message is displayed accordingly.
I did something to really
I did something to really mess things up, in process of going through and commenting everything and get the insert function in it's own function. Trying to figure out what I broke (it writing dbtype, dbname, dbuser, dbpass, dbhost and 0 for dbport to the database). I thought I had it so it would switch databases on the fly (because dsm($db_url); had values for default and freeradius array) but when I try and switch and get a value from the second database it either gives an error that site is off line or produces error about the database (because values I retrieving are not in the drupal database). I thought I was doing good because I have 2 functions that gets a value 1 from drupal database and 1 from the database I want to switch to and puts the results in a select list, but since I broke things it doesn't retrieve the table information from the database. Guess this will teach me to do what I should be doing from the start and comment things as I go.
If I split the submit
If I split the submit function up into 2 a submit and a insert function I get an error about parimeter 2 but if I put it all in one it works with no errors.
Now that this is working I can try and figure out why it is taking my site off line if I try and use the $db_url in an array.
Awesome! Let me know if
Awesome! Let me know if there's anything else you could use some help on. It looks like you're picking this up very quickly and learning by doing has always worked for me!
OK I think I figured out why
OK I think I figured out why database switching was not working. I put some code in to get the headers of a database (the non drupal database) and when I was trying to execute it, it would say my site was off line but I could back out clear cache and go to any other page. At first I thought it was how my module was connecting to the database but if so that was only partly true. I commented out all the code used to display information from the database, then used the $db_url('mydatabase'); and a drupal_set_message('using mydatabase'); under the $db_url array. I manually put my information into the $db_url array and it worked (I didn't use port like my module was) I put it back how it was and it gave an error again, took the port off and it is working, I just have to learn how to display field header and tablesort and so on to display the information I require.
Just a quick note on why the
Just a quick note on why the database switching wasn't working. Your normal $db_url in your settings.php file looks like this.
When I was adding the port I was adding it to the end after the database name. I not tested it, but I would hazzard a guess putting it after hostname might work.
Ahh yes!
Ahh yes!
One last quick question. I
One last quick question. I have some special characters in a select list ( := and +: ) When I hit save on the form tells me something about illegal operation, I tried escaping it with a \ but still same thing. I not sure if it php that complaining about it or mysql any ideas?
You should be able to use
You should be able to use those as the values but they cannot be used as array keys I think.
So you can do:
but I don't think you can do '+' => '+' because '+' is not a valid array key, but #options is an array. That's what I have done in those situations.
The values would actually be
The values would actually be := (colon equals) and :+ (colon plus) there others but using this to test it
Yeah but I'm just sayin
Yeah but I'm just sayin regardless I don't think you can use those symbols as array keys that's all. You should be able to use anything as a value, but not a key. Which is why I put 'equals' => '=' instead of '=' => '=' or ':=' => ':=' or anything else where the key is just some symbols. Just pretty sure array keys cannot contain operators.
With more thought I may be
With more thought I may be able to come up with something more meaningful, but for now I have it like this and it seems to save the proper 0 or 1 value to the database and not give me an illegal operation error when I click the save button, so looks like I good as far as that goes.
Actually, what you have here
Actually, what you have here will indeed save 0 or 1, but that is simply because you did not give the #options array any keys at all, resulting in numerical keys starting at zero. You have only given them values, '0 :=' and '1 :+'. Return values from select form elements will be keys. So you can force the return value as I showed above by setting the keys for the array. Arrays are like 'key' => 'value', 'key' => 'value'. If you just set 'value', 'value', 'value' then those three options will get the keys '0', '1', and '2'.
For instance:
This will return 'equals' if '=' is selected, and 'plus' if '+' is selected. Alternatively, if I just put:
Then it would return '0' if '=' is selected and '1' if '+' is selected.