I would like to make the following function available throughout my routers.
public function getAll($request, $response, $args, $table, $prefix, $order, $PermRead) {
// retrieve all records
// WORKING... Security questions
// 1. First, check to make sure authenticated (via JSESSION_ID, etc.)
// 2. Automatically apply site_id to ALL queries
// 3. Apply sch_id to this query
// 4. Get permissions
$status = null;
$site_id = $sch_id = 1;
if (!$PermRead) {
$status = 403; // 403 Forbidden
} else {
$sql =
"SELECT * from " . $table .
" WHERE " . $prefix . "_site_id = " . $site_id .
" AND " . $prefix . "_sch_id = " . $sch_id .
" AND " . $prefix . "_deleted_timestamp IS NULL " .
" ORDER BY " . $order;
$rows = $this->dbw->run($sql);
}
if (!$status) {
$status = 200; // 200 OK
}
return $response->withStatus($status)->withJson($rows);
}
However, I get the following error: Fatal error: Using $this when not in object context in C:\Wamp\www\ravine\server\src\routes.php on line 26
How should I make this function available so that I can call it inside my route, like this:
// retrieve all classroom records
$app->get('/classrooms', function ($request, $response, $args) {
$PermRead = true; // check permissions
return getAll($request, $response, $args, "classroom", "room", "room_name", $PermRead);
});
Implementation of Werner's suggestion to use the application's container:
I created a class called Common in /lib/common.php:
<?php
namespace lib;
use Interop\Container\ContainerInterface;
class Common {
protected $ci;
private $site_id = 1;
//Constructor
public function __construct(ContainerInterface $ci) {
$this->ci = $ci;
}
public function getAll($table, $prefix, $order, $PermRead) {
// retrieve all records
// WORKING... Security questions
// 1. First, check to make sure authenticated (via JSESSION_ID, etc.)
// 2. Automatically apply site_id to ALL queries
// 3. Apply sch_id to this query
// 4. Get permissions
$status = null;
$site_id = $sch_id = 1;
if (!$PermRead) {
$status = 403; // 403 Forbidden
} else {
$sql =
"SELECT * from " . $table .
" WHERE " . $prefix . "_site_id = " . $site_id .
" ORDER BY " . $order;
$rows = $this->ci->dbh->run($sql);
$this->ci->response->withJson($rows);
}
if (!$status) {
$status = 200; // 200 OK
}
return $this->ci->response->withStatus($status);
}
}
Then, I added the class to /src/dependencies.php
<?php
require __DIR__ . '/../lib/common.php';
$container = $app->getContainer();
// common router functions
$container['common'] = function ($c) {
$common = new lib\Common($c);
return $common;
};
Now, within my individual router files, I'm able to call the common function like this in /routers/classroom.router.php:
// retrieve all classroom records
$app->get('/classrooms', function ($request, $response, $args) {
$PermRead = true; // check permissions
return $this->common->getAll("classroom", "room", "room_name", $PermRead);
});
The container carries $request, $response and $args (and other functions).
I would suggest making use of application containers to simplify your application structure. Slim 3 has been designed to work well with application containers.
Pass the container to your class method - you will then have the request and response objects available via the (shared) container, since Slim assigns those (request and response) to the container object automatically.
You can even add/assign your database connection (and whatever else you want to make available to other classes) to the container, then you only need to pass the same container to all functions that require database functionality.
The idea is that you can write classes that can be re-used in other projects, even if you decide to use something different than Slim next time. As long as the framework uses application containers, you can probably re-use your classes.
Eg: In you index.php
$container = $app->getContainer();
$container['db'] = $myDbConnection;
$container['request'] and $container['response'] are assigned automatically by the framework.
E.g MyClass.php
use Interop\Container\ContainerInterface;
class MyClass {
public function getAll(ContainerInterface $container) {
// ...
$myDb = $container['db'];
// ... do DB stuff
$response = $container['response'];
return $response->withStatus($status)->withJson($rows);
}
}
$this is not available in your function, the easiest way would be to just add it as an parameter.
Something like:
public function getAll($request, $response, $args, $table, $prefix, $order, $PermRead, $app) {
[..]
$app->dbw->...;
Then call it with $this in the parameter
return getAll($request, $response, $args, "classroom", "room", "room_name", $PermRead, $this);
Related
I know this answer has been answered already but it seems like I couldn't understand anything in my case (I'm still very new to web developing, learning frontend since this october and I jumped onto php at the start of this month).
My function is supposed to check if user is logged in and I couldn't understand the answers I read for my problem cause it seems like in the answers I found, the functions weren't custom? I might be wrong and I hope if I am, you can laugh about it.
So, to get a bit of context, I encounter this error when I try to call the function "check_logged_in()" in one of my controllers, this controller in question (it's called Upload) extends the main Controller from my core folder (and I triple checked if my init file has required it so I can use it globally).
Then I call a custom function from the main controller to load models if needed (in this case, I need to load my user model to get access to the "check_logged_in()" function since it's written there).
And this is where thing happen. I'll provide a bit of code so you guys can understand what I'm saying.
The Upload controller
<?php
class Upload extends Controller
{
function index()
{
header("location:" . ROOT . "upload/image");
die;
}
function image()
{
$user = $this->loadModel("user");
if(!$result = $user->check_logged_in()){
header("location:" . ROOT . "login");
die;
}
$data['page_title'] = "Upload";
$this->view("upload", $data);
}
}
The main Controller
<?php
class Controller
{
protected function view($view, $data = [])
{
if (file_exists("../app/views/" . $view . ".php")) {
include "../app/views/" . $view . ".php";
} else {
include "../app/views/404.php";
}
}
protected function loadModel($model)
{
if (file_exists("../app/model/" . $model . ".php")) {
include "../app/model/" . $model . ".php";
return $model = new $model();
}
return false;
}
}
And the bit of code from the user model that is called
<?php
class User
{
function check_logged_in()
{
$DB = new Database();
if (isset($_SESSION['user_url'])) {
$arr['user_url'] = $_SESSION['user_url'];
$query = "select * from users where url_address = :user_url limit 1";
$data = $DB->read($query, $arr);
if (is_array($data)) {
//logged in
$_SESSION['user_name'] = $data[0]->username;
$_SESSION['user_url'] = $data[0]->url_address;
return true;
}
}
return false;
}
}
Thanks in advance for your time mates, I hope my noob self is clear enough for you to understand ^^'
Checking for the correct value of the $user variable should fix the error.
...
function image()
{
$user = $this->loadModel("user");
if(!$user || !$user->check_logged_in()){ // Here
header("location:" . ROOT . "login");
die;
}
$data['page_title'] = "Upload";
...
Then on $user is false condition will be
!false || !false->check_logged_in() which will lead to true.
Otherwise on $user is User:
!(object User) || !(object User)->check_logged_in() will call User::check_logged_in() method.
I have a class, that has a method call to an api resource, and other methods that use the api call method output. As it is right now, every time some other method calls the api method, the api method makes a request to the api over and over again. What would be the best way to make a single call to the api and then use the output trough my class? See example.
class foo {
$param1;
$param2;
function getApi {
return 'call_to_api' . $this->param1 . $this->$param2;
}
function do_stuff_1 {
return 'do_some_other_stuff . '$this->getApi() . $param1
}
function do_stuff_2 {
return 'do_some_other_other_stuff . '$this->getApi() . $param2
}
}
You can use Laravel Cache in your requests to the API:
$url = 'http://api.url.com?data1=x&data2=y';
if (Cache::has($url))
{
$apiResult = Cache::get($url);
}
else
{
$apiResult = $this->apiGetResult($url);
Cache::put($url, $apiResult, 5); // cache for 5 minutes
}
return $apiResult;
So, your API will only be hit if the it was never hit before or if the cache expired. The nice thing of using Laravel cache is that it works between requests, so if your application needs that same data in the next request it will not hit the API again.
You can create sort of a cache by calling the API again only if the parameters have been modified:
class foo {
$param1;
$param2;
private $resultAPI = '';
private $paramModified = false;
function getApi {
if ($this->resultAPI == '' || $this->paramModified) {
$this->resultAPI = 'call_to_api' . $this->param1 . $this->$param2;
$this->paramModified = false;
}
return $this->resultAPI;
}
function setParamX($val) {
if ($this->paramX != $val) {
$this->paramX = $val;
$this->paramModified = true;
}
}
function do_stuff_1 {
return 'do_some_other_stuff . '$this->resultAPI . $param1
}
function do_stuff_2 {
return 'do_some_other_other_stuff . '$this->resultAPI . $param2
}
}
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 am using the Slim PHP framework to create a RESTful API for my app. I would like all URLs to be able to accept parameters for sorting and pagination. Can someone tell me the best way to do this?
Also, can someone provide me with some proper REST URIs to this? (i.e. http://domain.com/api/category/fruit/?sort=DESC&results=25&page=2)
<?php
require 'Slim/Slim.php';
$sort = "ASC";
$results = 10;
$page = 1;
$app = new Slim();
$app->get('/wines', function () use ($app) {
$sort = $app->request()->params('sort');
$results = $app->request()->params('results');
$page = $app->request()->params('page');
getWines();
});
$app->get('/categories', function () use ($app) {
$sort = $app->request()->params('sort');
$results = $app->request()->params('results');
$page = $app->request()->params('page');
getCategories();
});
$app->get('/sub-categories', function () use ($app) {
$sort = $app->request()->params('sort');
$results = $app->request()->params('results');
$page = $app->request()->params('page');
getSubCategories();
});
$app->run();
function getWines() {
$sql = "select * FROM wine ORDER BY name " . $sort . " LIMIT " . $page . " , $results";
try {
$db = getConnection();
$stmt = $db->query($sql);
$wines = $stmt->fetchAll(PDO::FETCH_OBJ);
$db = null;
echo '{"wine": ' . json_encode($wines) . '}';
} catch(PDOException $e) {
echo '{"error":{"text":'. $e->getMessage() .'}}';
}
}
?>
There are many ways to solve this, I would recommend to use the Template Method pattern, so you defined a common behavior in the parent class, and handle the specific details in the child classes.
abstract class SortPageHandler {
public function getUrlHandler($app)
{
$me = $this;
return function () use ($app, $me) {
$sort = $app->request()->params('sort');
$results = $app->request()->params('results');
$page = $app->request()->params('page');
$app->response()->write($me->getItems($sort, $results, $page));
};
}
abstract public function getItems($sort, $results, $page);
}
class WineHandler extends SortPageHandler {
public function getItems($sort, $results, $page)
{
//return wines
}
}
class CategoryHandler extends SortPageHandler {
public function getItems($sort, $results, $page)
{
//return categories
}
}
class SubCategoryHandler extends SortPageHandler {
public function getItems($sort, $results, $page)
{
//return sub-categories
}
}
So the parent class SortPageHandler handles the common part with the function needed for Slim and the pagination and the sorting. Each getItems() method is specific to each entity. By declaring this method abstract in SortPageHandlerwe force all sub-classes to implement this functionality.
Now the Slim codes looks very clean:
$app = new \Slim\Slim();
$wineHandler = new WineHandler();
$categoryHandler = new CategoryHandler();
$subCategoryHandler = new SubCategoryHandler();
$app->get('/wines', $wineHandler->getUrlHandler($app));
$app->get('/categories', $categoryHandler->getUrlHandler($app));
$app->get('/sub-categories', $subCategoryHandler->getUrlHandler($app));
$app->run();
As always, you could refactor this code even more, but it's to give you an idea how this could be solved.
REWROTE: SOLVED
Hi there,
I currently worked on a simple application with a database, a bunch of controllers, views and a model class.
I coded the controllers and inserted the db connections directly
E.g.
Each controller method has his own PDO to connect to a specific database+table.
I refactored this because I had too many active PDOs per 1 controller, so I started to code the model class.
A short information: The model class is once accessed by a controller, when the controller is called.
Once the model object is constructed it is available through the whole controller, and you can pass custom request to it.
E.g. getUserById => Gets the User from the current controller table with the id "xy".
Now that I finally finished the model class and added my PDO class to the model:
Everytime I want to access any of my controllers, my FireFox asks me where to safe the empty "test.php" (test.php is my index file).
Restarting Apache2 / PHP / MySQL did not work, if I remove a certain part of my code, there is no error, but this part is essential. ;)
model.php
class Model extends db_pdo_adapter{
public function __construct($name)
{
$name = strtolower($name);
$this->dbh = parent::connect(Model::ATTR_HOST, Model::ATTR_USR, Model::ATTR_PASSWD, Model::ATTR_DB);
$this->name = $name.'s';
//$this->ATTR_TBL = $this->name;
}
public function __call($name,$values)
{
$string = preg_replace('/^get/','',$name);
$string = strtolower($string);
$by = preg_split('/by/',$string);
$by = strtolower($by[1]);
return $this->get($string, $by, $values); // when I remove this part no empty file is served.
}
public function get($item, $by, $conditions) // single item if is_no_array
{
if($item = preg_replace('/$s/','',$this->name))
{
$item = '*';
}
//if(count($conditions) <= 1)
//{
$query = 'SELECT ' . $item . ' FROM ' . $this->name . ' WHERE ' . $by . ' = :' . $by . '';
$pname = ':'.$by;
//}
$this->dbh->getStatement($query);
$this->dbh->bindParam($pname,$conditions[0]); // ->dbh-> also was missing
$this->dbh->exec();
return ($this->dbh->fetchAll());
}
}
Extract of test.php
header('Content-Type: text/html;');
$time_start = microtime(true);
include_once('db/model.php');
//include_once('village.php');
//include_once('player.php');
include_once('building.php');
//$village = Village::getVillage('12');
//$player = Player::getPlayer('423');
//$data = array('name' => 'peter','password' => 'nopasswd','email' => 'peter#hasnomail.org');
//$player->newPlayer($data);
//print_r($village->attr);
//print_r($player->playerObj);
//include('interface.phtml');
//var_dump($_SERVER);
//print_r($village);
//print_r($player);
echo '<br />';
var_dump(Building::getBuilding('321'));
Extract of the building.php (controller)
class Building{
private function __construct($id,$village = NULL)
{
$this->model = new Model(__CLASS__);
$model = $this->model;
$this->buildingObj = $model->getBuildingById($id);
}
public function getBuilding($id,$village = NULL)
{
return (new Building($id));
}
}
I found the problem:
I have to extend the Model class with the PDO adapter, I do not know why, but this was the problem.