I'm currently working on a PHP script, and I'm not sure what is the best practise for the following:
Since PHP does not allow multiple constructors within a class (User), I have a constructor which has an array as argument (for the id,name,address, see code block below).
When this array does not contain an id, but is does contain other attributes (such as name, address), it calls the insert method of the class and assigns the id of the inserted row to the id attribute of the class.
When an array which does contain an id, name and address is passed to the constructor, it assigns these values to the corresponding attributes.
When I want to edit the user in the database, I can implement it in the following ways:
Add an option to the constructor to only pass the id in the array
(without name and address attributes, because these are not known
when calling the update method), and than call update method on this
instance. A problem with this is that you've got an instance which is
not a good representation of the real object (as in the database).
For example, you can't call the getName() method since name is not
set.
Same as above, but now when only the id is passed to the constructor,
load the other attributes from the database, so that the instance is
a correct representation of the object as in the database. Then the
update method can be called on this instance.
Make the update method static, so that I can call the update method
without making an instance
The problem with the first two points is, that I get kind of spaghetti code in my constructor since I have to check which attributes are passed to the array in the constructor.
Which solution do you think I can choose best?
Do you've got a better approach which will not result in large amounts of spaghetti code in the constructor?
<?php
class User
{
private $id;
private $name;
private $address;
public function __construct($data)
{
$this->name = $data['name'];
$this->address = $data['address'];
if (isset($data['id'])) {
$this->id = $data['id'];
} else {
$result = $this->insert();
$this->id = $wpdb->insert_id; //Wordpress method for retrieving insert id
}
}
public function get_id() { return $this->id; }
public function get_name() { return $this->name; }
public function get_address() { return $this->address; }
public function insert() { //insert into DB }
public function update($data) { //update in DB the $data attributes}
public function delete() { //delete from database }
public static function get_user_list()
{
// load users from the database
// foreach user {
// create User instance by passing the id, name and address values from the database in an array to the constructor of User
// add user instance to a result array
// }
// return result array
}
}
?>
Create a method called loadUser (or something you prefer), if the data array passed to the constructor has and ID, use the loadUser method to load the details. If not, you can insert a user. After this you can move onto editing the user.
Related
I have a database column called modified with ON UPDATE CURRENT_TIMESTAMP definition.
When I modify and persist existing object the column stays with old value, because it is already set in the object property. Is there a way to tell Doctrine not to set that object property when persisting?
I got the desired result using unset before persisting, but this will make the code messy as not all entities have that property.
unset($object->modified);
$entityManager->persist($object);
Solved it by adding a LifecycleEvent.
In my ClassMetadataBuilder I have a method for creating the field:
public function addModifiedTimeField(): void {
$this->createField("modified", "timestamp")->build();
$this->addLifecycleEvent("unsetModified", "preFlush");
}
And entities that require modified field extend a Versionable class that defines the method.
abstract class Versionable extends JsonEncodable {
protected $modified;
public function getModified() {
return $this->modified;
}
public function unsetModified(): void {
$this->modified = null;
}
}
I'm using a custom PHP framework which is largely based on CodeIgniter.
In one of my controller files, I've set a class property called $orderId. After the user has filled in the form and submitted, I'll do a DB insert, get the order no. and override the $orderId class property value with that order no.
Then I'll redirect to the submit page where I want to access that updated $orderId class property value. This final part is not working, the submit class gets a blank value for property $orderId.
Where am I going wrong pls? The basic example below. Maybe I can't do this because of the redirect and should use a session var instead?
Thanks!
[EDIT] Or I could pass the orderId as the 3rd URL param in the redirect. E.G. redirect('orders/submit/'.self::$orderId); in which case I'll turn all the self:: instances into $this-> for class level scope.
class Orders extends Controller {
private static $orderId;
public function __construct() {
// assign db model
}
public function index() {
if($_SERVER['REQUEST_METHOD'] == 'POST') {
$data = [
// form data to pass to db model
];
self::$orderId = 12345; // example order no. return from db model
if(!empty(self::$orderId)) {
redirect('orders/submit');
}
}
}
public function submit() {
$data = [
'orderId' => self::$orderId
];
$this->view('orders/submit', $data);
}
}
The issue itself is a fundamental architecture problem. static only works when you're working with the same instance. But since you're redirecting, the framework is getting reinitialised. Losing the value of your static property. Best way to go about doing this is by storing the order id in a session variable and then read that variable. Sessions last for the as long as the browser window is open
So, in my framework X, let it be Phalcon, I often create models objects.
Let's assume that all fields already validated. Questions related only about creation logic.
A simple example of creating Users object and save it to DB:
<?php
$user = new Users();
$user->setName($name);
$user->setLastName($lastname);
$user->setAge($age);
$user->create();
For simplicity, I show here only 3 fields to setup, in the real world they always more.
I have 3 questions:
1) What the best way to encapsulate this logic in Factory class? If I create Factory class that will create objects like Users object, every time I will need pass long amount of parameters.
Example:
<?php
$factory = new UsersFactory();
$factory->make($name, $lastname, $address, $phone, $status, $active);
2) Even if I implement Factory in a way showed above - should Factory insert data in DB? In my example call method create()? Or just perform all setters operations?
3) And even more, what if i will need to create Users objects with relations, with other related objects?
Thank you for any suggestions.
Your question starts out simple and then builds with complexity. Reading your post it sounds like your concerned about the number of arguments you would have to pass to the method to build the object. This is a reasonable fear as you should try to avoid functions which take more than 2 or 3 args, and because sometimes you will need to pass the 1st 3rd and 5th arg but not the 2nd and 4th which just gets uncomfortable.
I would instead encourage you to look at the builder pattern.
In the end it will not be that much different than just using your User object directly however it will help you prevent having a User object in an invalid state ( required fields not set )
1) What the best way to encapsulate this logic in Factory class? If I create Factory class that will create objects like Users object, every time I will need pass long amount of parameters.
This is why I recommended the builder pattern. To avoid passing a large number of params to a single function. It also would allow you to validate state in the build method and handle or throw exceptions.
class UserBuilder {
protected $data = [];
public static function named($fname, $lname) {
$b = new static;
return $b
->withFirstName($fname)
->withLastName($lname);
}
public function withFirstName($fname) {
$this->data['first_name'] = $fname;
return $this;
}
public function withFirstName($lname) {
$this->data['last_name'] = $lname;
return $this;
}
public function withAge($age) {
$this->data['age'] = $age;
return $this;
}
public function build() {
$this->validate();
$d = $this->data;
$u = new User;
$u->setFirstName($d['first_name']);
$u->setLastName($d['last_name']);
$u->setAge($d['age']);
return $u;
}
protected function validate() {
$d = $this->data;
if (empty($d['age'])) {
throw new Exception('age is required');
}
}
}
then you just do..
$user = UserBuilder::named('John','Doe')->withAge(32);
now instead of the number of function arguments growing with each param, the number of methods grows.
2) Even if I implement Factory in a way showed above - should Factory insert data in DB? In my example call method create()? Or just perform all setters operations?
no it should not insert. it should just help you build the object, not assume what your going to do with it. You may release that once you build it you will want to do something else with it before insert.
3) And even more, what if i will need to create Users objects with relations, with other related objects?
In Phalcon those relationships are part of the entity. You can see in their docs this example:
// Create an artist
$artist = new Artists();
$artist->name = 'Shinichi Osawa';
$artist->country = 'Japan';
// Create an album
$album = new Albums();
$album->name = 'The One';
$album->artist = $artist; // Assign the artist
$album->year = 2008;
// Save both records
$album->save();
So to relate this back to your user example, suppose you wanted to store address information on the user but the addresses are stored in a different table. The builder could expose methods to define the address and the build method would create both entities together and return the built User object which has a reference to the Address object inside it because of how Phalcon models work.
I don't think it's entirely necessary to use a builder or "pattern" to dynamically populate your model properties. Though it is subjective to what you're after.
You can populate models through the constructor like this
$user = new Users([
'name' => $name,
'lastName' => $lastname,
'age' => $age,
]);
$user->create();
This way you can dynamically populate your model by building the array instead of numerous method calls.
It's also worth noting that if you want to use "setters" and "getter" methods you should define the properties as protected. The reason for this is because Phalcon will automatically call the set/get methods if they exist when you assign a value to the protected property.
For example:
class User extends \Phalcon\Mvc\Model
{
protected $name;
public function setName(string $name): void
{
$this->name = $name;
}
public function getName(): string
{
return $this->name;
}
}
$user= new MyModel();
$user->name = 'Cameron'; // This will invoke User::setName
echo $user->name; // This will invoke User::getName
It is also worth noting that the properties will behave as you'd expect a protected property to behave the same as a traditional protected property if the respective method is missing. For example, you cannot assign a value to a protected model property without a setter method.
All my classes that connect to a database need to get values of custom columns from their respective tables. So instead of coding a function for each class, is there a way for me to implement a base class from which my classes extend and I can use that base class function to easily get and update data on my database (at least for simple data).
class Users extend BaseClass
{
private $table = "users";
private $columns = ["name", "email", "password"];
}
so from an outside function, I can access the email value like this
Users->where("name", "John")->getEmail();
or possibly
Users->where("name", "John")->get("email");
I could also use this method to update data to the database. The functions where should be universal so it should exist in BaseClass. (I know the database queries that I should use, what I want to know is how to call get after calling where and also possibly setting multiple where requirements).
Users->where("name", "John")->where("last_name", "Smith")->get("email");
I think you want something like this
abstract class BaseClass
{
private $where_clauses=[];
private $columns=[];
private $table='';
protected function setData($table,$cols){
$this->columns=$cols;
$this->table=$table;
}
public function where($key, $value){
$this->where_clauses[$key]=$value;
return $this;
}
public function get($col){
$sql='SELECT '.$col.' FROM '.$this->table.' WHERE';
$first=true;
foreach($this->where_clauses AS $key=>$val){
if(!$first) sql.=' AND ';
$first=false;
$sql.=$key.' = '.$val;
}
// RUN QUERY, Return result
}
}
Note that the where function returns a reference to $this, which is what let's you string the function calls together (not tested the code). This would also need some adapting to let you put two conditions on the same column.
I hope the title was descriptive enough, i wasn't sure how to name it.
Let's say i have the following code:
Class Movie_model {
public method getMoviesByDate($date) {
// Connects to db
// Gets movie IDs from a specific date
// Loop through movie IDs
// On each ID, call getMovieById() and store the result in an array
// When all IDs has looped, return array with movies returned from getMovieById().
}
public function getMovieById($id) {
// Get movie by specified ID
// Also get movie genres from another method
// Oh, and it gets movie from another method as well.
}
}
I always want to get the same result when getting a movie (I always want the result from getMovieById().
I hope you get my point. I will have many other functions like getMoviesByDate(), i will also have getMoviesByGenre() for example, and i want that to return the same movie info as getMovieById() as well.
It it "ok" to do it this way? I know this puts more load on the server and increases load time, but is there any other, better way that i don't know of?
EDIT: I clarified the code in getMoviesByDate() a bit. Also, getMovieByDate() is just an example. As i said, i will be calling methods like getMoviesByGenre() also.
EDIT: I'm currently running 48 database queries on the frontpage of my project, and the frontpage is still far from finished, so that number would at least triple when i'm done. Almost all queries take around 0.0002, but as the database keeps growing that number will rise dramatically i'm guessing. I need to change something.
I don't think it's good to work like this in this particular case. The function getMoviesByDate would return an amount of "n" movies (or movie ids) from a single query. For each id in this query you would have a separate query to get the movie by the specified ID.
This would mean if the first function would return 200 movies, you would run the getMovieById() function (and the query inside it) 200 times. A better practice (IMO) would be to just get all the info you require in the getMoviesByDate() function and return it as a collection.
It doesn't seem very logical to have getMoviesByDate() and getMoviesById() methods on a Movie class.
An alternative would be to have some sort of MovieManager class that does all of the retrieving, and returns Movie objects.
class MovieManager {
public function getMoviesByDate($date) {
// get movies by date, build an array of Movie objects and return
}
public function getMoviesByGenre($genre) {
// get movies by genre, build an array of Movie objects and return
}
public function getMovieById($id) {
// get movie by id, return Movie object
}
}
Your Movie class would just have properties and methods specific to a single movie:
class Movie {
public id;
public name;
public releaseDate;
}
It's OK to have separate methods for getting by date, genre etc etc, but you must ensure that you are not calling for the same records multiple times - in that case you will want a single query that could join the various tables you need.
Edit - after you have clarified your question:
The idea of getting movie IDs by date, then running them all through getMovieById() is bad! The movie data should be pulled when getting by date, so you don't have to hit the database again.
You can modified your getMovieById function. You can pass date as a parameter, the function should return the movies by their id and filtered by date.
To keep track which records you've already loaded into RAM previously you can use a base class for your models which saves the id's of the records already loaded and a reference to object the model object in the RAM.
class ModelBase {
/* contains the id of the current record, null if new record */
protected $id;
// keep track of records already loaded
static $loaded_records = Array();
public function __construct(Array $attr_values) {
// assign $attr_values to this classes attributes
// save this instance in class variable to reuse this object
if($attr_values['id'] != null) {
self::$loaded_records[get_called_class()][$attr_values['id']] = $this;
}
}
public static function getConcurrentInstance(Array $attr_values) {
$called_class = get_called_class();
if(isset(self::$loaded_records[$called_class][$attr_values['id']])) {
// this record was already loaded into RAM
$record = self::$loaded_records[$called_class][$attr_values['id']];
// you may need to update certain fields of $record
// from the data in $attr_values, because the data in the RAM may
// be old data.
} else {
// create the model with the given values
$record = new $called_class($attr_values);
}
return $record;
}
// provides basic methods to update records in ram to database etc.
public function save() {
// create query to save this record to database ...
}
}
Your movie model could look something like this.
Class MovieModel extends ModelBase {
// additional attributes
protected $title;
protected $date;
// more attributes ...
public static function getMoviesByDate($date) {
// fetches records from database
// calls getConcurrentInstance() to return an instance of MovieModel() for every record
}
public static function getMovieById($id) {
// fetches record from database
// calls getConcurrentInstance() to return an instance of MovieModel()
}
}
Other things you could do do decrease the load on the DB:
Only connect once to the database per request. There are also possibilities to share a connection to a database between multiple requests.
Index thefields in your database which get searched often.
only fetch the records you need
Prevent to load the same record twice (if it didn't change)