I'm trying to use a session var and call the logged in users details through object on the page. The user has already logged in through my login object. Here are my objects:
class User {
public $userdata = array();
//instantiate The User Class
public function User(){ }
public function set($var, $value) {
$this->userdata[$var] = $value;
}
public function get($var) {
if(isset($this->userdata[$var]))
{
return $this->userdata[$var];
}
return NULL;
}
function __destruct() {
if($this->userdata){
$this->userdata;
}
}
}
class UserService {
private $db;
private $fields;
public $user;
private $session;
public function __construct() {
$this->db = new Database();
}
// //get current user by ID
public function getCurrentUser($session) {
$this->session = $session;
$query = sprintf("SELECT * FROM User WHERE idUser=%s",
$this->db->GetSQLValueString($this->session, "int"));
$result = $this->db->query($query);
if($result && $this->db->num_rows($result) > 0){
//create new user
$user = new User();
$row = $this->db->fetch_assoc($result);
//set as object
foreach($row as $key => $value) {
$user->set($key, $value);
break;
}
return $user;
//return $this->user;
}
return NULL;
}
}
On my page I've check my session var has a value which it does, so I call the object like so.
$um = new UserService();
$user = $um->getCurrentUser($_SESSION['MM_Username']);
echo $user->get('UserSurname');
however, I see no user surname on the page. I have checked with a none object query and I see a surname but as soon as its object is doesn't work.
I think the problem is here:
foreach($row as $key => $value) {
$user->set($key, $value);
break; // you should probably remove it
}
You should use unnecessary break and probably after setting for example id you stop setting another object properties (UserSurname, Name and so on).
In addition it's quite confusing that inside $_SESSION['MM_Username'] you store idUser and not UserName
Code review
Marcin Nabialek allready answered your question. That break in the foreach is. well. what is it doing there?
But, there are much more things broken in your code. So here is a code review:
Constrcutors
You obviously know what a constructor is. You use it in both classes. But, differently. why? You User class has a public function User() but your UserService has a public function __construct(). Pick one, and stick to it. And if you can choose, pick the correct one: __construct()
From the phpdoc:
As of PHP 5.3.3, methods with the same name as the last element of a namespaced class name will no longer be treated as constructor. This change doesn't affect non-namespaced classes.
So namespacing your User class will break your constructor. This may not be a problem now, but it smells. Simply use __construct(). It is the prefered and correct way to do it. We live today, and not in the past of php4- days :)
Code styling
Oh god, a lot of kittens died today!
Sometimes you have a bracket on a new line:
if (isset($this->userdata[$var]))
{
return $this->userdata[$var];
}
and sometimes you don't
if($this->userdata){
$this->userdata;
}
Again, pick something and stick to it. and if you want to save some kittens. stick to the standards: PSR-1 & PSR-2
Public atributes, jeuk
Your User class has a public var $attributes. So it is accessible from the outside world. But you also give us a get and set method. why?
A good rule is: public $var smells, protected $var should be used with caution and private $var is the good stuff.
What are your classes? and why don't you use __constructors?
If I look at the User class, I always look at the __constructor. The User class needs no variables. So I should be able to do something like this:
$me = new User();
$me->getName(); //who am I ?
This ofcourse doesn't work. A User without a name doesn't make sense. It always has one. So ask for it!
class User
{
public function __construct($name)
{
$this->name = $name;
}
}
$me = new User('jeroen');
$m->getName(); //I am jeroen :)
If you need something ask for it ;)
So:
public function __construct(Database $db)
Is the way to roll!
Don't make me read the database
Now, your get/set methods are tigthly coupled with your database. If you change the name of a column in the database. You can refractor your entire code to get('new_column_name'); . Sounds like fun!
Also, what does the method say me? Nothing, does it write easy? no
getName says what it does, it gets me the name.
get tels me i'm getting something. but what?
other questions rise: get('name') =?= get('Name')
It's ok for the User object to know what it has.
Summary
Ok, I outlined some things wrong in your code. Some concepts you should look into:
SOLID
PSR standards
Factory Pattern (this will help you with your UserService
Inversion Of Control
So, for the sake of the article, here is your code revamped. Note that I wrote it into this commentbox directly, so I could have missed some things and made some errors.
Changelist:
I cleaned up styling
I added some comments
removed _destruct() (it wasn't doing anything)
Used PDO instead of Databse class (no idea what you are using, but looks like a PDO wrapper)
changed some table names and the select query (never use * in a selct query. Onyl ask for that what you need)
used prepared statements
more flexibility
exceptions instead of returning null;
Your $DBH could be put into a singleton/factory to ease your $dbh creation: Database::getInstance(); or DatabaseFactory::getInstance()->createPDO(); or so. Long time since I wrote something like this
Usage:
$DBH = new PDO("mysql:host=$host;dbname=$dbname", $user, $pass);
$userRepository = new UserRepository($DBH);
$id = $_SESSION['MM_Username'];
try
{
$me = $userRepository->find($id);
}
catch( UserNotFoundException $e )
{
//user not found
}
print $me->getSurName();
User class:
class User
{
/**
* If the User persists in the DataBase
* $id holds it's db id
* #var int
*/
private $id;
/**
* #var String
*/
private $surName;
/**
* #var String
*/
private $firstName;
/**
* #param String $firstName
* #param String $surName
* #param int $dbId
*/
public function __construct($firstName, $surName, $dbId=null)
{
$this->id = $dbId;
$this->firstName = $surName;
$this->surName = $surName;
}
/**
* Does the user ecist in the DB?
* #return boolean
*/
public function hasId()
{
return $this->id !== null;
}
/**
* #return int
* #return null user doesn't persist in DB
*/
public function getId()
{
return $this->id;
}
/**
* Return the users full name
* The fullname consists of his FirstName and SurNAme
* #return String
*/
public function getName()
{
return $this->firstName . ' ' . $this->surName;
}
/**
* #return String
*/
public function getSurName()
{
return $this->surName;
}
/**
* #return String
*/
public function getFirstName()
{
return $this->firstName;
}
/**
* Setters: we return $this to allow chaning
*/
public function setId($id)
{
$this->id = $id;
return $this;
}
// ... other methods here. You can add extra swet stuff.
// for instance check or it is a valid firstName, or email or ...
//I removed your __destrouct, because wtf? it isn't doing anything at all
}
and your UserRepository:
/**
* The UserRepository queries the database and get's you your users
*/
class UserRepository
{
private $db;
public function __construct(PDO $db)
{
$this->db = $db;
}
public function find($id)
{
$statement = $this->db->prepare('SELECT id,first_name,sur_name FROM users WHERE id = :id');
$statement->execute(array(
'id' => $id
));
if ( null === ($user = $statement->fetch(PDO::FETCH_ASSOC)) )
{
throw new UserNotFoundException();
}
return new User(
$user['first_name'],
$user['sur_name'],
$user['id']
);
}
}
and the exception:
class UserNotFoundException extends Exception();
Related
I am using a library, and it has the following process to attach many operations to one event:
$action = (new EventBuilder($target))->addOperation($Operation1)->addOperation($Operation2)->addOperation($Operation3)->compile();
I am not sure how to dynamically add operations depending on what I need done.
Something like this
$action = (new EventBuilder($target));
while (some event) {
$action = $action->addOperation($OperationX);
}
$action->compile();
I need to be able to dynamically add operations in while loop and when all have been added run it.
Your proposed solution will work. The EventBuilder provides what is known as a Fluent Interface, which means that there are methods that return an instance of the builder itself, allowing you to chain calls to addOperation as many times as you want, then call the compile method to yield a result. However you are free to ignore the return value of addOperation as long as you have a variable containing an instance of the builder that you can eventually call compile on.
Take a walk with me...
// Some boilerplate classes to work with
class Target
{
private ?string $name;
public function __construct(string $name)
{
$this->name = $name;
}
public function getName(): string
{
return $this->name;
}
}
class Operation
{
private ?string $verb;
public function __construct(string $verb)
{
$this->verb = $verb;
}
public function getVerb(): string
{
return $this->verb;
}
}
class Action
{
private ?Target $target;
private array $operations = [];
public function __construct(Target $target, array $operations)
{
$this->target = $target;
$this->operations = $operations;
}
/**
* Do the things
* #return array
*/
public function run(): array
{
$output = [];
foreach ($this->operations as $currOperation)
{
$output[] = $currOperation->getVerb() . ' the ' . $this->target->getName();
}
return $output;
}
}
Here is a basic explanation of what your EventBuilder is doing under the covers:
class EventBuilder
{
private ?Target $target;
private array $operations = [];
public function __construct(Target $target)
{
$this->target = $target;
}
/**
* #param Operation $operation
* #return $this
*/
public function addOperation(Operation $operation): EventBuilder
{
$this->operations[] = $operation;
// Fluent interface - return a reference to the instance
return $this;
}
public function compile(): Action
{
return new Action($this->target, $this->operations);
}
}
Let's try both techniques and prove they will produce the same result:
// Mock some operations
$myOperations = [
new Operation('Repair'),
new Operation('Clean'),
new Operation('Drive')
];
// Create a target
$target = new Target('Car');
/*
* Since the EventBuilder implements a fluent interface (returns an instance of itself from addOperation),
* we can chain the method calls together and just put a call to compile() at the end, which will return
* an Action instance
*/
$fluentAction = (new EventBuilder($target))
->addOperation($myOperations[0])
->addOperation($myOperations[1])
->addOperation($myOperations[2])
->compile();
// Run the action
$fluentResult = $fluentAction->run();
// Traditional approach, create an instance and call the addOperation method as needed
$builder = new EventBuilder($target);
// Pass our mocked operations
while (($currAction = array_shift($myOperations)))
{
/*
* We can ignore the result from addOperation here, just keep calling the method
* on the builder variable
*/
$builder->addOperation($currAction);
}
/*
* After we've added all of our operations, we can call compile on the builder instance to
* generate our Action.
*/
$traditionalAction = $builder->compile();
// Run the action
$traditionalResult = $traditionalAction->run();
// Verify that the results from both techniques are identical
assert($fluentResult == $traditionalResult, 'Results from both techniques should be identical');
// Enjoy the fruits of our labor
echo json_encode($traditionalResult, JSON_PRETTY_PRINT).PHP_EOL;
Output:
[
"Repair the Car",
"Clean the Car",
"Drive the Car"
]
Rob Ruchte thank you for detailed explanation, one thing I did not include was that each operation itself had ->build() call and I needed to move that to each $builder for it to work.
I have a website written with procedural PHP and now I'm re-coding it in (at least partly) OOP style so that I can start learning it. It is a simple page displaying several learning courses (title, description, etc) from a database. The admin can add or delete anything so it has to be dynamic.
At first I ran a single line of code:
$courses=$pdo->run("SELECT id,title,description FROM courses WHERE status=1 ORDER BY id")->fetchAll(PDO::FETCH_CLASS, 'Course');
$cmax = count($courses);
and echoed e.g. $courses[3]->description but I felt like I'm doing nothing else but pretending OOP while just using a multidimensional array. Again, it would be ok for the purpose of the website but my question is, in order to get used to OOP logic, can I do it like this: I'm generating a dropdown menu with only the titles and IDs from the database, and after either is clicked, only then I'm creating the object (to get the description, date, teacher, whatever):
$obj = new Course($pdo,$userSelectedID);
echo $obj->getTitle($pdo);
$obj->showDetails($pdo); // etc
The class:
class Course {
protected $id;
protected $title;
protected $description;
public function __construct($pdo,$id) {
$this->id=$id;
}
public function getTitle($pdo) {
$this->title=$pdo->run("SELECT title FROM courses WHERE id=?",[$this->id])->fetchColumn();
return $this->title;
}
public function getDescription($pdo) {
$this->description=$pdo->run("SELECT description FROM courses WHERE id=?",[$this->id])->fetchColumn();
return $this->description;
}
public function showDetails($pdo) {
echo "<h3>".$this->getTitle($pdo)."</h3>".$this->getDescription($pdo);
}
}
Is this a wrong approach? Is it ok to run sql commands inside a class? Especially when I already had to use some DB data to generate the dropdown menu. I hope my question makes sense.
PS: I've heard passing the PDO object every time isn't the best practice but I'm not there yet to do it with the recommended instance(?)
A good approach is to create model classes for every table you have in your database.
A model contains private attributes corresponding yo your table columns, with associated getters and setters.
For your Course table:
class Course {
protected $id;
protected $title;
protected $description;
public function getId() {
return $this->id;
}
public function setId($id) {
$this->id = $id;
}
public function getTitle() {
return $this->title;
}
public function setTitle($title) {
$this->title = $title;
}
public function getDescription() {
return $this->description;
}
public function setDescription($description) {
$this->description = $description;
}
}
Then, you have the concept of Data Access Objects. You can create an abstract class that will be extended by your data access objects:
abstract class AbstractDAO {
protected $pdo;
public function __construct($pdo)
{
$this->pdo = $pdo;
}
abstract public function find($id);
abstract public function findAll();
abstract protected function buildModel($attributes);
}
For your course table:
class CourseDAO extends AbstractDAO {
public function find($id) {
$statement = $this->pdo->prepare("SELECT * FROM courses WHERE id = :id");
$statement->execute(array(':id' => $id));
$result = $statement->fetch(PDO::FETCH_ASSOC);
return $this->buildModel($result);
}
public function findAll() {
$statement = $this->pdo->prepare("SELECT * FROM courses");
$statement->execute();
$results = $statement->fetchAll(PDO::FETCH_ASSOC);
$courses = [];
foreach($results as $row)
$courses[] = $this->buildModel($row);
return $courses;
}
public function findByTitle($title)
{
...
}
public function create(Course $course)
{
...
}
protected function buildModel($attributes) {
$course = new Course();
$course->setId($attributes['id']);
$course->setTitle($attributes['title']);
$course->setDescription($attributes['description']);
return $course;
}
}
Modern frameworks do this automatically, but I think it is good to understand how it works before using powerful tools like Eloquent or Doctrine
I'm having a bit of trouble in designing my classes in php.
As you can see in my Code, i want to have one Class instance and having more classes as children which "talk" from one to another. im getting the logged user and get all his information stored to a variable. In my other Classes i recently need to get this UserData.
Any help and Ideas are welcome :)
class Factory
{
private $UserData;
public function Factory()
{
DB::connect();
$this->getLoggedUserData( $_SERVER['REMOTE_USER'] );
}
private function getLoggedUserData( $user )
{
$result = DB::query( "SELECT * FROM users WHERE user='$user' LIMIT 1" );
$this->UserData = $result->fetch_assoc();
}
public function getMyTasks()
{
// how to call that class, without instancing it over and over again
MyOtherClass -> getMyTasks();
}
}
class MyOtherClass
{
public function getMyTasks()
{
// how to access the "global" variable
$result = DB::query( "SELECT * FROM tasks WHERE userID=" . $UserData['userID'] . " LIMIT 1" );
// doSomething ($result);
}
}
class DB
{
private static $mysqli;
public static function connect()
{
$mysqli = new mysqli(MYSQL_SERVER, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DB);
if ($mysqli->connect_error) {
die('Connect Error (' . $mysqli->conect_errno . ')' . $mysqli->connect_error);
}
mysqli_set_charset($mysqli, 'utf8');
self::$mysqli = $mysqli;
}
public static function query( $query )
{
$result = self::$mysqli->query( $query );
if ( self::$mysqli->error ) {
error_log("QUERY ERROR: " . self::$mysqli->error);
error_log("QUERY: " . $query);
}
return $result;
}
}
$Factory = new Factory();
OK, here goes a simple trivial approach to your problem
Mind you, this is not complete. Gimme some feedback if this is closing in on what you'd expect
your classes changed a bit
<?php
class Factory {
private $UserData;
private $UserTask;
public function Factory() {
DB::connect();
$this->getLoggedUserData($_SERVER['REMOTE_USER']);
}
private function getLoggedUserData($user) {
$result = DB::query('SELECT * FROM users WHERE user="'.$user.'" LIMIT 1');
$this->UserData = $result->fetch_assoc();
}
public function getMyTasks() {
// how to call that class, without instancing it over and over again
if (!isset($this->UserTask)) $this->UserTask = new MyOtherClass($this->UserData);
return $this->UserTask->getMyTasks();
}
}
class MyOtherClass {
private $UserData;
public function __construct($userData) {
$this->userData = $userData;
}
public function getMyTasks() {
// how to access the "global" variable
$task = DB::query('SELECT * FROM tasks WHERE userID='.$this->UserData['userID'].' LIMIT 1');
return $this->performTask($task);
}
public function performTask($task) {/* doSomething(); */}
}
// usage is not complete, waiting for some extra input
$factory = new Factory();
$taskResults = $factory->getMyTasks();
Any input on how to improve this is very welcome
edit following comments
Let's take a look at how you can solve the problem of having to share instances between different "apps" in your code
the singleton approach: an instance is created on the first call, all subsequent calls are passed the single instance
the registry pattern: an object created at the start of the script picks up all initialized requirements and stores them. If any "app" needs the basic set of services (it's not standalone), then pass the registry object to it's initializer/constructor.
I hope I understood your comments well enough, if not feel free to ask and correct me
Hard to say what would be best for you when i dont know more about the scale of your application etc.
Anyway the simplest way is something like this:
$otherClass = new MyOtherClass();
$Factory = new Factory($otherClass);
Class Factory
class Factory
{
private $UserData;
private someClass;
public function Factory(&$someClass)
{
$this->someClass = $someClass;
DB::connect();
$this->getLoggedUserData( $_SERVER['REMOTE_USER'] );
}
...
Usage
$this->someClass->getMyTasks();
But in case you only want access to the methods/variables of the parent, then yes extend the class.
I have a basic problem with following code:
<?php
interface UserInterface
{
public function getId();
public function getName();
}
class User implements UserInterface
{
private $_id;
private $_name;
public function __construct($id, $name)
{
$this->_id = $id;
$this->_name = $name;
}
public function getId()
{
return $this->_id;
}
public function getName()
{
return $this->_name;
}
}
class UserMapper
{
public function insert(UserInterface $user)
{
... insertion code
}
}
?>
The insert method of the UserMapper expects an UserInterface object. So I create one:
<?php
$user = new User(1, "Chris");
$userMapper = new UserMapper();
$userMapper->insert($user);
?>
My problem is, that the user's id is an auto-increment value that is coming from the database after inserting the object. But the object's constructor forces me to define an id because the object would not be complete without an id. How to solve that general problem?
To pass the id as a second parameter to the constructor woth a default value is not an option, because in my understanding the object would be incomplete without having an id.
Thanks for your help.
You can pass null:
$user = new User(null, "Chris");
$_id is not checked for a valid integer and with null you know that this model has no valid ID yet.
I am a learner, I have a class db to help me connect and fetch results in mySQL.
$set = $db->get_row("SELECT * FROM users");
echo $set->name;
this way i use echo results outside a class.
Now i have created another class name user and it has this function
public function name() {
global $db;
$set = $db->get_row("SELECT * FROM users");
$this->name = $set->name;
}
after initializing the class user, when i try to echo $user->name i dont get expected results.
Note i have declared above var $name; in class user
I'm pretty concerned by several things I see here
The method name name() is terribly uncommunicative as to what the method is supposed to do. Remember, methods are actions - try to give them some sort of verb in their name.
Usage of global in a class (or even usage of global period) when you should be using aggregation or composition.
You don't show any execution examples, but I can only assume you never actually call User::name(), which is why your test is failing
Here's some code that addresses these concerns.
<?php
class DB
{
/* Your methods and stuff here */
}
class User
{
protected $db;
protected $name;
public function __construct( DB $db )
{
$this->db = $db;
}
public function getName()
{
if ( is_null( $this->name ) )
{
$set = $this->db->get_row( "SELECT * FROM users" );
$this->name = $set->name;
}
return $this->name;
}
}
$db = new DB();
$user = new User( $db );
echo $user->getName();
class DB
{
public function get_row($q)
{
# do query and store in object
return $object;
}
}
class User
{
public $name;
public function __construct()
{
$this->name();
}
public function name() {
global $db;
$set = $db->get_row("SELECT * FROM users");
echo "<pre>".print_r($set)."</pre>"; # make sure $set is returning what you expected.
$this->name = $set->name;
}
}
$db = new DB();
$user = new User();
echo $user->name;
I am very much sorry, i figured out that problem was on my part, i was using cookies and had two cookies set which were giving problems :(