Zend Framework and Preventing Fat Controllers - php

Avoiding Fat Controller
So I'm using Zend Framework and I have a question involving preventing fat controllers with one of my actions. Basically I am normalizing a CSV file into my database.
This means that I have to get the feed and then use my model.
The feed grabbing is just there to show how it works, but that is now an Action Helper.
I am using the Data Mapper pattern with Zend Framework. I hate that I am doing this in my Controller. All of those setProperty()->setProperty()->setProperty() look incredibly fugly and I feel like I am doing it in the wrong place? Would it be a better option to just create some kind of service layer where I pass the entire $feed and then in that class I instantiate my Models and my Mapper?
Also, I need to normalize, which means I should be using a transaction, but I'm unsure where I should start my transaction. Because of the way I am doing things currently, the only place I could ever consider is in my Controller. wow.. that would be an awful place.
How can I get the model behaviour and operations out of my controller?
ImportController.php
public function indexAction() {
$start = $this->getRequest()->getParam('start');
$end = $this->getRequest()->getParam('end');
$url = "http://www.domain.com/admin/GetBookingData.aspx";
$client = new Zend_Http_Client();
$client->setParameterGet('dateEnteredMin', $start);
$client->setParameterGet('dateEnteredMax', $end);
$client->setParameterGet('login', 'login');
$client->setParameterGet('password', 'password');
$client->setUri( $url );
$client->setConfig(array(
'maxredirects' => 0,
'timeout' => 30));
// Send the request.
$response = $client->request();
// Grab the feed from ->getBody and add it to $feed
$feed = $this->csv_to_array(trim($response->getBody()));
// The first item in the array is the heading in the CSV, so we can remove it from the array using shift().
$title = array_shift($feed);
// Create my Models and Mappers.
// *** EVERYTHING BELOW HERE IS WHAT I DON'T LIKE ***
$bookings = new Bookings_Models_Bookings();
$property = new Bookings_Models_Property();
$clients = new Bookings_Models_Clients();
$bookingsMapper = new Bookings_Models_Bookings_Mapper();
$propertyMapper = new Bookings_Models_Property_Mapper();
$clientsMapper = new Bookings_Models_Clients_Mapper();
$bookings->setId($feed[9])
->setPropertyId($feed[1])
->setClientId($feed[2])
->setDate($feed[4]);
$bookingsMapper->save($bookings);
$property->setId($feed[1])
->setPropertyName($feed[23])
$propertyMapper->save($bookings);
$clients->setId($feed[2])
->setFirstName($feed[20])
->setLastName($feed[21])
$clientsMapper->save($clients);
}

Service layer is probably the way I'd go. So you'd create a service class that looks something like this:
class Your_Service_Import
{
public function importFromCsv($csv)
{
// etc.
}
}
you'd then move all of your controller method code that's after the csv_to_array call into that method, leaving the end of your controller method looking something like this:
$feed = $this->csv_to_array(trim($response->getBody()));
$service = new Your_Service_Import();
$service->importFromCsv($feed);
This makes it easier to test your import code (since it's in a standalone class) and easier to reuse in other parts of your application.

I'd go one step (or two steps) further than #Tim Fountain
Create a Service or Domain Helper that takes a start, end (and can be configured with a username password and url) and returns the csv list as an array.
Create a Service that maps a known dimension array (the csv) and maps it onto the database.
Your controller will then just be
$start = $this->getRequest()->getParam('start');
$end = $this->getRequest()->getParam('end');
$dataService = new Your_Service_Get();
$data = $dataService->get($start, $end);
$mapService = new Your_Service_Map();
$mapService->map($data);

Related

Delete expired tokens in oauth2-server-php

I'm using the bshaffer/oauth2-server-php module to authenticate my rest api. Everything works fine but meanwhile I have over 20,000 access tokens in the database.
As I read, the framework will not delete expired tokens automatically or by config parameter. So I'm trying to do the job by my own.
I know the tables which hold the tokens and I already built the delete statements. But I can't find the right place (the right class/method) to hook with my cleanup routine.
I think a good option here is to create a Command, here a small part in Symfony but can also be a plain PHP command since you know the table names and just execute it every hour:
$doctrine = $this->getContainer()->get('doctrine');
$entityManager = $doctrine->getEntityManager();
$qb = $entityManager->createQueryBuilder();
$qb->select('t')
->from('OAuth2ServerBundle:AccessToken', 't')
->where('t.expires < :now')
->setParameter('now', new \DateTime(), Type::DATETIME);
$accessTokens = $qb->getQuery()->getResult();
$cleanedTokens = 0;
foreach ($accessTokens as $token) {
$entityManager->remove($token);
$cleanedTokens++;
}
This just covers the access_token table as an example. By the way I still could not get how to change the Token expiration with this library ;)
UPDATE: To change the lifetimes just edit parameters.yml and add
oauth2.server.config:
auth_code_lifetime: 30
access_lifetime: 120
refresh_token_lifetime: 432000
I didn't read the complete source off bshaffer oauth server.
But want you can try is to create your own class by extending from class Server.
And use the __destruct() function to be executed when the object customServer is destroyed by PHP
<?php
include('src/OAuth2/Server.php'); # make sure the path is correct.
class customServer extends Server {
public __construct(($storage = array(), array $config = array(), array $grantTypes = array(), array $responseTypes = array(), TokenTypeInterface $tokenType = null, ScopeInterface $scopeUtil = null, ClientAssertionTypeInterface $clientAssertionType = null)) {
parent::_construct($storage, $config, $grantTypes, $responseTypes, $tokenType, $scopeUtil, $clientAssertionType);
}
}
public function __destruct() {
// run your cleanup SQL from here.
}
?>

