I am relatively new to OO PHP and I am trying to create a login class.
The issue I am having is that I want to pass the POST values username and password to my class but I cannot establish a decent way of doing so.
below is a snippet of my class
class PortalLogin{
private $username;
private $password;
function __construct(){
//I connect to DB here
}
function login($username, $password){
//error check the paramaters here
//then I can run the query
}
function __destruct(){
//I disconnect from DB here
}
}
Above is a breakdown of the class I am creating below is how i plan to execute it (my main issue at the moment).
$login = new PortalLogin();
if(isset($_POST['username'])){
if(isset($_POST['password'])){
$login->login($_POST[username],$_POST[password]);
} else {
//throw error
}
} else {
//throw error
}
I really do not like the construction of the code above it seems to messy to be doing so much outside of my class. how can I pass the POST information to the class and execute the checks there? I am worrying that if I pass the POST information to the class and one of the POSTS contains nothing it will error.
I think you got a problem with the syntax of post..
if(isset($_POST['username']) && isset($_POST['password'])){
$login->login($_POST['username'],$_POST['password']);
}
use AND.. so if both username and password exist then call the login function()
I’m not sure where OOP comes in to this, but if you were going the object-oriented route you would have a class that represents a request from which you could grab POST data from:
$username = $request->post('username');
$password = $request->post('password');
Your post() method could return a default value (null) if the variable didn’t exist in the POST data.
You could then have a class that checks your user based on these variables:
$auth = new AuthService($dbConnection);
if ($auth->checkCredentials($username, $password)) {
// Valid user
} else {
$error = $auth->getLastError();
}
I know I might be in the minority with suggesting this, but I favour static methods for things like this. PortalLogin represents an action rather than data
class PortalLogin
{
/**
* Attempt login
* #param string $username
* #param string $password
*/
public static function login ($username, $password)
{
// do your login stuff
}
}
Then to use you would do this:
if (isset($_POST['username']
&& !empty($_POST['username']
&& isset($_POST['password']
&& !empty($_POST['password']
) {
PortalLogin::login($_POST['username'], $_POST['password']);
}
Even better OO would be to have the username/password checking baked into the User class. (Maybe User::checkLoginCredentials($u, $p); // boolean yup/nope)
You can use error suppression, like this:
$login->login(#$_POST['username'], #$_POST['password']);
If one or both values are not present in the $_POST variable, there won't be an error when calling the method, so you can do the error handling inside your class method.
For more info, check:
http://php.net/manual/en/language.operators.errorcontrol.php
Edit:
Another option is to do this:
$login->login((isset($_POST['username']) ? $_POST['username'] : null), (isset($_POST['password']) ? $_POST['password'] : null));
Related
Let's say I have a table called user
I want to make a HTTP call roughly like this
http://my.awesome.server/dev_api/index.php/login/get_user.php?user=steven&password=12345
Which checks the database if there's a user 'steve' with the password '12345'. These are my codes.
controller
<?php
if(!defined("BASEPATH")) exit("No direct script access allowed");
class login extends CI_Controller
{
public function index()
{
$this->load->model("login_model");
$data["users"]=$this->login_model->get_user();
$this->load->view("login_view", $data);
}
}
model
class Login_model extends CI_Model {
function get_user(){
// get username, like $_GET['user']
$user = $this->input->get('user');
// get the password and MD5-ed it, like md5($_GET['password'])
$md5_pass = md5($this->get('password'));
// the where condition
$this->db->where(array('userName' => $user, 'password' => $md5_pass));
// ok, now let's query the db
$q = $this->db->get('user');
if($q->num_rows() > 0){
foreach ($q->result() as $row){
$data[] = $row;
}
}
return $data;
}
}
?>
view
<?php
if (!empty($users)){
foreach ($users as $u){
echo $u->userId .' '. $u->userName.' '.$u->password.' ';
}
}
?>
Then I opened this on browser:
http://my.awesome.server/dev_api/index.php/login/. The result is
How to properly make a HTTP call, then?
The method in your model is name as get_user() while you call it as login_model->get()
More over you should use POST instead of GET for username and password.
Also use bcrypt or another hashing algorithm instead of MD5 it's more secure.
DO NOT USE MD5
You are trying to work against the framework you are using.
CodeIgniter abstract working with GET parameters by proposing you to use URL segments.
The URI scheme in CodeIgniter is as follow (see docs):
controller/method/params...
It is divided in segments:
the controller (mandatory)
the method (mandatory, but may be implied in case of index, see below)
the first param (optional)
the second param (optional)
... and so on
You want to use the method index() of the controller Login, it translates to
http://my.awesome.server/dev_api/index.php/login/index
Also, with mode_rewrite activated with htaccess, it could be simplified in
http://my.awesome.server/dev_api/login/index
Now, index() is a special method as it is the one called by default in a controller. Hence the final URL would be:
http://my.awesome.server/dev_api/login
Now, if you want to pass parameters to your function, CodeIgniter does this through subsequent segments.
But you need to declare them in the controller method.
class login extends CI_Controller
{
public function index($user = null, $password = null)
{
// Check for $user / $password emptiness
// ...
// Use $user / $password as needed
$data["users"]=$this->login_model->get_user($user , $password);
// use the results...
}
}
And now you could call with:
http://my.awesome.server/dev_api/login/index/steven/12345
Notice that I've put index in the URL? It's because when passing parameters, the method is mandatory.
That said, I will reiterate what other people have said:
Avoid passing login/password through GET. Prefer POST.
Use an encrypted connection (HTTPS)
Do NOT hash your password with md5 or even sha1. You need a strong algorithm. You also need to salt. Anyway, PHP got you covered with password_hash. Don't reinvent the wheel.
Good luck.
if you are passing data via url http://my.awesome.server/dev_api/index.php/login/get_user.php?user=steven&password=12345
You can retrieve it via URI segment
$user= $this->uri->segment(3);
$password= $this->uri->segment(4);
Sorce:
https://www.codeigniter.com/userguide3/libraries/uri.html
It is posible that this line $md5_pass = md5($this->get('password')); is setting the error if $this->get('password') is empty.
I'm trying to learn MVC pattern but,even if I'm trying hard, it seems I still got big issues.
I have got a controller,named baseController that do the following:
class baseController {
public $model;
public $user;
...
$activeuser = $this->model->getlogin();
if ($activeuser != 'invalid user' && $activeuser != "") {
$this->user=$activeuser;
header("Location:home.php");
}
I have got a model.php file which contains the getlogin() function:
public function getlogin() {
if (isset($_REQUEST['username']) && isset($_REQUEST['password'])) {
$username = mysql_real_escape_string($_REQUEST['username']);
$pwd = mysql_real_escape_string($_REQUEST['password']);
$pwd = md5($pwd);
$query = mysql_query("SELECT * FROM users WHERE username='$username' AND password ='$pwd' AND attivato =1;");
if (mysql_num_rows($query) == 1) {
require_once 'User.php';
$sql=mysql_fetch_array($query);
$activeuser = new User();
$activeuser->username=$sql['username'];
$activeuser->email=$sql['email'];
return $activeuser;
} else {
return 'invalid user'; //TO-DO
}
}
}
The home.php create a new homeController and calls its invoke() function.The homeController file include the view page,that's called afterlogin.php.
In the afterlogin.php I've got the "ERROR":
if (isset($activeuser)){
echo "<p>Utente ".$activeuser->username."</p>";
echo "<p>Email ".$activeuser->email."</p>";}
//echo "<p>Pass ".$activeuser->pwd."</p>";
echo"<h1> HOMEPAGE, LOGIN OK </h1>";
It seems the homeController,and so the afterlogin page cannot access the user created in the baseController file. If I try an echo inside the baseController of $this->user->username everything is working. What should I do?? HELP!!
The client-server lifecycle is effectively stateless; on every page load, your variables and objects are wiped out.
There are the client-sourced $_POST and $_GET superglobals, which is part of the standard form submission and url query processes.
The server has databases, file writing (sketchy from a security POV) and the $_SESSION superglobal. These are the ways the server can manage a data state between pageloads.
Understand that if you're using objects, you need to have them instantiated on every page load for them to work. You can store your user_ID in $_SESSION['user_ID'] and instantiate the user object from it every time, making appropriate changes according to how the data changes.
I'm currently validating input and returning errors in a "fat controller" as follows:
class SomeController
{
public function register()
{
// validate input
$username = isset($_POST['username']) && strlen($_POST['username']) <= 20 ? $_POST['username'] : null;
// proceed if validation passed
if (isset($username)) {
$user = $this->model->build('user');
if ($user->insert($username)) {
$_SESSION['success'] = 'User created!';
} else {
$_SESSION['error'] = 'Could not register user.';
}
} else {
$_SESSION['failed']['username'] = 'Your username cannot be greater than 20 characters.';
}
// load appropriate view here
}
}
class SomeModel
{
public function insert($username)
{
// sql for insertion
// ...
return $result;
}
}
While this works and is easy enough for me to implement, I understand that this is incorrect because the validation belongs in the model, which I'm attempting to correct using a "fat model" as follows:
class SomeController
{
public function register()
{
$user = $this->model->build('user');
$user->insert($_POST['username']);
// load appropriate view here
// ...
}
}
class SomeModel
{
public function insert($username)
{
// validate input
$error = false;
$username = trim($username) != '' && strlen($username) <= 20 ? $username : null;
// proceed if validation passed
if (isset($username)) {
// sql for insertion
// ...
$_SESSION['success'] = 'User created!';
} else {
// store error in session
$error = true;
$_SESSION['error']['username'] = 'Your username cannot be greater than 20 characters ';
}
return $error ? false : true;
}
}
The problem I see here is that the model is supposed to be portable, in that it should never need to change. But if the requirement for the length of $username changes, then obviously I'll have to alter my model.
I feel like this may be a really common question but I've yet to find a straight-forward answer. Without implementing any extra "layers", "mappers" or whatever other confusing terms are out there, how could the example pseudo-code provided be modified to correctly handle this transaction? (eg, validate input, return error if validation fails)?
Without implementing any extra "layers", "mappers" or whatever
You should consider the "model" to be a application layer rather than a single class. The term "layer" could be thought of as a simple way to reference the M slice of MVC sandwich. So to accomplish the flexibility you desire you will need to create it.
A number of clear seperations can be made. I would consider having three abstractions: services, data mappers and entities.
A service would be exposed to the controller and perform the service being requested.
// some controller
function register() {
$service = $this->getUserService();
$user = $service->register($_POST['first_name'], $_POST['last_name']);
if ($user instanceof \My\Entity\User) {
// set user in view
} else {
// redirect to error
}
}
So task one complete, the controller is now dumb to whatever happens within register, all it wants to know is how to resolve the appropriate result. If there is a user object, success, otherwise false something went wrong.
The service class itself would encapsulate the services being offered:
// class UserService.php
function register($firstname, $lastname) {
// validate arguments
if ($this->isValidUsername(....
$userMapper = $this->getUserMapper();
$user = new My\Entity\User();
$user->setFirstName($firstname);
$user->setLastName($lastname);
return $userMapper->save($user);
}
return false;
}
We handle the validation of the arguments and also create the new user, passing it to the data mapper which will perform the "actual save" abstracting the database operations.
// UserMapper
function save($user) {
// save $user to db
$sql = 'INSERT INTO ....
return true;
}
I'm not sure what you would consider to be an undesirable "layer" or "mapper". This is an interesting question, and my first though was that you could just include a configuration file that defined a constant for your username length. My second though was that you could have someModel extend a class or implement an interface, wherein you values would be set as properties or constants. I suspect that you have thought of these, and are avoiding them; that this is what you mean by avoiding "layers" and "mappers" It seems that you are being guided by these principals in this code:
Avoid "magic numbers"
KISS
Composition over inheritance
skinny controller/fat model
So, are you running php5.4+ ? Maybe define a trait which could be used in this and other models that defines the username length and other changeable values in the application. Or maybe that too is to much of a "layer"?
I know, globals not (;
I am new to OOP, and I'm refactoring some functions I created into classes, but I come to a problem. Some of my classes are called from the pages themselves that the users enter (example: $Link->create('page/to/go');). Since this is outside any class, there's no problem, the links get created.
But then, I have a class that attempts to login the user when created, and if the email entered is not in the database it redirects the user to the register page. Obviously, only doing header ('Location '.$Link->create('page/to/go')) does not work.
What I would do is to set the create method as static and then call it from everywhere. But I think this would be similar to using globals and I am trying to correct bad habits. So how should I do this?
Here's some of the code for the class Link, implementing a 404-detect that I explained here:
class Link
{
private function valid($check)
{
$exceptions=array("help/report", "translate"); // More pages and rules to be added
return in_array($check,$exceptions);
}
public function create($arg)
{
if (!file_exists("/path/to/".$arg) && !$this->valid($arg))
{
// Call a function to store the error in a database.
error ("404 for ".$arg);
// One way of handling it. Replace '/' for ' ' and google that string in this page.
$arg=str_replace("/","%20",$arg);
return "https://www.google.com/#q=site%3A".Configuration::get('BaseUrl')."%20".$arg;
}
else
{
// If the page exists or is an exception, create the normal link.
if(empty($arg)) return Configuration::get('BaseUrl');
else return Configuration::get('BaseUrl').$arg;
}
}
}
As you can see in the code, when I implement error() into a class I will have a similar problem.
One option I just thought is that I might want to return an error and parse it from outside the __construct() of the User class. But it only works with this, as it's a yes/not, and I don't think making a error code up is proper for other cases.
So, what is your suggestion for passing properties and methods from one classes to others? Is it okay to use static for this context?
EDIT. The difficulty of my question it's that, almost all book, tutorial, page etc I've seen talks about how to create a SINGLE class. I haven't seen any explaining deeply how classes should talk to each other.
EDIT 2. As requested in the comments, here goes some more code. The user accesses his courses entering only the email (getting a level 1), while the user can only edit his settings if he gets a level 2 in the settings page. Not finished as I'll put some more methods.
class User
{
private $Email;
private $Name;
public function __construct()
{
if (!empty($_POST['logout'])) session_destroy();
else if ( !empty($_POST['email']) )
{
$this->loginEmail($_POST['email']);
}
else if ( $_SESSION['level'] == 1 )
{
if (!empty($_POST['password']))
{
$this->loginFull($_SESSION['email'],$_POST['password']);
}
else
{
$this->loginEmail($_SESSION['email']);
}
}
else if ( $_SESSION['level'] == 2 )
{
$this->loginFull($_SESSION['email'],$_SESSION['pass']);
}
else session_destroy();
}
private function loginEmail($Email)
{
$sql=mysql_query("SELECT * FROM users WHERE email='".mysql_real_escape_string($Email)."'"); //Retrieve the entries from the database
$row=mysql_fetch_assoc($sql);
if(mysql_num_rows($sql)==1)
{
$this->getData($row);
$_SESSION['level']=1;
}
else header ('Location: http://example.org/new/student/');
}
private function loginFull($Email,$Pass)
{
$sql=mysql_query("SELECT * FROM users WHERE email='".mysql_real_escape_string($Email)."' AND pass='".md5($Pass)."'"); //Retrieve the entries from the database
$row=mysql_fetch_assoc($sql);
if(mysql_num_rows($sql)==1)
{
$this->getData($row);
$_SESSION['pass']=$Pass;
$_SESSION['level']=2;
}
else $this->loginEmail($Email);
}
private function getData($row)
{
$_SESSION['email']=$row['email'];
$this->Email=$row['email'];
$this->Name=$row['name'];
}
public function get($Var)
{
return $this->$Var;
}
}
And now the class Error. As you can see, I already performed some DI without even knowing about it here.
class Error
{
private $Page;
private $Language;
private $User;
public function __construct($Page,$Language,$User="None")
{
$this->Page=$Page;
$this->Language=$Language;
$this->User=$User;
if (!empty($_REQUEST['banner']))
$this->Banner=$_REQUEST['banner'];
}
public function add($Kind)
{
if (!mysql_query("INSERT INTO error (kind, page, lang, user, version, date) VALUES ('".mysql_real_escape_string($Kind)."', '".mysql_real_escape_string($this->Page)."', '".mysql_real_escape_string($this->Language)."', '".mysql_real_escape_string($this->User)."', '".Configuration::get('Version')."',NOW() )"))
mail(Configuration::get('ErrorEmail'), "Error '".$Kind."' that couldn't be stored.",
"Full url: ".$FullUrl."\n Language: ".$this->Language->Lang."\n User: ".$Identif."\n Version: ".$Version); // Inform of the error by email
}
}
Create instance of Link class and pass it to where it is needed (using setter or constructor).
Definitelly read something abou DI (Dependency Injection) and then DI Containers.
Nice introduction in Nette Framework - Dependency Injection
Singletons and Static aren't a good choice. In the end everything falls onto the globals problem.
You should inject your link helper into the classes by constructor or setter (DI as #jasir said).
If you'll redirect the user, you can also inject an redirector helper:
$redirector->redirect('controller','action', array('my','params'));
Hint: this is bad too Configuration::get(). Inject the config instead.
And always remember: Don't look for things!
I'm using Zend_Auth with a project using doctrine.I believe every bootstrapping is done correctly and i can log in.
my adapter looks like this:
class Abra_Auth_Adapter_Doctrine implements Zend_Auth_Adapter_Interface {
protected $_resultArray;
private $username;
private $password;
public function __construct($username, $password) {
$this->username = $username;
$this->password = $password;
}
//based on feedbacks as response authenticate has changed to this
public function authenticate() {
$q = Doctrine_Query::create()
->from("Abra_Model_User u")
->leftJoin("u.Role r")
->where("u.username=? AND u.password=?", array($this->username,$this->password));
$result = $q->execute();
if (count($result) == 1) {
return new Zend_Auth_Result(Zend_Auth_Result::SUCCESS, $result->get("Mylibrary_Model_User"), array());//autoloaderNamespaces[] = "Mylibrary_" in application.ini
} else {
return new Zend_Auth_Result(Zend_Auth_Result::FAILURE, null, array("Authentication Unsuccessful"));
}
}
my Abra_Controller_Pluging_Acl looks like this
class Abra_Controller_Plugin_Acl extends Zend_Controller_Plugin_Abstract {
public function preDispatch(Zend_Controller_Request_Abstract $request) {
parent::preDispatch($request);
$controller = $request->getControllerName();
$action = $request->getActionName();
$module = $request->getModuleName();
$auth = Zend_Auth::getInstance();
if($auth->hasIdentity()){
$identity = $auth->getIdentity();
$roles = $identity["Role"];
$role = $roles["name"];
$role = (empty ($role) || is_null($role))? "regular" : $role ;
} else {
$role = "guest";
}
}
now having Doctrine_Event Fatal error: spl_autoload() [function.spl-autoload]: Class Doctrine_Event could not be loaded. i've seen this post here and i'm wondering how that can affect my using of Zend_Session, and it's true that i have apc.dll enabled in my php.thanks a lot for reading this
How to get the role: In your adapter, on successful login, rather than returning only the username field, how about returning the whole user object? Then the whole thing will be available when you call Zend_Auth::getIdentity().
Question 1: If you treat controllers as resources and the ACL rules are going to be different per module, then the resource names should reflect the module, as well. This will address the issue of modules with identical controller names.
Question 2: I am not sure I am understanding correctly. Zend_Auth and its storage will take care of keeping the uer identity in its own session namespace. However, I have run into the issue of what to do when the user record in the db changes - say, the user modifies his full name in his profile during his logged-in session - and you are displaying that full name in your site template, pulled from Zend_Auth::getIdentity(). As a user, I would expect the change to be reflected in the visible interface, but the change has only occurred back in the db, not in the session.
What I have done in the past is to create an additional auth adapter that fetches the new user record and always returns success. When the user updates his profile, I call Zend_Auth::authenticate() using this trivial adapter. The session storage gets updated and all is well with the world.
[This approach is almost certainly a hack, so I'd be interested in hearing alternative approaches. I'm sure I can set a value in the session storage directly, but when I last tried it, I couldn't make it quite work. So resorted to the additional adapter workaround.]