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.
}
?>
Related
I'm referring to using the die() function for something else than debugging.
This is a "well it works" situation, but is it bad practice?
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Filesystem\Exception\IOExceptionInterface;
/**
* This Command will help Cjw create a new demo site to start off
* with the Multisite bundle.
*
* Class CreateSiteCommand
* #package Cjw\GeneratorBundle\Command
*/
class RemoveSiteCommand extends ContainerAwareCommand
{
private $vendor; //eg "Cjw"
private $fullBundleName; //eg "SiteCjwNetworkBundle"
private $fullBundleNameWithVendor; //eg "CjwSiteCjwNetworkBundle"
(more vars)
/**
* this function is used to configure the Symfony2 console commands
*/
protected function configure()
{
$this
->setName('cjw:delete-site')
->setDescription('Delete Cjw Site')
->addOption('db_user', null, InputOption::VALUE_REQUIRED, 'If set, the database user will be shown on the instructions');
}
/**
* This function executes the code after the command input has been validated.
*
* #param InputInterface $input gets the user input
* #param OutputInterface $output shows a message on the command line
* #return int|null|void
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
// set optional db_username
$dialog = $this->getHelper('dialog');
$reusable = new \Reusable();
$fs = new Filesystem();
$cmd = new \ColorOutput();
//push only vendors into the vendors array
$vendors = $reusable->getFileList($reusable->getMainDirectory('src'),true);
//select a vendor from the array
$vendor = $dialog->select(
$output,
'Select your vendor [1]: ',
$vendors,
1
);
$bundles_in_vendor = $reusable->getFileList($reusable->getMainDirectory('src/'.$vendors[$vendor]),true);
//push bundles that start with 'Site' into array
$sites = array();
foreach($bundles_in_vendor as $bundle)
{
if($reusable->startsWith($bundle,'Site'))
{
array_push($sites,$bundle);
}
}
$site_to_delete = $dialog->select(
$output,
'Select site to remove: ',
$sites,
1
);
$bundle_deletion_path = $reusable->getMainDirectory('src/'.$vendors[$vendor]).'/'.$sites[$site_to_delete];
$are_you_sure = array('yes','no');
$confirmation = $dialog->select(
$output,
'Are you sure you want to delete: '.$sites[$site_to_delete],
$are_you_sure,
1
);
if($are_you_sure[$confirmation] == 'yes')
{
echo $cmd->color('yellow','attempting to remove bundle in: '.$bundle_deletion_path);
$fs->remove($bundle_deletion_path);
//returns demo
$sitename = strtolower($sites[$site_to_delete]);
$sitename = substr($sitename,0,-6);
$sitename = substr($sitename,4);
$this->setRawSiteNameInput($sitename);
// returns acmedemo
$symlinkname = strtolower($vendors[$vendor].substr($sites[$site_to_delete],0,-6));
$this->removeSymlinks($symlinkname,$this->getRawSiteNameInput());
$this->createSetters($vendor,substr($sites[$site_to_delete],0,-6));
$this->deleteEzPublishExtension();
echo $this->getFullLegacyPath();
echo $cmd->color('green','deletion process completed.');
}
else
{
echo "deletion canceled";
die();
}
function_that_further_deletion_process();
}
This is a symfony2 console script that removes a site from a certain structure
That is perfectly safe, if that is your question, since php as a quasi interpreted language does not leave any traces or artifacts when terminating the execution.
If it is a good practice is another thing. I'd say it is fine for testing purposes, but you should avoid it in final code. Reason is that it makes code hard to maintain. Consider someone else diving into your code and trying to understand the logic. One would actually have to go through all the code to stumble over this detail. Chances is one does not, so the behavior of your code appears broken.
Instead try to do one of these:
throw an exception to leave the current scope. Such an exception might well be caught and swallowed by the calling scope, but it is a clear and predictable way of returning. Obviously you should document such behavior.
return a value clearly out of scope to what would "normally" be returned. So for example null or false instead of a typical value. This forces the calling scope to check the return value, but that is good practice anyway.
restructure your code such that there is no reason to suddenly terminate the execution.
You haven't told us what your intention was with that die.. consequently we cannot tell whether you're using the right tool...
die and it's synonym, exit both exit the script. If that's what you want, it's fine.
I tend to use exit for normal operations, and die for erroneous situations (something you cannot properly recover from, e.g.: couldn't connect to database). I also use a wrapper for die so if needed, I can log such events. Though both commands do the same thing, there's a difference in intention, and I want to express that intention in the code.
In your example I would use exit.
I'm using Doctrine with Symfony in a couple of web app projects.
I've optimised many of the queries in these projects to select just the fields needed from the database. But over time new features have been added and - in a couple of cases - additional fields are used in the code, causing the Doctrine lazy loader to re-query the database and driving the number of queries on some pages from 3 to 100+
So I need to update the original query to include all of the required fields. However, there doesn't seem an easy way for Doctrine to log which field causes the additional query to be issued - so it becomes a painstaking job to sift through the code looking for usage of fields which aren't in the original query.
Is there a way to have Doctrine log when a getter accesses a field that hasn't been hydrated?
I have not had this issue, but just looked at Doctrine_Record class. Have you tried adding some debug output to the _get() method? I think this part is where you should look for a solution:
if (array_key_exists($fieldName, $this->_data)) {
// check if the value is the Doctrine_Null object located in self::$_null)
if ($this->_data[$fieldName] === self::$_null && $load) {
$this->load();
}
Just turn on SQL logging and you can deduce the guilty one from alias names. For how to do it in Doctrine 1.2 see this post.
Basically: create a class which extends Doctrine_EventListener:
class QueryDebuggerListener extends Doctrine_EventListener
{
protected $queries;
public function preStmtExecute(Doctrine_Event $event)
{
$query = $event->getQuery();
$params = $event->getParams();
//the below makes some naive assumptions about the queries being logged
while (sizeof($params) > 0) {
$param = array_shift($params);
if (!is_numeric($param)) {
$param = sprintf("'%s'", $param);
}
$query = substr_replace($query, $param, strpos($query, '?'), 1);
}
$this->queries[] = $query;
}
public function getQueries()
{
return $this->queries;
}
}
And add the event listener:
$c = Doctrine_Manager::connection($conn);
$queryDbg = new QueryDebuggerListener();
$c->addListener($queryDbg);
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
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);
I can't find a way to set the default hydrator in Doctrine. It should be available. Right?
http://docs.doctrine-project.org/projects/doctrine1/en/latest/en/manual/data-hydrators.html#writing-hydration-method
The above documentation page explains how to create a custom hydrator. The drawback here is that you need to "specify" the hydrator each and every time you execute a query.
I figured this out by reading Chris Gutierrez's comment and changing some stuff.
First, define an extension class for Doctrine_Query. Extend the constructor to define your own hydration mode.
class App_Doctrine_Query extends Doctrine_Query
{
public function __construct(Doctrine_Connection $connection = null,
Doctrine_Hydrator_Abstract $hydrator = null)
{
parent::__construct($connection, $hydrator);
if ($hydrator === null) {
$this->setHydrationMode(Doctrine::HYDRATE_ARRAY); // I use this one the most
}
}
}
Then, in your bootstrap, tell Doctrine about your new class.
Doctrine_Manager::getInstance()->setAttribute(Doctrine_Core::ATTR_QUERY_CLASS, 'App_Doctrine_Query');
Chris Gutierrez defined the attribute for the connection instead of globally but I have more than one connection and I want to use this default for all of them.
Now you don't have to call Doctrine_Query::setHydrationMode() every time you build a query.
Here's more information
http://www.doctrine-project.org/projects/orm/1.2/docs/manual/configuration/en#configure-query-class
EDIT: Changes below
I have found a problem with the above. Specifically, doing something like "Doctrine_Core::getTable('Model')->find(1)" will always return a hydrated array, not an object. So I have altered this a bit, defining custom execute methods for use in a Query call.
Also, I added memory freeing code.
class App_Doctrine_Query extends Doctrine_Query
{
public function rows($params = array(), $hydrationMode = null)
{
if ($hydrationMode === null)
$hydrationMode = Doctrine_Core::HYDRATE_ARRAY;
$results = parent::execute($params, $hydrationMode);
$this->free(true);
return $results;
}
public function row($params = array(), $hydrationMode = null)
{
if ($hydrationMode === null)
$hydrationMode = Doctrine_Core::HYDRATE_ARRAY;
$results = parent::fetchOne($params, $hydrationMode);
$this->free(true);
return $results;
}
}
That'd be a great idea, and on reading your question I thought it'd be something you could do via Doctrine. However, reading through the code makes me think you can't:
Doctrine_Query::create() creates a new query specifying only the first argument of Doctrine_Query_Abstract::__construct(), the connection, without specifying the second argument - the hydration mode. No calls to configuration are made. As no hydrator is passed, a new Doctrine_Hydrator is created, and its constructor equally does not look anywhere for a configuration option, and thus it has the default Doctrine::HYDRATE_RECORD setting.
Perhaps subclassing Doctrine_Query with the below factory method is the easiest option?
public static function create($conn = null)
{
return new Doctrine_Query($conn,Doctrine::HYDRATE_ARRAY);
}