Change the type of an object through a method call without returning a new instance

Recently I stumbled upon a problem about object oriented design.
A little bit about the background:
I decided to create a good old todo-app to learn a bit more about
PHPUnit and testable PHP in general. Requirements were the usual
retrieve/store/edit tasks. The todos should be stored in a MySQL
database.
My idea was to create a base class Todo, which holds a PDO instance
as well as the id and title of the todo (equivalent to their
representation in the database).
Additionally it should have two factory methods responsible for
creating instances of different todos. These are:
New todos defined with the frontend of the app
Already saved todos.
The reason for this is the different behaviour of storing the
current state (the title/description) in the database.
My first attempt without subclasses looked something like this:
class Todo {
[...] // fields and other methods
public function save() {
$statement = '';
$parameters = [];
if (is_null($this->_id)) {
$statement = 'insert into todos (title) values(?)';
$parameters = [$this->_title];
} else {
$statement = 'update todos set title = ? where id = ?';
$parameters = [$this->_title, $this->_id];
}
$prepared_statement = $this->_pdo->prepare($statement);
$result = $prepared_statement->execute($parameters);
return $result;
}
}
The if statement revealed that I basically just checked for the type of
the todo. In other words: Is the todo a new todo that I want
to store with an insert-statement, or is the todo already in
the database so that I have to treat it with an update-statement.
To get rid of the if statement I introduced the subclasses NewTodo and SavedTodo.
Eventually I ended up with the following:
abstract class Todo {
[...] // fields and other methods
public abstract function save();
}
class NewTodo extends Todo {
public function save() {
$statement = 'insert into todos (title) values(?)';
$parameters = [$this->getTitle()];
$prepared_statement = $this->getPdo()->prepare($statement);
$result = $prepared_statement->execute($parameters);
return $result;
}
}
class SavedTodo extends Todo {
public function save() {
$statement = 'update todos set title = ? where id = ?';
$parameters = [
$this->getTitle(),
$this->getId()
];
$prepared_statement = $this->getPdo()->prepare($statement);
$result = $prepared_statement->execute($parameters);
return $result;
}
}
Although this seems way more reasonable to me, I'm now facing the actual
problem with this approach.
I - in a naive way - would expect an instance of NewTodo that calls
the save() method to be an instance of SavedTodo afterwards.
Obviously this is not the case in my implementation.
After this "wall of introduction", finally my questions:
Is there any way to implement this in PHP without returning a new instance?
And maybe more important:
Is it reasonable to have this kind of behaviour implemented at all?
I don't think trying to represent an objects state through its type is a good idea. As you have pointed out, what do you do when its saved, and it then should be a SavedTodo. Even if you implemented that, it would be a strange structure to work with. For example:
$t = new NewTodo();
$t->content = 'testing';
$savedTodo = $t->save();
$t = $savedTodo; // need to overwrite the original Todo with the SavedTodo
As you can see, it's not what you want expect to have to do, if you were to look at the code from an outsiders perspective.
The if statement in your first case is not a problem that needs solving. In OOP, inheritance is a feature, not a requirement so if it doesn't fit the job, don't use it.
A good rule of thumb in OOP is to loosley follow SRP (Single Responsibility Principle), which is basically to have clear roles for your classes where they aren't doing too much. Ideally they should have a single responsibility.
Personally I wouldn't put the save method on the Todo because it is an entity, whose responsibility is to represent a Todo in the system. Giving a Todo the ability to save itself could be considered another responsibilty, so I would prefer to have a TodoMapper class that deals with fetching, inserting, updating and deleting.
Also, the Todo entity in the system shouldn't be concerned about what method of data storage the system uses, or what type of database etc etc, or even if gets stored at all.
For example:
$todoMapper = new TodoMapper($pdo);
$todo = new Todo();
$todo->content = 'testing';
$todoMapper->save($todo);
In this system, the mapper can check if the Todo has an ID or not, and do an UPDATE or INSERT accordingly. Or if you prefer, you can have insert() and update() methods on the mapper class.
Another benefit of this design is the Todo doesn't need the PDO object because it doesn't concern itself with CRUD operations.
Martin Fowler has more information on Data Mapper classes here.

