CakePHP - how to simply reset cached database models - php

I have several apps based on CakePHP and this basically applies to all of them. When my debug mode is set to 0 (live mode), every time I update the database structure, like new tables and fields, then as soon as my app uses those, I always get the default "An Internal Error Has Occurred" message. It is solved if I set debug to 1 and then use those new fields. Is there a better way to do this? I don't want to enable debugging and doing a test write every time I have to update my database. Also /tmp/cache subfolders are empty, so I don't know where it is stored.

Here's a function I wrote to do exactly that.
function clear_cache() {
$cachePaths = array('js', 'css', 'menus', 'views', 'persistent','models');
foreach($cachePaths as $config) {
clearCache(null, $config);
}
}
It uses the clearCache function in Cake.

You need to clear all cache configs
bin/cake cache clear_all
Source

For cake 2.x, you can delete the cache directory like this:
rm -rf app/tmp/cache/

For CakePHP 2.x, place this line of code anywhere in your application to clear the model cache:
Cache::clear(false, '_cake_model_');
This is decoupled from the low-level cache engine (File, Memcache, Redis, etc), so it should work as-is.
CakePHP 2.x Docs: Caching

Related

CodeIgniter - Loading an application package prevents loading of view and dealing with collisions

I've been learning CodeIgniter and was just experimenting with adding Application Packages.
In the default install I've added a package path to the third_party folder that contains a single view, and then I want it to continue loading the default welcome_message. Separately this all works fine, but together the welcome_message view file can't be found apparently. Reading on in the docs at http://ellislab.com/codeigniter/user-guide/libraries/loader.html it mentions view collisions, and talks about setting the second parameter. Okay no problem, there isn't another view named welcome_message, but I do what then mention according to the example provided, which sets it to FALSE to get the welcome_message to display, but that doesn't work.
In fact I have to set it to TRUE to get it to work, which is the exact opposite of the docs. Can someone explain this logic reversal? and regarding view naming collisions due to a lack of a description in the docs does this mean setting the second param to whichever boolean prevents collisions and allow full use of all views regardless of naming? Or does it simply throw an error instead of loading the improper view?
class Welcome extends CI_Controller {
public function index()
{
$this->load->add_package_path(APPPATH . 'third_party/foo_bar/', FALSE);
$this->load->view('foo_bar');
$this->load->view('welcome_message'); // throws err on FALSE and loads on TRUE in add_package_path() call
}
}
After $this->load->view('foo_bar');
reset the path using $this->load->remove_package_path();
When you use add_package_path CI will check that folder for all path requests. This is fine when you are working with a self contained app. When you are done with that and want to access the "regular" CI paths for views etc, you need to remove the package path first.

Renaming a Mongo Collection in PHP

