I am writing a small framework where my goal is to make it easy for my developers to set up a redirect to the details page of a resource after adding/inserting one.
Ordinarily this would be as easy as PDO::lastInsertID(), but I am using more than just the ID to identify the resource, I am also using a formatted version of the resource's name/title.
For example a new tournament might be called "Test Tournament" and therefore it's resource URI will be domain.com/tournament/328-test-tournament.
So when I redirect after inserting, I need to redirect to '328-test-tournament' and not just '328'. This is by design for URL integrity purposes. I don't want people accessing individual resources with mis-matched IDs and titles.
So that said, after I insert, I would like to be able to automatically return not just the ID, but the entire data set for what I entered, so that I can then format the title and redirect.
I could do this in the controller:
$this->TournamentModel->insert();
$id = PDO::lastInsertID();
$title = my_title_formatting_function($_POST['title']);
#header("Location: domain.com/tournament/{$id}-{$title}");
But I want a solution that's slightly more elegant like this:
$id = $this->TournamentModel->lastInsert();
Where lastInsert is actually a public method in the core model by retrieving not just the id of the last insert, but the entire row. I would then handle the title formatting and id concatenation right there.
Does something like this exist? Or at the very least is there a PDO method that returns the table that was inserted into so that I could construct a query using the table name and the id?
Check Doctrine or Propel.
They are two very famous Object Relational Mapper's, which have as base PDO.
ORM's can do what you asked, and have lots of other features you will enjoy, check it up.
Does a 'title' not belong to a Tournament object? i.e.
class TournamenentModel {
private $id;
private $title;
public function setTitle($title) {
$this->title = $title;
}
public function getTitleFormatted() {
return my_title_formatting_function($this->title);
}
}
in which case, your insert method might look something like
PDO::save(array('title' => $this->title));
and your workflow would be along the lines of:
$tournament = new TournamentModel();
$tournament->setTitle($_POST['title']);
$tournament->insert();
$title = $tournament->getTitleFormatted();
#header("Location: domain.com/tournament/{$id}-{$title}");
The title is persisted in memory until you release the object, there is no need to save and then retrieve it from a database?
Related
The problem is not exactly passing arrays to objects per se, but distributing the initial values acquired from the database correctly in their related objects. Here's the situation. I have a forum member class and I need to pass only the values relevant to that class and also make it so it would assume certain array keys are one and the same, specifically the user ID key.
Example of a member class:
class Member {
private $id;
private $username;
public function __construct($row) {
foreach($row as $key => $value) {
$this->$key = $value;
}
public function getUserURL() {
return '' . $this->username . '';
}
}
Now imagine there is a posts table too and I need to get both post and user/author data from that:
$res = $db->query("SELECT posts.id, posts.author_id, users.username FROM posts JOIN users ON author_id = users.id");
$row = $res->fetch_assoc();
And now I need to inject the user data into the member class for instantiation:
$author = new Member($row);
And there goes the first problem. I'm not really injecting only the author data but also post data into it. This could be solved in a number of dubious ways. One of them is injecting the relevant values separately instead of entire array like this:
$author = new Member($row["author_id"], $row["username"], ...);
But this isn't very convenient as there could be more to author values than just these two, plus they can vary in number and position, which means I'd have to remember their positions and sometimes fill in the gaps with empty values or NULLs.
Of course, I could just ignore the fact I'm passing foreign data along, memory is cheap, but you may have noticed that there is still one more problem on top of it, which is the user ID key (author_id!= id). Depending on a page/table it can be "id", "uid", "author_id", "from_id", etc. Using an alias for user/author ID in the queries isn't an option either, because sometimes I need those foreign keys to be exactly what they are as they provide more leverage to the code logic.
A potential solution to both issues could be the following:
$author = new Member($row["author_id"]);
$author->getUserURL($row["username"]);
This way I don't have to worry about either foreign data or varying user/author ID keys as all are strictly defined and just once. However, the problem with this scenario is that it makes the member class hardly useful now that most of it has to be done manually. Passing values to each method for one thing. And if previously it would use existing object properties to concoct the needed result, now they mostly just return what's already in the array. What I really want is just inject an array and do something as simple as this:
$author->getUserURL();
$author->getUserAvatar();
$author->getUserHomepage();
...etc. But it seems unachievable unless two conditions are met: 1) Fixed user ID key, 2) Strictly user data. It obviously works on profile and members pages (no foreign data), but not anywhere else. Of course, I could ignore all that foreign data in object and just use array_key_exists() to check for all possible variants of author/user ID like this:
public function __construct($row) {
if(array_key_exists("author_id", $row)) {
$this->id = $row["author_id"];
}
if(array_key_exists("from_id", $row)) {
$this->id = $row["from_id"];
}
...
}
...but this is just not elegant enough. I need a more refined approach and I can't seem to come up with one. It appears that to get it right I have to at least partially break the class or somehow split the array into posts and user data, but I don't see how this could be done without too much work. It's at times like this that I just want to run two DB queries to get two separate arrays of data out of the box, which could solve everything, but everyone knows that that, too, is a bad practice.
So, has anyone got better ideas? I do realize that this isn't the best of questions as it's mostly about efficiency and not "how", but it's small things like this that could lead to bad coding in the long run and I'm still fresh to OOP.
In codeigniter or restful structure, the page can be route to through the URI
For example, if I would like to look at the item list of id: 1 , then I just need to create a path like that:
domain.com/item/view/1
And in controller
function view() {
$id = $this->uri->segment(3);
//database get data and return view...
}
This should be the standard way to implement restful structure. However, when in the member system, and the item id is dependent to the user, how can I protect the link?
So that other user can not brute force to try different ID and read the other member item.
One approach is just compare the user_id and item_id in each function. But if the system is large that means I need to compare in each function and that is a lot of coding and checking .
Are there any smarter way to reduce the overhead / coding ?
Thanks
There is no way to protect URL's. Someone could simply change the number in the URL and fetch different data. If the ID is sensitive, you would not want to pass the information through the URL.
One option is to encrypt the ID being passed in the URL (ex: 29 could be encrypted so it appears as 'S84jG483dJd').
The ID could also be passed through using the code-igniter session library or even with flash data (stored for one request). This way if the ID's are sensitive, the likelihood of anyone altering them would be slim (enable session cookie encryption in CI for more security).
However, if the information is that sensitive, I would always have checks in place before the database is fetched and shown to the user. It is always good practice to code with your users worst intentions in mind!
In domain.com/item/view/1, always stands for base_url/controller/Method
so if you create controller (item), then function (view).
class Item extends CI_Controller
{
public function __construct()
{
parent::__construct();
}
public function view($id)
{
//so in $id it will assign the 3rd value to it.
$new_id = $id;
echo $new_id;
}
I'm working on an application written in PHP. I decided to follow the MVC architecture.
However, as the code gets bigger and bigger, I realized that some code gets duplicated in some cases. Also, I'm still confused whether I should use static functions when quering the database or not.
Let's take an example on how I do it :
class User {
private id;
private name;
private age;
}
Now, inside this class I will write methods that operate on a single user instance (CRUD operations). On the other hand, I added general static functions to deal with multiple users like :
public static function getUsers()
The main problem that I'm facing is that I have to access fields through the results when I need to loop through users in my views. for example :
$users = User::getUsers();
// View
foreach($users as $user) {
echo $user['firstname'];
echo $user['lastname'];
}
I decided to do this because I didn't feel it's necessary to create a single user instance for all the users just to do some simple data processing like displaying their informations. But, what if I change the table fields names ? I have to go through all the code and change those fields, and this is what bothers me.
So my question is, how do you deal with database queries like that, and is it fine to use static functions when querying the database. And finally, where is it logical to store those "displaying" functions like the one I talked about ?
Your approach seems fine, howerver I would still use caching like memcached to cache values and then you can remove static.
public function getUsers() {
$users = $cacheObj->get('all_users');
if ($users === false) {
//use your query to grab users and set it to cache
$users = "FROM QUERY";
$cacheObj->set('all_users', $users);
}
return $users;
}
(M)odel (V)iew (C)ontroller is a great choice choice, but my advice is look at using a framework. The con is they can have a step learning curve, pro is it does a lot of heavy lifting. But if you want to proceed on your own fair play, it can be tough to do it yourself.
Location wise you have a choice because the model is not clearly define:
You'll hear the term "business logic" used, basically Model has everything baring views and the controllers. The controllers should be lean only moving data then returning it to the view.
You model houses DB interaction, data conversions, timezone changes, general day to day functions.
Moudle
/User
/Model
/DB or (Entities and Mapper)
/Utils
I use Zend and it uses table gateways for standard CRUD to avoid repetition.
Where you have the getUsers() method you just pass a array to it, and it becomes really reusable and you'd just have different arrays in various controller actions and it builds the queries for you from the array info.
Example:
$data = array ('id' => 26)
$userMapper->getUsers($data);
to get user 26
enter code here
$data = array ('active' => 1, 'name' => 'Darren')
$userMapper->getUsers($data);`
to get active users named Darren
I hope this help.
I have a custom class that populates a controller's action parameters based on the typehint of the parameter. This works well for documents (using public properties and setters).
My aim is to make the controller simple:
function updateAction(Article $article)
{
$dm = new DocumentManager(); // code elsewhere
$dm->merge($article);
$dm->flush();
return $this->redirect('/article/' . $article->getId());
}
The problem is that the input supplying the fields to programatically populate the Article class doesn't contain all of the properties of an Article class (perhaps the edit form only contains Title and Content, but disregards Author, etc).
I was hoping that the presence of an ID would allow the document to be merged gracefully with what is currently in the database. However, any fields that are missing at the time of a merge will be removed from the document in the database.
Is there a way to update a document in such a way that only the fields that are present (non-null, I guess) are updated?
Rather than hitting the db twice - once for the find, and once for the update, you can use a FIND_AND_UPDATE query.and do it all in one step.
See this docs page for details: http://docs.doctrine-project.org/projects/doctrine-mongodb-odm/en/latest/reference/find-and-update.html
It seems that a clean way would be to bind the model AFTER retrieving it from the database. Something along the lines of ASP.NET MVC's UpdateModel.
function updateAction($id)
{
$dm = new DocumentManager(); // code elsewhere
$article = $dm->getRepository('Article')->find($id);
$this->updateModel($article);
$dm->flush();
return $this->redirect('/article/' . $article->getId());
}
If there are any better suggestions, feel free to answer...
I'm looking for a way to prevent repeated calls to the database if the item in question has already been loaded previously. The reason is that we have a lot of different areas that show popular items, latest releases, top rated etc. and sometimes it happens that one item appears in multiple lists on the same page.
I wonder if it's possible to save the object instance in a static array associated with the class and then check if the data is actually in there yet, but then how do I point the new instance to the existing one?
Here's a draft of my idea:
$baseball = new Item($idOfTheBaseballItem);
$baseballAgain = new Item($idOfTheBaseballItem);
class Item
{
static $arrItems = array();
function __construct($id) {
if(in_array($id, self::arrItems)){
// Point this instance to the object in self::arrItems[$id]
// But how?
}
else {
// Call the database
self::arrItems[id] = $this;
}
}
}
If you have any other ideas or you just think I'm totally nuts, let me know.
You should know that static variables only exist in the page they were created, meaning 2 users that load the same page and get served the same script still exist as 2 different memory spaces.
You should consider caching results, take a look at code igniter database caching
What you are trying to achieve is similar to a singleton factory
$baseball = getItem($idOfTheBaseballItem);
$baseballAgain =getItem($idOfTheBaseballItem);
function getItem($id){
static $items=array();
if(!isset($items[$id])$items[$id]=new Item($id);
return $items[$id];
}
class Item{
// this stays the same
}
P.S. Also take a look at memcache. A very simple way to remove database load is to create a /cache/ directory and save database results there for a few minutes or until you deem the data old (this can be done in a number of ways, but most approaches are time based)
You can't directly replace "this" in constructor. Instead, prepare a static function like "getById($id)" that returns object from list.
And as stated above: this will work only per page load.