calling a function from action in symfony 1.4

I'm in need of calling a function located in a class stored in app/lib directory before every action (ie. in preExecute action)
In that function I do need a $this pointer from an action.
I can do it in a static way - Classname::functionname(), however it results with a PHP warning, which I want to avoid.
For now I handle it with
ini_set('display_errors', '0'); # don't show any errors...
error_reporting(E_ALL | E_STRICT); # ...but do log them
but it's an ugly way..
How can I do it in a dynamic way?
===============
EDIT:
as requested - I add a code to show what am I actually doing with that $this pointer
...
$c = new Criteria();
$c->add(ArticlePeer::PUBLISHED, true);
$articles = ArticlePeer::doSelect($c);
$this->articles = $articles;
...
Yes, I know that I can put this Select in preExecute action in every module and it will be working fine. However - I have many modules, and as I have a set of actions that are all the same for all those modules, so putting them in one procedure and just calling that function would be the smartest way...
Especially when it comes to maintaining the app - It's only one place to change code, instead of a dozen of them...
In your class inside your app/lib folder, you simply have to return $articles:
$c = new Criteria();
$c->add(ArticlePeer::PUBLISHED, true);
return ArticlePeer::doSelect($c);
Then, inside your preExecute():
public function preExecute()
{
$this->articles = Classname::functionname();
}
If you have to return multiple value, you can do it using an array:
$c = new Criteria();
$c->add(ArticlePeer::PUBLISHED, true);
$articles = ArticlePeer::doSelect($c);
$c = new Criteria();
$c->add(BookPeer::PUBLISHED, true);
$books = BookPeer::doSelect($c);
return array(
'articles' => $articles,
'books' => $books,
);
End then, use this array to populate your variable inside your action:
public function preExecute()
{
$data = Classname::functionname();
$this->articles = $data['articles'];
$this->books = $data['books'];
}
You cannot change the value of $this in a method. Never.
Instead, look at the class how you can access $articles from outside, there should be something like getArticles(). It looks like the class is a Propel model, those have generated getters like that.
Dependent on what your end goal is, there might be better solutions than instantiating this model, calling the mysterious method and then getting the attribute. After all, classes are not just collections of functions and should not be treated like that. Not repeating code is good, but it has to be done with some sense.

How to convert an existing PHP library file so it can be used in a CakePHP framework?