PHP's Mongo driver lacks a renameCommand function. There is reference to do this through the admin database. But it seems more recent versions of the Mongo driver don't let you just "use" the admin database if do don't have login privileges on that database. So this method no longer works. I've also read this doesn't work in sharded environments although this isn't a concern for me currently.
The other suggestion people seem to have is to iterate through the "from" collection and insert into the "to" collection. With the proper WriteConcern (fire and forget) this could be fairly fast. But it still means pulling down each record over the network into the PHP process and then uploading it back over the network back into the database.
I ideally want a way to do it all server-side. Sort of like an INSERT INTO ... SELECT ... in SQL. This way it is fast, network efficient and a low load on PHP.
I have just tested this, it works as designed ( http://docs.mongodb.org/manual/reference/command/renameCollection/ ):
$mongo->admin->command(array('renameCollection'=>'ns.user','to'=>'ns.e'));
That is how you rename an unsharded collection. One problem with MR is that it will change the shape of the output from the original collection. As such it is not very good at copying a collection. You would be better off copying it manually if your collection is sharded.
As an added note I upgraded to 1.4.2 (which for some reason comes out from the pecl channel into phpinfo() as 1.4.3dev :S) and it still works.
Updates:
Removed my old map/reduce method since I found out (and Sammaye pointed out) that this changes the structure
Made my exec version secondary since I found out how to do it with renameCollection.
I believe I have found a solution. It appears some versions of the PHP driver will auth against the admin database even though it doesn't need to. But there is a workaround where the authSource connection param is used to change this behavior so it doesn't auth against the admin database but instead the database of your choice. So now my renameCollection function is just a wrapper around the renameCollection command again.
The key is to add authSource when connecting. In the below code $_ENV['MONGO_URI'] holds my connection string and default_database_name() returns the name of the database I want to auth against.
$class = 'MongoClient';
if( !class_exists($class) ) $class = 'Mongo';
$db_server = new $class($_ENV['MONGO_URI'].'?authSource='.default_database_name());
Here is my older version that used eval which should also work although some environments don't allow you to eval (MongoLab gives you a crippled setup unless you have a dedicated system). But if you are running in a sharded environment this seems like a reasonable solution.
function renameCollection($old_name, $new_name) {
db()->$new_name->drop();
$copy = "function() {db.$old_name.find().forEach(function(d) {db.$new_name.insert(d)})}";
db()->execute($copy);
db()->$old_name->drop();
}
you can use this. "dropTarget" flag is true then delete exist database.
$mongo = new MongoClient('_MONGODB_HOST_URL_');
$query = array("renameCollection" => "Database.OldName", "to" => "Database.NewName", "dropTarget" => "true");
$mongo->admin->command($query);

Getting CakeS3 to work in the CakeShell

I want to be able to call the CakeS3 plugin from the Cake Shell. However, as I understand it components cannot be loaded from the shell. I have read this post outlining strategies for overcoming it: using components in Cakephp 2+ Shell - however, I have had no success. The CakeS3 code here is similar to perfectly functioning cake S3 code in the rest of my app.
<?php
App::uses('Folder','Utility');
App::uses('File','Utility');
App::uses('CakeS3.CakeS3','Controller/Component');
class S3Shell extends AppShell {
public $uses = array('Upload', 'User', 'Comment');
public function main() {
$this->CakeS3 = new CakeS3.CakeS3(
array(
's3Key' => 'key',
's3Secret' => 'key',
'bucket' => 'bucket')
);
$this->out('Hello world.');
$this->CakeS3->permission('private');
$response = $this->CakeS3->putObject(WWW_ROOT . '/file.type' , 'file.type', $this->CakeS3->permission('private'));
if ($response == false){
echo "it failed";
} else {
echo "it worked";
}
}
This returns an error of "Fatal error: Class 'CakeS3' not found in /home/app/Console/Command/S3Shell.php. The main reason I am trying to get this to work is so I can automate some uploads with a cron. Of course, if there is a better way, I am all ears.
Forgive me this "advertising"... ;) but my plugin is probably better written and has a better architecture than this CakeS3 plugin if it is using a component which should be a model or behaviour task. Also it was made for exactly the use case you have. Plus it supports a few more storage systems than only S3.
You could do that for example in your shell:
StorageManager::adapter('S3')->write($key, StorageManager::adapter('Local')->read($key));
A file should be handled as an entity on its own that is associated to whatever it needs to be associated to. Every uploaded file (if you use or extend the models that come with the plugin, if not you have to take care of that) is stored as a single database entry that contains the name of the config that was used and some meta data for that file. If you do the line of code above in your shell you will have to keep record in the table if you want to access it this way later. Just check the examples in the readme.md out. You don't have to use the database table as a reference to your files but I really recommend the system the plugin implements.
Also, you might not be aware that WWW_ROOT is public accessible, so in the case you store sensitive data there it can be accessed publicly.
And finally in a shell you should not use echo but $this->out() for proper shell output.
I think the App:uses should look like:
App::uses('CakeS3', 'CakeS3.Controller/Component');
I'm the author of CakeS3, and no I'm afraid there is no "supported" way to do this as when we built this plugin, we didn't need to run uploads from shell and just needed a simple interface to S3 from our controllers. We then open sourced the plugin as a simple S3 connector.
If you'd like to have a go at modifying it to support shell access, I'd welcome a PR.
I don't have a particular road map for the plugin, so I've tagged your issue on github as an enhancement and will certainly consider it in future development, but I can't guarantee that it would fit your time requirements so that's why I mention you doing a PR.

Symfony2 - something other than bundle

I'm currenly working on the project where i need something orther than bundle. Something i call "Module".
It should be different from the bundle in that when project is starting system doesn't know which "Modules" will be used
and how they will be configured.
Also i'm going to use these modules similar to bundles
$response = $this->forward('AcmeHelloModule:Hello:fancy');
OR
$response = $this->forward('Acme/Hello:Hello:fancy');
Here HelloController->fancyAction(); would be executed. And this controller described say in file /src/modules/Acme/Hello/Controller/HelloController.php
So the question is how to implement this ?
a solution would be to implement a PluginBundle that can dynamicly install, load and run your so called modules.
the PluginBundle would not contain specific plugin code at all, but the runtime environment for you modules/plugins. you may then save in the database which plugins/modules are enabled and load them dynamicly at runtime.
with this sollution it should be possible to create a dynamic plugin mechanism as in wordpress. modifying the AppKernel at runtime is not a good solution because you'd also have to clear the cache when en- disabeling bundles.
In AppKernel add the following method:
public function getBundle($name, $first = true)
{
if (substr($name, -6) == 'Module')) {
return $this->getBundle('ModuleBundle')->getModule($name, $first);
}
return parent::getBundle($name, $first);
}
and all the logic runs in ModuleBundle.
But make sure the type of response is the same as Kernel->getBundle();

