I'm trying to write a webapp with CakePHP, and like most webapps I would like to create an installer that detects whether the database has been initialized and, if not, executes the installation process.
This process will be entirely automated (it assumes the database itself already exists, and that it is granted full administrative access through the anonymous account with no password...this is for a sandbox environment, so no worries about security), so it needs to be able to detect (regardless of the request!) if the database tables have been created and initialized, and if not, to perform that initialization transparently and then still serve up the user's original request.
I considered writing a sort of Bootstrap controller through which all requests are routed, and a single SQL query is run to determine if the database tables exist, but this seemed cumbersome (and the controller requires a corresponding model, which needn't be the case here). The other possibility is was to override AppModel and place within it the same test, but I was unsure how to do this, as there isn't any documentation along these lines.
Thanks in advance!
tl;dr version: What is the CakePHP equivalent (or how can the equivalent be written for CakePHP) of a J2EE servlet's "init()" method?
Check out the AppError class - may be there is a missing database table error that your can override and run your create database table SQL from there?
Something like this might do what you're looking for, but does require an extra query per model.
<?php
class AppModel extends Model {
var $create_query = false;
function __construct($id = false, $table = null, $ds = null) {
parent::__construct($id, $table, $ds);
if (!empty($this->create_query)) {
$this->query($this->create_query);
}
}
}
?>
Then you would just need to add var $create_query = 'CREATE TABLE IF NOT EXISTS ...;` in your models. The MySQL CREATE TABLE documentation has more information on IF NOT EXISTS.
However, I'm not certain that this isn't trying to call query() too early. Or before the check to see if the table already exists, which it would have to be. Otherwise, CakePHP would error out instead of creating your table. There isn't much documentation on the subject and your best bet for more information going to be to take a look at cake/libs/model/model.php directly.
Update: The code above would not work as written.
After looking into the Model class a little deeper, Model::__construct calls Model::setSource(). Model::setSource() checks to see if the table exists and throws an error if it doesn't. To use this method, that's where you'd have to override and insert the query. The code below may need to differ, depending on which version of CakePHP you're using.
<?php
function setSource($tableName) {
// From Model::setSource(). I believe this is needed to make query() work.
$this->setDataSource($this->useDbConfig);
// Create our table if we have a create query
if (!empty($this->create_query)) {
$this->query($this->create_query);
}
// Now call the parent
parent::setSource($tableName);
}
?>
Do you really want to make every request check that the database exists? This seems terribly wasteful.
The first time it checks it will set up the database, and then for every one of the next million requests it will check again even though the database has of course by this time been set up!
It's a better idea to require that whomever installs your application do some setup. You should provide a separate script that is not run during every request, but is run manually by the person during installation.
Re your comment about the best way to do this with CakePHP, have you looked at Cake's support for database schema migrations?
http://book.cakephp.org/view/734/Schema-management-and-migrations
I thought about doing this at one point in time, and came up with a good way to do it.
First off, a note: I believe your app needs write access to app/config/database.php, so you can write the correct information. I have a workaround, but let me explain the method by which the installer works.
PHP Scripts can rewrite themselves (or delete themselves) on the fly. Since the script is loaded into memory and THEN the bytecode is executed, it isn't dependent upon the file once it is done working. So you could theoretically have something that overrides the AppError class to run a specific controller (say your SetupWizardController) with all the regular, CakePHP stuff loaded. Once you go through the entire wizard, write the database.php file/setup anything else needed, overwrite the AppError and AppController files (I can see you needing the AppController to work differently if the app hasn't been installed in certain cases) with ones that are stored somewhere like in app/vendors/myapp. The script has finished executing and now your checks will not occur on subsequent runs.
This would get rid of the problem Bill Karwin brought up, utilize AppError, and make an installer.
The other way to do it is to include the idea of Environments in your application. Say you store your environments in app/config/environment.php. This file would be given write access to (and that access would be removed at the end of setup), or could be overwritten. Regardless, it would exist whenever someone attempts to deploy your application, and database.php automatically imports the databases from the environment.php file. The constructor of the database.php class would then overwrite it's $default database with the one specified in environment.php. The $default in database.php would reference a NoDB Datasource, which you can check in the AppController (I think you would be able to instantiate a dummy model and check it's datasource), and then push up the required Installer. Obviously you'd overwrite the AppController after this as well.
Related
I am learning OOP and PHP.
I am trying to create a class for file uploads (from here on "File"). When I create an instance of File, I want to call a function that usually succeeds, but sometimes fails (it attempts to connect to a database and create a unique value there. It tries up to 32 times).
If it fails, I don't want any functions of this File instance to work. I don't want to throw errors or anything at this time, but I want to make sure I'm aware of best practices.
Right now I have this in my __construct():
...
$this->id = $this->id_submit_random() or die();
...
After this - if id_submit_random() fails - the __construct() script stops executing on that line, and no methods or properties are accessible. The variable I would assign the class instance to would return as not belonging to that class, too. That's exactly what I want.
Basically, I want to safeguard my code from running and uploading stuff if the database is not accessible or a unique ID can't be generated.
I would like to know - is there another method of achieving this? Are there problems with this one that I'm unaware of? I don't find it in searches, so it's either ugly or problematic, or maybe I shouldn't be trying to do something like this in the first place.
Thank you.
Background:
I have an admin system, that needs to connect to and edit multiple databases. The database structures are 100% similar to each other, but the data varies.
What I have tried:
I'v tried to use $this->Model->setDataSource('db_variable_here'); in the controllers to change the database on the fly. The problem with this, is that all the related data seems to still be from my default database.
Example:
Imagine this: User HABTM Post, if I want to get a post from a different database, and use $this->Post->setDataSource('db_variable_here'); to achieve this, then it seems that I still get the related user from my default database, and not the same as the one I got the post from.
I'm guessing this is due to the fact that I only change the database on the Model Post, so it could be fixed by doing $this->Model->setDataSource('db_variable_here'); for each related model.
My Question:
Is it possible to change the datasource for every model in the app on the fly?
Ex. something like: $this->setDatasource('datasource_name')? Or do I really have to do it manually for all the related models?
Just save the database that you need to use in Session/Cookie (whatever tickles your fancy), then in your AppModel's __constructor() if the Session variable is defined then override either setDataSource() or setSource() accordingly.
Note that IIRC Cake's Session/Cookie are not available on the Models by default (because it's not supposed to be), so you might wanna use the good ol' $_SESSION or $_COOKIE or you will need to load it with App.
I do this to select to either use a Azure SQL database or a Rackspace MySQL database depending on the domain/URL, works as expected.
You could try making your own getDataSouce method in the AppModel.
You can see the CakePHP one here:
https://github.com/cakephp/cakephp/blob/master/lib/Cake/Model/ConnectionManager.php
So, in your AppModel, just make sure to accept/return the da
class AppModel extends Model {
//...
public function getDataSource() {
// some logic here to determine which source you want
// Maybe use Configure::write('MYSOURCE', 'other_datasource');
// somewhere else, then just check it here.
$source = 'default';
$this->setDataSource($source);
return parent::getDataSource();
}
//...
}
This should get called instead of CakePHP's 'getDataSource()', then it will run your checks to determine which connection to use, then calls CakePHP's 'getDataSouce()' to do the rest of the actual work of getting the data source.
Assuming you set a variable (like a Configure variable) that's accessible from here, you could set it once anywhere in the app, and when this runs, it will use whatever you've specified.
I have a master class, DBAPI which contains all the interaction with the database. It's not singleton per se, but is designed to only be instantiated once as $DBAPI.
When I alter the database, I obviously have to add functions to DBAPI to let the site use the new functionality, however, since there are a lot of different actions that can be taken, instead of including everything in a single massive class file, I've split them out by functionality/permission level as traits, and the DBAPI class file is dynamically created by adding traits flagged based off of permission level (read only, read-write etc.). Since the only time the file needs to be created is when new traits are added, I only create the class file if it doesn't exist for that specific user permission level, otherwise I use the already generated file. (If there's a better way to do this I'm all ears).
The issue I'm running into now is that if I add some functions in a new trait, the previously generated classes are obviously not aware of it, and I don't find out about that until I try to use the function in the code somewhere and it fails. It's pointless to write wrappers around every single function call made to check if it is a function first- is there some way to get the DBAPI class to do some action if code attempts to access a method it can't find?
for example, if code calls some function $DBAPI->newfunction() $DBAPI handles the exception itself, running some code that will attempt to update itself, which will cause newfunction() to run if it can be found.
(N. B. This architecture has a really bad code smell. I'm sure there's a better way to do this.)
PHP classes can implement the __call magic method that is used when there is no matching method name.
function __call( $name, $arguments ) {
// Code to run...
}
I am trying to implement some unit tests into a legacy PHP application.
There have been a number of challenges with this, but in particular for this question, I am currently looking at a small class that manages the app config.
The class interface is pretty simple; it does the following:
The constructor calls a Populate method, that uses our Recordset class to load the config for the requested module from the database.
Get method, which returns a specified config value.
Set method, which sets the config value in memory.
Save method, which writes the config update(s) back to the DB.
Testing the Get/Set methods is straightforward; they map directly to private array, and work pretty much as you'd expect.
The problem I have is with testing the database handling. The class uses a number of fields on the config table (module name, language, etc) to determine which config items to load and in what priority. In order to do so, it constructs a series of elaborate SQL strings, and then makes direct calls to the DB to get the correct config data.
I have no idea how to go about writing a unit test for this. Aside from the Get/Set methods, the class consists pretty much entirely of building SQL strings and running them.
I can't see a way to test it sensibly without actually running it against a real DB, and all the issues that go with that -- if nothing else, the complexity of the config loader would mean I'd need at least seven or eight test databases populated with slightly different config. It seems like it would be unmanageable and fragile, which would defeat the point somewhat.
Can anyone suggest how I should proceed from here? Is it even possible to unit test this kind of class?
Many thanks.
I must say I'm not sure I agree that unit tests would be somewhat pointless without hitting the database here. My goal would be to get the business logic that produces the SQL under test, without involving your database. Here is an example of what I'm talking about:
class Foo {
// ... Getters and setters for your config ...
public function doSomeBusinessLogicThenHitDb()
{
$sql = 'SELECT * FROM mytable WHERE ';
$sql .= $this->_doSomethingComplicatedThatInvolvesParsingTheConfig();
$this->_queryDb($sql);
}
protected function _queryDb($sql)
{
// Do something with a PDO or whatever
}
}
Having abstracted the _queryDb() bit to a separate function, you can then write this test:
public function testMyClassUnderSomeCircumstances()
{
// Set up config
$exampleConfig = // whatever
// Set up expected result
$whatTheSqlShouldLookLikeForThisConfig = 'SELECT ... WHERE ...';
// Set up a partial mock that doesn't actually hit the DB
$myPartialMockObject = $this->getMock('Foo', array('_queryDb'), array(), '');
$myPartialMockObject->expects($this->once())
->method('_queryDb')
->with($whatTheSqlShouldLookLikeForThisConfig);
// Exercise the class under test
$myPartialMockObject->setConfig($exampleConfig);
$myPartialMockObject->doSomeBusinessLogicThenHitTheDb();
}
The point of this test is to test the business logic that produces your SQL - not to test your database itself. By putting the expectation in place that the resulting SQL must look like whatever it must look like, you are ensuring that your tests will fail if innocent refactoring of _doSomethingComplicatedThatInvolvesParsingTheConfig() accidentally breaks your code, by making it produce different SQL from what it used to.
If testing the whole application, including its database, is your goal, try a proper integration testing suite like Selenium. Unit tests monitor individual classes and tell you when they've stopped behaving as they're supposed to. You will face problems with speed of execution, and with error localisation (i.e. is the bug even in the code, let alone the class under test, or is it a DB thing?), if you let them overreach.
One straight forward to better test these things is to give your config class an object to access the database, so you can run any config with the real database or just some mock of it that writes to memory instead or even lightweight files if you need persistence for example.
That can be done with creating an adapter with a defined interface. The first adapter you write is for your database.
When the config object is created, you pass in the adapter. As it has a defined interface, the config class can work with any adapter that has that interface. First of all the database.
Then you either mock an adapter or write an adapter of it's own for your tests. Inside your tests, you don't use the database adapter, but the test adapter.
You then can unit-test the config class independent of the database.
I have been using a controller method post directly to perform some db and social network operations but im finding a few points of failure between it and the hardware — so I came up with the idea of storing all the request in a db table to be used as a queuing system instead so I can process them in my own time rather than real time
The thing I'm struggling with now is handling my requests . I know this isn't very MVC — but its quick fix.
How do I call another controller's method from within my process queue method? I have tried including the file and instantiating it — then passing it the variables i would have done from the web.
function process(){
$result = $this->mque->get_all();
include('post.php');
$get = new post();
foreach($result->result_array() as $item){
$get->index($item['rfid'],$item['station_id'],$item['item']);
}
}
but i get an error- when i call the normal index method- it runs fine but i get an undefined method error when call it through the instantiated class method- (this is my problem)
Message: Undefined property: post::$db
The why
I am setting the process queue method to run based on a cron job running at a set interval of time.
Originally everything ran to index method of post — but since post::index() can take 10-15 seconds to run and the reader is not multi threaded — someone could use the reader within 7 seconds and the script wouldn't have run completely.
Is there a better way of doing this rather than using my current process method?
update
there is two ways to do this- either use php to fopen/get from the web
or do it sprogramming using $class->method()- i would prefer to do this the first method but dont really see any option with the error i mentioned before
That's easy: you don't have one controller call another. As a rule, if you need something to exist in two different places, you have two options:
Have them both subclass the same object
pro: That way the method is already there
con: You can only subclass one thing, and you have to build your own class loading system (NOT GOOD)
Have a library (or model) which they both share
pro: The method can then be tested better (it is (or it was at one point) easier to unit test models than it is to test controllers), the code can be shared without a custom class-loading syntax.
con: This may involve a little refactoring (but it should be as easy as moving the code from the controller's method to a library's method and then simply calling the library in the public controller method).
Either one of those would solve your particular problem. Personally, because of how CI loads controllers, my preference is to create libraries.
CodeIgniter: Load controller within controller
Is this something that could help you out quickly? Check the bottom reply.