I'm little confused about controller and model in MVC framework (codeIgniter). Its clear to me that controller methods calls the views and Model methods interact with database.
However, I'm little confused about the following types of methods, which are called by methods in a controller.
hash_password //returns hash password.
valid_email //validates email format and return true or false
is_logged //check if session has a variable, returns true or false
generate_random_string //generates and hashes a random string
Should they be placed in controller or in a model?
Currently I place all of the above functions in a controller. Is it correct?
I think the is_logged should be placed in the Model for User. Note that the User might be a customer in your case or any class that you have made to model a user of your service.
The valid_email and generate_random_string are more or less utility functions, which you can place in a Utility or Utilities model, so that these are reusable in various controllers in your application.
The hash_password, can be placed in either the User model or Utility model. I am more tempted to place it in Utility model, since its a hashing function and there is nothing the user cares about. However, I can imagine there can be argument(s) otherwise.
The following SO question (though for a different framework) can also serve as a rule of thumb:
Where to put custom functions in Zend Framework 1.10
generally controllers are used to determine how to handle the http requests made..
There's nothing wrong in creating some functions which directly respond to the http requests.
but if it has anything to do with the DB, its better to place those function in the model, and call them from the controller.
Controller should combine view with model, so every validation shoulde be placed in model
this is my example from kohana
CONTROLLER
<?php
/**
* User Controller
*/
class Controller_Admin_User extends Controller_Admin_Template {
public function action_index()
{
$this->template->body = View::factory('admin/user/index')
->set('i', 0)
->bind('users', $users)
->bind('groups', $groups)
->bind('id_user_group', $id_user_group);
$model_user = new Model_Admin_User;
$users = $model_user->get_users(Arr::get($_GET, 'sort'), Arr::get($_GET, 'order'));
$model_usergroup = new Model_Admin_Usergroup;
$groups = $model_usergroup->get_user_group();
}
public function action_add()
{
$this->template->body = View::factory('admin/user/form_add')
->bind('error', $error)
->bind('groups', $groups)
->bind('post', $post);
$model_usergroup = new Model_Admin_Usergroup;
$groups = $model_usergroup->get_user_group();
if($_POST)
{
$model_user = new Model_Admin_User;
if($model_user->save($_POST) == false)
{
$error = $model_user->error;
$post = $_POST;
}
else
{
$this->request->redirect('admin/user');
}
}
}
MODEL
class Model_Back_User extends Model {
private $qb;
public $aliases = array(
'id'=> 'id_user'
);
public $error = array(
'name' => null,
'surname' => null,
'login' => null,
'password' => null,
'id_user_group' => null,
'old_password' => null,
'new_password' => null,
'confirm' => null,
'email' => null,
'phone' => null,
);
private $rules = array(
'name' => array('not_empty' => null, 'alpha' => null),
'surname' => array('not_empty' => null, 'alpha' => null),
'login' => array('not_empty' => null),
'password' => array('not_empty' => null),
'id_user_group' => array('not_empty' => null),
'email' => array('not_empty' => null, 'email' => null),
'phone' => array('not_empty' => null),
'old_password' => array('not_empty' => null),
'new_password' => array('not_empty' => null),
'confirm' => array('matches' => array('new_password'))
);
public function __construct()
{
$this->qb = new Querybuilder;
//parent::__construct();
}
public function change_password($data)
{
$validate = Validate::factory($data)
->filter(true, 'trim')
->rules('old_password', $this->rules['old_password'])
->rules('new_password', $this->rules['new_password'])
->rules('confirm', $this->rules['confirm'])
->callback('old_password', array($this, 'password_exists'), array('id_user'=> $data['id_user']));
if($validate->check() == false)
{
$this->error = array_merge($this->error, $validate->errors('user'));
return false;
}
$u = Session::instance()->get('user');
$this->edit(array('password'=> $this->password($data['new_password'])), array('id_user'=> $u['id_user']));
return true;
}
public function password_exists(Validate $valid, $field, $param)
{
if($this->user_exists(array('password'=> $this->password($valid[$field]), 'id_user'=> $param['id_user'])) == false)
{
$valid->error($field, 'old password is incorrect', array($valid[$field]));
}
}
public function save($data)
{
$validate = Validate::factory($data)
->filter(true, 'trim')
->rules('name', $this->rules['name'])
->rules('surname', $this->rules['surname'])
->rules('user_group_id', $this->rules['id_user_group'])
->rules('email', $this->rules['email'])
->rules('phone', $this->rules['phone']);
$edit = false;
if(isset($data['id_user']) AND Validate::not_empty($data['id_user']))
{
$edit = true;
}
else
{
$validate->rules('login', $this->rules['login'])
->rules('password', $this->rules['password']);
}
if($validate->check() == false)
{
$this->error = array_merge($this->error, $validate->errors('user'));
return false;
}
if($edit == true)
{
$this->edit(
array(
'name' => $data['name'],
'user_group_id' => $data['user_group_id']
),
array(
'id_user'=> $data['id_user']
)
);
return true;
}
return $this->add(
array(
'name' => $data['name'],
'login' => $data['login'],
'password' => $data['password'],
'user_group_id' => $data['user_group_id']
)
);
}
protected function add($data)
{
$data['password'] = $this->password($data['password']);
return $this->_db->query(Database::INSERT,
$this->qb->insert('user')->set($data)->build_query()
);
}
View is not so important thats why i dont put this here.
Generally speaking - a model should know stuff about it's own data. So anything related purely to a model's own data - should go in the model.
Eg the hash_password and email-validation methods - a model should know how to validate or update it's own data-fields, so those should go in the model.
However a controller should know about how to direct user actions appropriately and to load the correct models for views etc.
EG the session-related method should go in the controller, because the session is used for storing the user's state (based on past actions).
The "generate random string" method is very vague and may be used everywhere. I'd put that in a separate library possibly included in the model/controller as appropriate.
I've been using Codeigniter for a long time and I'd do the following with your functions as far as placement goes:
hash_password //returns hash password.
I'd put something like a password hasher in a library or helper file so I could call it from my controller like:
// pretend library I'd make for tasks like hashing etc
$this->load->library('passwords');
// transform posted password into it's hashed version
$password = $this->password_library->hash_password($this->input->post('password'));
I'm assuming you want to hash/salt the password and store it in your database in that example
valid_email //validates email format and return true or false
This is already in form_validation, so...
is_logged //check if session has a variable, returns true or false
This should also connect to a authentication library
generate_random_string //generates and hashes a random string
Again, this would come from a library or helper.
SO WHEN DO YOU USE A MODEL?
Me, I use models exclusively for in/out on the database. All my queries go in there. I usually have my model's functions return data objects so I can loop through them in my views.
Controllers call your data from your models, then dump everything into your views. Outside functionality always goes into libraries and helpers. I like to do the "MY_library" and extend Codeigniter's own stuff - especially with forms and the html helper etc.
Related
Using Laravel Spark, if I wanted to swap in a new implementation for the configureTeamForNewUser, at first it looks like it's possible because of the Spark::interact call here
#File: spark/src/Interactions/Auth/Register.php
Spark::interact(self::class.'#configureTeamForNewUser', [$request, $user]);
i.e. the framework calls configureTeamForNewUser using Spark::interact, which means I can Spark::swap it.
However, if I look at the configureTemForNewUser method itself
#File: spark/src/Interactions/Auth/Register.php
public function configureTeamForNewUser(RegisterRequest $request, $user)
{
if ($invitation = $request->invitation()) {
Spark::interact(AddTeamMember::class, [$invitation->team, $user]);
self::$team = $invitation->team;
$invitation->delete();
} elseif (Spark::onlyTeamPlans()) {
self::$team = Spark::interact(CreateTeam::class, [
$user, ['name' => $request->team, 'slug' => $request->team_slug]
]);
}
$user->currentTeam();
}
This method assigns a value to the private $team class property. It's my understanding that if I use Spark::swap my callback is called instead of the original method. Initial tests confirm this. However, since my callback can't set $team, this means my callback would change the behavior of the system in a way that's going to break other spark functionality.
Is the above a correct understanding of the system? Or am I missing something, and it would be possible to swap in another function call (somehow calling the original configureTeamForNewUser)?
Of course, you can swap this configureTeamForNewUser method. Spark create a team for a user at the registration. You have to add the swap method inside the Booted() method of App/Providers/SparkServiceProvider.php class.
in the top use following,
use Laravel\Spark\Contracts\Interactions\Auth\Register;
use Laravel\Spark\Contracts\Http\Requests\Auth\RegisterRequest;
use Laravel\Spark\Contracts\Interactions\Settings\Teams\CreateTeam;
use Laravel\Spark\Contracts\Interactions\Settings\Teams\AddTeamMember;
In my case I want to add new field call "custom_one" to the teams table. Inside the booted() method, swap the method as bellow.
Spark::swap('Register#configureTeamForNewUser', function(RegisterRequest $request, $user){
if ($invitation = $request->invitation()) {
Spark::interact(AddTeamMember::class, [$invitation->team, $user]);
self::$team = $invitation->team;
$invitation->delete();
} elseif (Spark::onlyTeamPlans()) {
self::$team = Spark::interact(CreateTeam::class, [ $user,
[
'name' => $request->team,
'slug' => $request->team_slug,
'custom_one' => $request->custom_one,
] ]);
}
$user->currentTeam();
});
In order to add a new custom_one field, I had to swap the TeamRepository#createmethod as well. After swapping configureTeamForNewUser method, swap the TeamRepository#create method onside the booted(),
Spark::swap('TeamRepository#create', function ($user, $data) {
$attributes = [
'owner_id' => $user->id,
'name' => $data['name'],
'custom_one' => $data['custom_one'],
'trial_ends_at' => Carbon::now()->addDays(Spark::teamTrialDays()),
];
if (Spark::teamsIdentifiedByPath()) {
$attributes['slug'] = $data['slug'];
}
return Spark::team()->forceCreate($attributes);
});
Then proceed with your registration.
See Laravel Spark documentation
I came accross curious problem. Lets say we want to validate some id. Validation should pass 10 different conditions(constraints) and we have to do it in 10 different places. I thought I can save myself writing unnessecary code by nesting one validation in another.
Here's what I did:
I've created new, custom validation constraint called IdParameter
I've registered IdParameterValidator.php file as service and injected validator service to it
I've put there another validation process(in our example 10 constraints which I will have to use in 10 different places) - I used Constraints\Collection to do it, so it looks kinda like this:
<?php
namespace Awesome\BlogBundle\Validator\Constraints;
use Symfony\Component\Validator;
class IdParameterValidator extends Validator\ConstraintValidator
{
private $_data = array();
private $_validator;
public function __construct(Validator\Validator\RecursiveValidator $validator)
{
$this->_validator = $validator;
}
public function validate($value, Validator\Constraint $constraint)
{
/* Preparing object of constraints */
$postIDConstraints = new Validator\Constraints\Collection(array(
'postId' => array(
new Validator\Constraints\Type(array(
'type' => 'integer',
'message' => 'This ain\'t no integer man!'
)),
new Validator\Constraints\Range(array(
'min' => 1,
'minMessage' => 'Post id is not valid'
))
)
));
/* Validating ID */
$this->_data['errors'] = $this->_validator->validate(array('postId' => $value), $postIDConstraints);
/* Checking validation result */
if(count($this->_data['errors']) > 0) {
$this->context->buildViolation($constraint->message)->addViolation();
}
}
}
So now I can use as many constraint as I whish and still have a clean service code:
$postIDConstraints = new Validator\Constraints\Collection(array(
'postId' => array(
new myValidator\Constraints\IdParameter()
)
));
/* Validating postID */
$this->_data['errors'] = $this->_validator->validate(array('postId' => (int)$postID), $postIDConstraints);
I'm wondering if it's correct approach?
Best Regards,
R.
P.S
I always comment my code - I didn't put comments here to keep it clean.
I am creating a basic CMS to teach myself the fundamentals of Laravel and PHP.
I have a 'pages' table and I am storing a url_title. I want this URL title to be unique for obvious reasons. However, whatever I do to validate it, fails. It just saves anyway. I'm sure it is something simple. Can you spot what is wrong with this code?
I am also using Former in the view, that doesn't validate either. I have tried hard-coding a value as the last option in the unique method and it fails also.
http://anahkiasen.github.io/former/
http://laravel.com/docs/validation#rule-unique
States: unique:table,column,except,idColumn
Here is my Controller:
public function store()
{
$validation = Pages::validate(Input::all());
if($validation->fails()) {
Former::withErrors($validation);
return View::make('myview');
} else {
Pages::create(array(
'title' => Input::get('title'),
'url_title' => Input::get('url_title'),
'status' => Input::get('status'),
'body' => Input::get('body'),
'seo_title' => Input::get('seo_title'),
'seo_description' => Input::get('seo_description')
));
//check which submit was clicked on
if(Input::get('save')) {
return Redirect::route('admin_pages')->with('message', 'Woo-hoo! page was created successfully!')->with('message_status', 'success');
}
elseif(Input::get('continue')) {
$id = $page->id;
return Redirect::route('admin_pages_edit', $id)->with('message', 'Woo-hoo! page was created successfully!')->with('message_status', 'success');
}
}
}
Here is my model:
class Pages extends Eloquent {
protected $guarded = array('id');
public static $rules = array(
'id' => 'unique:pages,url_title,{{$id}}'
);
public static function validate($data) {
return Validator::make($data, static::$rules);
}
}
I have tried the following:
public static $rules = array(
// 'id'=> 'unique:pages,url_title,{{$id}}'
// 'id'=> 'unique:pages,url_title,$id'
// 'id'=> 'unique:pages,url_title,:id'
// 'id'=> 'unique:pages,url_title,'. {{$id}}
// 'id'=> 'unique:pages,url_title,'. $id
);
Any ideas? I spoke to the guy who created Former. He can't make head nor tail about it either. He suggested tracking it back to find our what query Laravel uses to check the uniqueness and try running that directly in my DB to see what happens. I can't find the query to do this. Does anyone know where to track it down?
Many thanks
Your rule should be:
public static $rules = array(
'url_title' => 'unique:pages,url_title,{{$id}}'
);
As I guessed from your code Input::get('url_title')
You have to use the field name used in the form.
Thanks peeps. I have been using the Laravel unique solutions and it hasn't been working well. I found this package which solves the issue brilliantly.
https://github.com/cviebrock/eloquent-sluggable#eloquent
Definitely worth a look.
Thanks for your feedback.
I'm using a validation service to validate user submitted form input (something along the lines of: http://laravel.io/bin/vrk).
Using this approach (validation service classes) to validate user submitted form data against a set of rules, how can I validate user submitted data when rules have a unique rule. For example, if a user has the username of John then when I try to update the model validation fails (because John exists as a username, even though it belongs to the current model).
To solve this in Laravel I can do something like 'username' => 'required|alpha_dash|unique:users,username'.$id. How should I modify my current code, in the link, to best accommodate this? Should I have separate validator classes depending on the scenario (for example, UserCreateValidator, UserUpdateValidator, etc). Or should I do something like create separate validation rules in UserValidator class and pass which rule I want as an argument to either the constructor or the passes() method when calling UserValidator?
I think you could do something like this
First update UserValidator rules like this.
class UserValidator extends Validator {
// Override parent class $rules
protected $rules = [
'default' => [
'username' => 'required|alpha_dash|unique:users',
'password' => 'required|between:6,16|confirmed',
'password_confirmation' => 'required|between:6,16'
],
'update' => [
'username' => null,
]
];
}
Then modify Validator's passes method like this
public function passes($rule = null) {
$rules = $this->rules['default'];
if ($rule && isset($this->rules[$rule])) {
$rules = array_merge($rules, $this->rules[$rule]);
}
$validator = \Validator::make($input, $rules);
if ($validator->fails()) {
$this->validator = $validator;
return false;
}
return true;
}
Then in your controller's PUT method, this will merge update rules to default rules
$rule = 'update';
// user has changed his username
if ($input['username'] !== $old_username) {
$rule = 'create'; // validate uniqueness
}
else {
unset($input['username']); // remove it, we don't validate it anymore since it's the same
}
$validator->passes($rule); // override 'default' rules with 'update' rules
You don't have to change your controller's POST method, it'll stay the same
$validator->passes(); // use 'default' rules
If I'm understanding right, you have issues updateng data because of primary key constraints on your model. What you need to do is to create 2 sets of rules, one for insert, and one for update.
Asuming you have a set of rules like this:
protected $rules = [
'id' => 'required|unique:users'
]
You should implement something like this:
protected $rules = [
'id' => 'required|unique|unique:users,id,' . $this->id
];
This should tell laravel to ignore the duplicate id in the table users for the specified id, in this case, the id for the current object.
You can read more about this on laravel's documentation at http://laravel.com/docs/validation
unique:table,column,except,idColumn
The field under validation must be unique on a given database table.
If the column option is not specified, the field name will be used.
Well, what are you doing on post?
Because this is what you should be doing:
$user = User::find($userId);
$user->username = $input['username'];
$user->email = $input['email'];
$user->save();
To update a record.
Or
$input = array('username' => 'w0rldart', 'email' => 'hahafu#dumbledore.com');
// Retrieve the user by the attributes, or create it if it doesn't exist,
// based on the data above, which can come from an Input::all();
$user = User::firstOrCreate($input);
... many possibilities. But you could also do:
$input = array_forget($input, 'username');
To comply with your case, by removing the username index from the input array.
This is all I call tell you, based on the information you gave us. If you want more, post the controller's put method.
Update:
Here's my version of your PUT method: http://laravel.io/bin/OaX
I really think that try catch syntax is useless, since it's obvious that a User model will always be there. But I still don't know what you're trying to update. Even though I can't test it right now, I don't think that updating should be giving that problem, and if it does, retrieve user by username/id then unset the username index in your input array, and update it according to your specifications.
A little modification in UserValidator class
class UserValidator extends Validator {
// Override parent class $rules
protected $rules = [
'username' => 'required|alpha_dash|unique:users',
'password' => 'required|between:6,16|confirmed',
'password_confirmation' => 'required|between:6,16'
];
// ADD THIS
public function __construct(Array $rules = array())
{
parent::__construct();
if(count($rules)){
foreach($rules as $k => $v) $this->rules[$k] = $v;
}
}
}
In your controller putUpdate method
$user = User::whereUsername($username)->firstOrFail();
$rules = ['username' => 'required|alpha_dash|unique:users,username,'. $user->id];
// Pass the rule to update the rule for username in this method
$validator = \Services\Validators\UserValidator(Input::all(), $rules);
Check the manual here.
I am creating a User Model using Codeigniter and php-activerecord and the wiki says I can use 'on' => 'create' to have a validation only run when a new record is created, like this,
static $validates_presence_of = array(
array('title', 'message' => 'cannot be blank on a book!', 'on' => 'create')
);
It also states that we have access to "save", "update" and "delete"...
None of these are working for me though and I can figure out why, here is my code,
// Validations
static $validates_presence_of = array(
array('email', 'message' => 'Please enter a valid email address.'),
array('password', 'message' => 'Password must be provided.', 'on' => 'create')
);
I want to set it up like this so that when a user updates their profile, they can leave their password blank to keep their current one.
I would appreciate any help or guidance! Thanks!
The reason for this is most likely because it's not been implemented.
Relevant classes are lib/Model.php and lib/Validations.php
From a purely abstract standpoint, you would need to track the mode of operation between save and create. To do this, I created a public property (public $validation_mode) within lib/Model.php and set that property to 'create' or 'save' in private methods Model::insert() and Model::update() respectively. These values match the 'on' property you are trying to use.
Then within lib/Validations.php, I modified the following methods:
Validations::validates_presence_of()
public function validates_presence_of($attrs)
{
$configuration = array_merge(self::$DEFAULT_VALIDATION_OPTIONS, array('message' => Errors::$DEFAULT_ERROR_MESSAGES['blank'], 'on' => 'save'));
foreach ($attrs as $attr)
{
$options = array_merge($configuration, $attr);
$this->record->add_on_blank($options[0], $options['message'], $options);
}
}
Errors::add_on_blank()
public function add_on_blank($attribute, $msg, $options = array())
{
if (!$msg)
$msg = self::$DEFAULT_ERROR_MESSAGES['blank'];
if (($value = $this->model->$attribute) === '' || $value === null)
{
if(array_key_exists('on', $options))
{
if($options['on'] == $this->model->validation_mode)
{
$this->add($attribute, $msg);
}
} else {
$this->add($attribute, $msg);
}
}
}
What this does basically is passes ALL the $options specified in your model (including the 'on' property) down to the Errors::add_on_blank() method where it now has enough information to differentiate between 'on' => 'create' and the default ('on' => 'save'). Using the public $validation_mode property from the Model class ($this->model->validation_mode), we can determine what the current mode of operation is and whether or not we wish to continue adding the error message or skip it this time around.
Obviously you would want to document any changes you make and test thoroughly. According to the documentation, all validation methods supposedly support this "common option" along side allow_null, allow_blank but again, if it's not implemented, you will have to make it happen yourself by making these necessary changes.
should be call the validation method like this:
#example
$your_obj = new Instace();
if($your_obj->is_valid()) {
// if all is correct your logical code
}
else {
// your logical to show the error messages
}
//doesnt work if you write
if(!$your_obj->is_valid())
//the other way you must be use the next method
if($your_obj->is_invalid())
I'm find a answer for your question without edit library.
Add the before_validation callback and add in this callback a validation rule. It works for me.
static $before_validation_on_create = array('before_validation_on_create');
static $validates_presence_of = array(
array('login'),
);
public function before_validation_on_create() {
self::$validates_presence_of[] = array('password');
}