I want to make some simple objects/models for unspecified frameworks/systems.
Additionally I want to use MySQL as backend for my data.
My goal is simple implementation - small changes to a configuration file and thats basically it.
My problem is that I'm convinced I need to check, when using my model, that the database tables actually exists - and if not, create the table(s) for me.
I was thinking something like:
<?php
class MyObject
{
public function __construct()
{
$dal->query("SHOW TABLES LIKE 'MyTable'");
if($dal->num_rows() == 0)
$this->_createTables();
}
...
}
?>
But I'm worried about the performance with this model - I'm looking for either confirmation on the efficiency of my solution or a better solution.
In my opinion, and depending on your application's needs, you might be better off checking for this condition only if an error occurs, and to assume that the table exists otherwise. Something like this would avoid the extra query on every instance (you should, however, put the check into its own method).
public function insert($data, $spiralingToDeath=false)
{
// (do actual insertion here)
if ($this->isError) {
// nothing obvious
if ($spiralingToDeath) {
// recursion check
throw new DBException("Tried to create a table and failed.");
} else {
$dal->query("SHOW TABLES LIKE 'MyTable'");
if($dal->num_rows() == 0) {
$this->_createTables();
}
// try again:
$this->insert($data, true);
}
}
}
What about CREATE TABLE IF NOT EXISTS?
Require the user to change the configuration through your editor and make your editor modify the table layout before it saves the configuration.
also, you may want to consider just calling 'show tables', then caching it at the class level in a private var, so you could do a simple isset later, eg.
isset($this->tables_cache[$db_name][$table_name])
this would allow you to scale a bit better with more tables.
you could also save this as a json structure or serialized struct in your filesystem, loading it on __construct of your class, and (re)saving it on __destruct.
Related
I am aware that there's a question similar to this, with postgres, but that uses raw Sql, and I am looking for a laravel migration friendly ORM solution. That's why this question is different. This may in the long run be the right answer but I thought I'd pose it to the community.
Sometimes my data set is a table, sometimes it's view. If we are running the unit test build, this exists as a table, but everywhere else it's a view.
This is what I got.
Obviously this is not idea. It's not an accurate conditional. While it may execute when I want it to, It's not a true check if a view exists. I want a way to check the postgres schema to see if a view exists.
if ('dev' === env('APP_ENV') || 'production' === env('APP_ENV')) {
$sql = 'DROP VIEW IF EXISTS backend.friends';
DB::statement($sql);
} else {
Schema::dropIfExists('backend.friends');
}
Mostly I need a conditional. Something like this.
Any ideas good stack friends?
if (backed.friends) == view {
$sql = 'DROP VIEW IF EXISTS backend.partners';
} else {
Schema::dropIfExists('backend.friends');
}
Thank you!
The answer to this, is what Tim Lewis Suggested, staudenmeir/laravel-migration-views package.
The way we use this is the following:
Since we already have the namespace class Schema in use by laravel, we need to alias it.
use Staudenmeir\LaravelMigrationViews\Facades\Schema as ViewSchema;
Then you can perform conditionals and actions such as:
if (!empty($view) && ViewSchema::connection('<schema>')->hasView($view)) {
ViewSchema::dropViewIfExists('<schema>.' . $view);
}
(replace <schema> to apply to your situation of course)
In our case, we are migrating from one schema to another and we need to be able to see if views exists otherwise create or drop them depending on the situation.
Basically everything you could do before on tables, now can be applied to views.
ViewSchema::connection('old_schema')->hasView('example')
ViewSchema::dropViewIfExists('old_schema.'.$exampleView);
Here's the repo, thank you staudenmeir for making this exist!
https://github.com/staudenmeir/laravel-migration-views
I'm sure there's more functionality, but this is all we have needed thus far.
I'm querying big chunks of data with cachephp's find. I use recursive 2. (I really need that much recursion sadly.) I want to cache the result from associations, but I don't know where to return them. For example I have a Card table and card belongs to Artist. When I query something from Card, the find method runs in the Card table, but not in the Artist table, but I get the Artist value for the Card's artist_id field and I see a query in the query log like this:
`Artist`.`id`, `Artist`.`name` FROM `swords`.`artists` AS `Artist` WHERE `Artist`.`id` = 93
My question is how can I cache this type of queries?
Thanks!
1. Where does Cake "do" this?
CakePHP does this really cool but - as you have discovered yourself - sometimes expensive operation in its different DataSource::read() Method implementations. For example in the Dbo Datasources its here. As you can see, you have no direct 'hooks' (= callbacks) at the point where Cake determines the value of the $recursive option and may decides to query your associations. BUT we have before and after callbacks.
2. Where to Cache the associated Data?
Such an operation is in my opinion best suited in the beforeFind and afterFind callback method of your Model classes OR equivalent with Model.beforeFind and Model.afterFind event listeners attached to the models event manager.
The general idea is to check your Cache in the beforeFind method. If you have some data cached, change the $recursive option to a lower value (e.g. -1, 0 or 1) and do the normal query. In the afterFind method, you merge your cached data with the newly fetched data from your database.
Note that beforeFind is only called on the Model from which you are actually fetching the data, whereas afterFind is also called on every associated Model, thus the $primary parameter.
3. An Example?
// We are in a Model.
protected $cacheKey;
public function beforeFind($query) {
if (isset($query["recursive"]) && $query["recursive"] == 2) {
$this->cacheKey = genereate_my_unique_query_cache_key($query); // Todo
if (Cache::read($this->cacheKey) !== false) {
$query["recursive"] = 0; // 1, -1, ...
return $query;
}
}
return parent::beforeFind($query);
}
public function afterFind($results, $primary = false) {
if ($primary && $this->cacheKey) {
if (($cachedData = Cache::read($this->cacheKey)) !== false) {
$results = array_merge($results, $cachedData);
// Maybe use Hash::merge() instead of array_merge
// or something completely different.
} else {
$data = ...;
// Extract your data from $results here,
// Hash::extract() is your friend!
// But use debug($results) if you have no clue :)
Cache::write($this->cacheKey, $data);
}
$this->cacheKey = null;
}
return parent::afterFind($results, $primary);
}
4. What else?
If you are having trouble with deep / high values of $recursion, have a look into Cake's Containable Behavior. This allows you to filter even the deepest recursions.
As another tip: sometimes such deep recursions can be a sign of a general bad or suboptimal design (Database Schema, general Software Architecture, Process and Functional flow of the Appliaction, and so on). Maybe there is an easier way to achieve your desired result?
The easiest way to do this is to install the CakePHP Autocache Plugin.
I've been using this (with several custom modifications) for the last 6 months, and it works extremely well. It will not only cache the recursive data as you want, but also any other model query. It can bring the number of queries per request to zero, and still be able to invalidate its cache when the data changes. It's the holy grail of caching... ad-hoc solutions aren't anywhere near as good as this plugin.
Write query result like following
Cache::write("cache_name",$result);
When you want to retrieve data from cache then write like
$results = Cache::read("cache_name");
Let's keep it simple.
A project has just two models:
User (hasMany Project)
Project (belongsTo User)
Users are only allowed to perform actions on the projects which they own. No one else's.
I know how to manually check who the logged in user is and whether or not he/she owns a specific project, but is there a better, more global way to do this? I'm looking for a more D.R.Y. way that doesn't require repeating the same validation inside multiple actions. For example, is there a config setting like maybe...
Configure::write('Enforce_belongs_to', true);
...or maybe a setting/option on the Auth component.
Maybe this is crazy, but I figured I'd ask.
Adding to Nunser's answer, here would be a general concept of how the behavior would be. You can then attach it to the applicable model.
class StrongBelongBehavior extends ModelBehavior
{
public function beforeFind( Model $Model, $query = array() ) {
$query['conditions'] = array_merge( (array)$query['conditions'], array( $Model->alias.'.user_id' => CakeSession::read("Auth.User.id" ) );
return $query;
}
public function beforeSave( Model $Model ) {
$projectId = Hash::get( $Model->data, 'Poject.id' );
if( $projectId ) {
$Model->loadModel('UserProject'); // UserProject is a custom model
$canEdit = $Model->UserProject->projectIDExists( $projectId ); // returns true if projectId belongs to the current user
if ( ! $canEdit ) {
return false;
}
}
return true;
}
}
I'm not sure if what I'm answering is the best-utermost-dry-it's-almost-dehydrating approach, but is the simplest thing I could think of.
In the Project model, create a function that return an array of project ids associated to an user.
class Project extends AppModel {
public function getByUserId($userId) {
$projectsArray = array();
if ($userId != "valid")
//do all the checks, if it's not null, numeric, if the id exists, etc
$projects = $this->Project->find('all', array('conditions'=>
array('user_id'=>$userId)));
if (!empty($projects)) {
foreach($projects as $i => $project)
$projectsArray[] = $project['Project']['id'];
}
return $projectsArray;
}
}
You mention a find('first') in your comment, but I'm assuming you want all the projects related to the user instead of just the first. If not, it's a simple modification of that function. Also, I'm just getting the ids, but you may want an $id=>$name_project array, up to you.
Now, I don't know what you mean by "only allowed to perform actions", is it just edits that are restricted? Or lists or views should be restricted and not even shown to the user if the project is not his/hers?
For the first case, restrict editing, modify beforeSave.
public function beforeSave($options = array()) {
if(!$this->id && !isset($this->data[$this->alias][$this->primaryKey])) {
//INSERT
//not doing anything
} else {
//UPDATE
//check if project inside allowed projects array
$allowed = $this->getByUserId(CakeSession::read("Auth.User.id"));
if (!in_array($this->id, $allowed))
return false; //or throw error and catch it in the controller
}
return true;
}
The code is untested, but in general terms, you prevent the edit of a project that is not "the user's" just before the update of the record. I assume the insert of new projects is free for everyone. According to this post, all saving functions except saveAll pass through this filter first, so you will need to overwrite the saveAll function and add a validation similar to the one in beforeSave (as explained in the answer there).
And for the second part, filtering results so the user isn't even aware that there are other projects instead of his/hers, change beforeFind. The docs talk about restricting results based on user's roles, so I guess we're on the right track.
public function beforeFind($queryData) {
//force the condition
$allowed = $this->getByUserId(CakeSession::read("Auth.User.id"));
$queryData['conditions'][$this->alias.'.user_id'] = $allowed;
return $queryData;
}
Since the $allowed array has just id values, it'll work like an IN clause, but if you change that array structure, be sure to modify these functions accordingly.
And that's it. I'm thinking about the more basic cases here, edit, view, delete... Ups, delete... change the beforeDelete function also, to avoid any evil users who want to delete others property. The logic remains the same (check if project id is in allowed array, if not, return false or throw error), so I won't add the example of that function here. But that's the basic stuff. If for some reason you want to have the allowed projects in the controller, call the getByUserId function in beforeFilter and handle that ids array there. You can even store it in session, but you'll have to have in mind maintaining that session when adding or deleting projects.
If you want a superadmin that can see and edit everything, just add a condition in getByUserId that checks the role of the user, and if it is an admin, return all projects.
Also, keep in mind: maybe Project has many... subprojects (not much imagination), and so, the user related to the project can add subprojects, but the same evil user as before modifies the hidden project_id that subproject has and edits it. In that case, I recommend you also add a validation in Subproject to avoid actions on models related to Project that are not his. If you have the Security component in place and the edit and delete actions can just be reached by forms, this is a minor thing because Security Component well used avoids form tampering. But give it a thought to see if you need to validate "Subproject" instances also.
As Ayo Akinyemi mentioned, you can use all this as a behavior. I haven't personally done so, but it meets the requirements, all the callbacks modified here are what you modify in a behaviour. You'll have to encapsulate the logic, column names (need to be variable an not set hardcoded, like user_id), etc, but it will be reusable in any other cake project you'll have. Something like StrongBelongBehavior or MoreDRYBehavior. And share it if you do it :)
I'm not sure if Auth component has some way of doing what you want, but that would be the best option I guess. Until some enlightens me (I haven't investigate much this issue), this is the solution I'd use.
OK, I am just trying to get better at making more loosely coupled classes etc in PHP just to improve my skills. I have a local test database on my computer and for the user table I have a column named "role". I am trying to build a function that is a general function for getting permissions for a user so it doesn't depend on a specific task they are trying to do.
When a user tries to do something such as create a new forum topic etc, I want to query the database and if "role" is a certain value, store permissions in a multidimensional array like the following:
$permissions = array(
'forums' => array("create", "delete", "edit", "lock"),
'users' => array("edit", "lock")
);
Then I want to be able to search that array for a specific permission without typing the following at the top of every PHP file after a user posts a form by checking isset($var). So if the user is trying to edit a user I want to be able to do something like the following via a class method if possible
if (Class::get_permissions($userID),array($permissionType=>$permission))) {
// do query
} else {
// return error message
}
How would be a good way to have a loosely coupled permission checking function that will be able to do something like this? It doesn't have to be laid out exactly like this but just be loosely coupled so it can be reused and not be bound to a certain task. But I want to be able to have an array of permissions instead of just "admin","user", etc for reusability and so it doesn't restrict my options down the road. Because I have a bunch of code that is like this right now in the top of my php script files.
if (Class::get_permissions($userID) == "admin") {
// allow query
} else {
// return error
}
Thanks for any input to help me get this to where I don't keep writing the same stuff over and over.
Your question is a little vague, but I will do my best. You said you're storing their permissions in an array $permissions.
public static $permissions = array();
public static function setPermissions($perms)
{
if (!is_array($perms)) {
throw new Exception('$perms must be an array.');
}
self::$permissions = $perms;
}
public static function hasPermission($section, $action)
{
if (array_key_exists($section, self::$permissions)
&& in_array($action, self::$permissions[$section])
) {
return true;
}
return false;
}
Using that logic, when you read a user's permissions from the DB, then set the Class::$permissions static var like so:
Class::setPermissions($permissions);
// ...
if (Class::hasPermissions($page, $action)) {
// user has permission
}
Note, my code is pretty generic and will have to remain that way until I have more information. For now, I'm assuming your permissions array is using a page section as the index and the array is a list of actions within that page section that the user has access to. So, assuming $page has been set to something like "forums" and the user is currently trying to perform an edit (so $action = 'edit'), the Class::hasPermission() function would return true.
I ran out of characters in the comments... But this is to your comment.
#corey instead of having a static object, I include a function that sets my permissions in the user's session. It as part of my LoginCommand class that gets called whenever the user logs in, obviously.
The permissions are then stored from view to view and I don't have to keep querying. The permissions check for most things only happen when the user logs in. However, certain sensitive things I'll run another query to double check. This has the disadvantage that, if the user's permissions change while the user has an active session, these changes won't be pushed to the user.
Remember to exercise good session security.
PHP Session Security
The only reason you wouldn't store data in your session size is because your session got too big. But unless you sessions are megabyte's, you probably don't need to worry about this.
Suppose I have 2 identical table having same structure(Call it 'tableA' & 'tableB').
I want to save certain data on table 'A' and certain data on table 'B'.
NOW I want to use the same MODEL for both the table.
I want to change the table linked with the Model(say 'ModelM') to change dynamically based on condition at the controller.
e.g.
In controller:- //sample code
function saveProduct(){
$this->loadModel('ModelM');
if(condition){
$this->ModelM->useTable = 'A';
}else{
$this->ModelM->useTable = 'B';
}
$this->ModelM->save($this->data);
}
ADDITION ON 14th JANUARY 2011
Following is the copy/paste of code I am working on:
function experiment(){
$tableName = 'temp_table'.'1234';
mysql_query('CREATE TABLE '.$tableName.' LIKE temp_home_masters');
$sql = $this->createInsertQuery($new_arr,$tableName);
$status = mysql_query($sql);
if($status){
echo "saved successfully";
}else{
echo "error";
}
$this->NewHomeMaster->setSource($tableName);//NewHomeMaster was previously attached to a different table , here I want to change the tableName the model linked with dynamically.Model 'NewHomeMaster' already exists and uses a table ...Here I am willing to link this model to the newly created tempory table.//
$home_details=$this->paginate('NewHomeMaster',array($new_conditions));
mysql_query('DROP table '.$tableName);
}
UNFORTUNATELY THIS DOES NOT WORK...
I originally set out to figure a solution to your problem, but the more I think about it, I believe your logic is flawed.
I can see how the fact that the tables are similar or identical can lead you to the decision of using a single model to interact with both tables. However, when you look at a what a model is supposed to be (In CakePHP basically an interface to a table), it doesn't make sense to switch back and forth.
The CakePHP docs explain models like this:
In object-oriented programming a data model is an object that represents a "thing", like a car, a person, or a house.
In your example, you really have two separate "things" that look exactly the same. Therefore, they should have their own models.
If your models are really going to have the exact same methods, then "the CakePHP Way" would be to define a custom Behavior that encapsulates your shared methods. Then attach the behavior to both models.
Then you can load the model you need in the Controller condition:
private $DynamicModel;
public function saveProduct() {
if (condition) {
App::import('Model', 'ModelZ');
$this->DynamicModel = new ModelZ;
} else {
App::import('Model', 'ModelY');
$this->DynamicModel = new ModelY;
}
$this->DynamicModel->save($this->data);
}
Do this on your controllers before filter function:
$this->CakePHPModelName->setSource('table_name');
This will use different table.
Source: http://www.mainelydesign.com/blog/view/changing-cakephps-model-usetable-on-fly
The situation is tricky, as Stephen describes it, because your approach somewhat violates the MVC conventions.
However, if you're willing to go to the dark side of custom hacks, you could consider creating you own customized Datasource in CakePHP that handles this kind of logic for you. An option is to extend a given Datasource (presumably the MySQL one) with you own custom logic that aims to perform some prelimary filtering/conditioning before interacting with the database. Not that clean because the logic is placed in the wrong scope, but could work. Have a look here for a start: http://book.cakephp.org/view/1075/DataSources
Alternatively, you could create two different models and make them share the same logic using a behavior. This kinda limits you to take the choice of model earlier in the flow (and thus doesn't only affect the location of data storage), but might be a possibility.