I have this library in PHP non-Cake format, the usual PHP scripting which currently works like a charm. I need to use this in a Cake framework. The library file is as follow: (example extracted)
<?php
// REST API functions
function sendAction($itemurl, $itemimageurl, $sessionid, $userid, $rating=""){
global $someapiwebsiteURL, $apiKey, $tenantId;
$somewebsiteAPI = $someapiwebsiteURL.$action."?apikey=".$apiKey.
.....
................
}
//Codes extract
?>
I've come across a few ways of doing it. Currently confused, how am I going to place this library file into my Cake framework?
App::import()
Datasource
The functions in the library file above (I supposed it'd be used in one of my Controllers to render the data outputting through the view).
Currently working in a non-Cake framework structure, the view page is such as: (example extracted)
<?php
// my view page
$viewResponse = sendAction($itemdescription ,$itemurl , $itemimageurl,$sessionid,$userid);
//sample code only
?>
Both the files are working fine. The logic of putting it in a CakePHP framework is the problem here. Anyone may suggest "the" way of doing this without over-strenuously working on a data source? If we have to use a data source in App/models/datasources/, how exactly is the structure of it? Like, e.g., in datasource file, do we include the library functions? or is it some generic ReST datasource file which can be found here: CakePHP ReST datasource . I've gone through the cookbook chapter on datasource and understand we have to define the datasource in our database.php, but if someone is certain about their way of accomplishing it either using datasource or app::import() method, please share with more details?
UPDATE:
Hi Lionel!, thanks for filling up. Well, actually users will click on view action: function view (){} in my foods_controller. I'm appending some scripts here to include my view function in my foods_controller so maybe it may help you to help out easier. Thanks..
function view($id = null) {
if (!$id) {
$this->Session->setFlash(__('Invalid food', true));
$this->redirect(array('action' => 'index'));
}
$this->set('food', $this->Food->read(null, $id));
}
The view action triggers the send_action function, (each time a user clicks on view page on foods controller). So each time, a user clicks on view action, his (dynamic variables): userid, sessionid, that page's itemid, url, itemdescription; (timerange value is a static string value "ALL"), and if any (etc.), so far only these values are available: Will be used as the "parameters" in the Send Action function. What you wrote is close to what the codes can do. You're right. Except we should include the Send Action function inside the view() in foods controller?
If we look at dynamically filling in the variables mentioned in the point above, could you modify your second code (the code from your product_controller, e.g.) so it also works to receive the variables dynamically? (as you asked in the last update: how to get the parameters..)
Just to make it clear.
A user views the page. The send action collects data and send to the API. (as we've already done by calling the function in the library the (ACME.php). *just waiting for your update if possible, thanks.
In the function view() of the foods controller: there's also an additional calling. The (2)second calling which is this:
$recommendResponse = getRecommendations("otherusersviewed", $itemId, $userId);
The second calling calls the ACME.php library file in which there consists the (2)second function that retrieves data, here it is: (it's in working order, but just needs to be changed into a public static function like you did for the (1)first function. Could you help to modify this code too, please?:
function getRecommendations($recommendationType, $itemId, $userId){
// sample code similar to the first one.
}
That's all to it. It seems quite simple in the normal PHP format, and it works easily, but getting it on an MVC framweork is a bit challenging for some, a lot for me. Thanks for helping out, Lionel. :-)
P.S. Hi Lionel, I notice something missing in the library after changes? Look originally we have this:
$somewebsiteAPI = $someapiwebsiteURL.$action."?apikey=".$apiKey.
Look, the variables for $SomeWebsiteAPI and $SomeApiWebsiteURL are different. Did I miss out something? or you have modified so it is more efficient ? I see that the variable named $SomeWebsiteAPI is modified to become variable called $link ? and variable $SomeApiWebsiteURL is changed to the named variable, $url, am I right ? .. thanks.
Thanks, best regards. John Maxim
To me, if I have this piece of code, I would first wrap it into a static (or normal) class, and named it ACME, then I will move the acme.php into /apps/libs/acme.php. Then in the controller, I will use App::import('Lib', 'acme'). This action do nothing but just requiring the file, so you can just use it instantly by calling ACME::sendAction(...).
And regarding the global thing, you might just need to declare a static (or normal) class, then define the shared variables as part of the class properties, so you can share them among all the functions in the class.
For example, this is the /app/libs/acme.php
class ACME {
private static $someapiwebsiteURL = "http://thewebsite/api/1.0/";
private static $apiKey = "0010KIUMLA0PLQA665JJ";
private static $tenantId = "THE_TENANT_NAME";
/**
* Simple builder to build links from array of $params
*
* #param string $url The api url
* #param array $params The given parameters
* #return string built url
*/
private static function BuildLink($url="", $params=array()) {
$link = $url;
foreach($params as $k=>$v) {
$link .= "&$k=$v";
}
//Replace the first & to ?
$link = preg_replace("/&/", "?", $link, 1);
//Not sure if we need URL encode here, please uncomment this
//if the API could not work.
//$link = urlencode($link);
return $link;
}
public static function SendAction($action, $itemId, $itemdescription, $itemurl, $itemimageurl, $sessionid, $userid, $rating="") {
$somewebsiteAPI = self::BuildLink(self::$someapiwebsiteURL.$action, array(
"apikey"=>self::$apiKey,
"sessionid"=>$sessionid,
"userid"=>$userid,
"tenantid"=>self::$tenantId,
"itemid"=>$itemId,
"itemdescription"=>$itemdescription,
"itemurl"=>$itemurl,
"itemimageurl"=>$itemimageurl,
/**
* Assuming your API smart enough to only use this value when
* the action is "rate"
*/
"ratingvalue"=>$rating
));
$xml = simplexml_load_file($somewebsiteAPI);
return $xml;
}
public static function GetRecommendations($recommendationType, $itemId, $userId) {
$somewebsiteAPI = self::BuildLink(self::$someapiwebsiteURL.$recommendationType, array(
'apikey'=>self::$apiKey,
'tenantid'=>self::$tenantId,
'itemid'=>$itemId,
'userid'=>$userId
));
$xml = simplexml_load_file($somewebsiteAPI);
return $xml;
}
}
And in your controller
App::import('Lib', 'acme');
class FoodController extends AppController {
//Food is plural already I assume? You can just use
//food, should be ok I think, else it will be weird
//to use /foods/view/?
var $name = "Food";
var $uses = array("Item", "Food");
function view($id="") {
//We accepts only valid $id and $id > 0.
//Take notes that this $id will be a string, not int.
if (ctype_digit($id) && $id > 0) {
//I don't know how you would gather the information, but I assume you
//have a database with the information ready.
//I assumed you have an `items` table
$item = $this->Item->findById($id);
$sessionid = "00988PPLO899223NHQQFA069F5434DB7EC2E34"; //$this->Session->...?
$timeRange = "ALL";
$userid = "24EH1725550099LLAOP3"; //$this->Auth->user('id')?
if (!empty($item)) {
$desc = $item['Item']['description'];
$url = "/foods/view/".$id;
$img = $item['Item']['img'];
$viewResponse = ACME::SendAction("view", $id, $desc ,$url, $img, $sessionid, $userid);
$this->set('food', $this->Food->read(null, $id));
}else{
$this->Session->setFlash(__('Invalid food', true));
$this->redirect(array('action' => 'index'));
}
}else{
$this->Session->setFlash(__('Invalid food', true));
$this->redirect(array('action' => 'index'));
}
}
}
Edit
The code has been filled up, and of course, without any warranty :). I personally don't really like to have long arguments in a function (like SendAction, error prune), rather use shorter one like the $params in ACME::BuildLink. But just to respect your code, I didn't modify much on the SendAction method.
Then I'm not too sure how you would make use of this code, so I assumed you have a ProductsController, and somehow the user trigger url like /products/send_action/. If you can provide more information, then we would be able to help out.
Edit Again
I have modified the ACME class, as well as the controller. Yea I do miss out some variables, but I had added them back to the updated code.
Not too sure if it would work (perhaps typo), you can just modify the code if it doesn't work for you.
And for personal conventions, I usually capitalize methods which are static, like ACME:GetRecommendations or ACME::SendAction.
Oh yea, I better stick back to the variables you used. Sorry for modifying them, just I don't like long names :)
And btw, the RoadRunner's ACME Corporation? Lol!
Cheers
Lionel

MVC model where to put data specific checks

I'm writing my first application with Zendframework.
My question is about the Model–View–Controller (MVC) architectural pattern.
I currently have a model with refer to a database table.
Here's the classes that I currently have :
Model_Person
Model_PersonMapper
Model_DbTable_Person
Now, I see a lot of examples on the net, but all of them are simple cases of insert/update/delete.
In my situation, I have to check if a person exists, and if it doesn't, I have to insert it and retrieve the ID (I know save return the Id, but it's not exactly what I have to do, this is and example).
It's quit simple, but I want to know where to put the database logic for all the others specific cases. Some others cases might involve checks across other tables or ... whatever !
Should I add all the specific functions in my Model_XXXXMapper with something that would be very specific with the current validation/process that I want to do? like a function getIdOfThePersonByNameOrInsertIfNotExists() (sample name of course!!!)
Or should it reside in the controller with some less specifics access to my model would be validated?
In other word, where do I put all the data specifics functions or check ?
I think the real work should occur in your model objects, not in the controller. Any selects/creates that start with the person table would be in the DbTable_Person object, things like:
// DbTable_Person
// returns sets of or single Person objects
public function createByName( $name ) // perhaps throws exception if name already exists
public function findById( $id )
public function findByName( $name )
public function findHavingAccount( $account_id ) // references another table
// controller
// with your example, like what Galen said,
// I would let the controller handle this logic
$person = $person_table->findByName($name);
if ( !$person ) {
$person = $person_table->createByName($name);
}
if ( !$person ) { throw new Zend_Exception('huh?'); }
$id = $person->id; // you wanted the ID
I would definitely split the function up into search/create functions.
Here's a basic implementation...
$personTG = new Model_PersonTableGateway;
if ( !$person = $personTG->findByName( $name ) ) {
$person = new Model_Person;
$person->name = $name;
// other variables
$newPersonId = $personTG->create( $person ); // creates a new person
}
I use table gateway. You can substitute your class for the TG.
You can have the create() function return just the id of the newly created person, or the entire person...it's up to you.
You might be interested in Zend_Validate_Db_NoRecordExists and its sister. If you are using Zend_Form you can add this validator to your form element. Many folks use Zend_Form to validate and filter data before they reach the domain model.
If you are not using Zend_Form, you can simply use this validation class in your service layer. A simple service class could be something like
`
class Service_Person_Validate
{
public function creatable($data)
{ // return true|false
}
}

Categories