Propel Data Load - "default" context does not exist

I am currently stuck on the error The "default" context does not exist. when trying to build my data model with the command symfony propel:build --application=frontend --all --and-load --no-confirmation
After lots of Googling it appears this error is caused by using sfContext inside a model or a form so I have found these and commented them out (see below), the error still occurs, does anyone else know a fix?
>> file- /var/www/html/dev/meeting/config/generated-sfGuardPlugin-schema.xml
>> file- /var/www/html/dev/meeting/config/generated-schema.xml
>> propel load data from "/var/www/html/dev/meeting/data/fixtures"
>> propel load data from "/var/www/html/dev/meeting/plugins/sfGuardPlugin/data/fixtures"
The "default" context does not exist.
grep -R sfContext lib/model/*
lib/model/MeetingMeetings.php: return "";//sfContext::getInstance()->getController()->genUrl('meeting/show?id='.$this->getId(), $full);
lib/model/sfGuardUserProfile.php: //if(!is_null(sfContext::getInstance())&&($useYou||$useYourself)&&$this->getUserId()==sfContext::getInstance()->getUser()->getId()) {
grep -R sfContext lib/form/*
lib/form/MeetingMeetingsForm.class.php: //sfContext::getInstance()->getUser()->setFlash("info",
Many thanks for your time,
Not sure what information I can provide, does anyone have any other questions?
I resolved this problem by doing the following.
First find all references to sfContext in your model files and find an alternative way to get what ever sfContext was needed for (for example passing it to the method).
Check all library files mentioned inside any models for use of sfContext, repeat above solution.
Use is_null checks on sfContexts where it has to exist so it only does if it required.
The problem in my case was my save method used another library which used sfContext to get the current user, which obviously doesn't exist when inserting data to the model
Just ran into this today with data-load. Instead of modifying the function signature, here's what I did.
if(!sfContext::hasInstance())
$configuration = ProjectConfiguration::getApplicationConfiguration('frontend', 'cache', true);
else
$configuration = sfContext::getInstance()->getConfiguration();
And then I can run stuff like this in my model classes.
$configuration->loadHelpers('Texturizer');
I'm not sure what the 'cache' parameter does in getApplicationConfiguration, but I found that line off of the Jobeet book here http://www.symfony-project.org/jobeet/1_4/Propel/en/21
To resolve the problem quickly, comment the code /lib/form/baseForm.class.php , generate the module, and restores the code

Categories