Compare commits

...
12 Commits
7 changed files with 720 additions and 145 deletions
+152
View File
@@ -0,0 +1,152 @@
# Infor-Request-Response
PHP library for HTTP request/response abstraction. Provides clean, static-style APIs for reading request parameters, building responses, and managing session flash messages.
**Package:** `inforsistemas/rr`
**Namespace:** `RR`
**Requires:** PHP 8.1+
---
## Installation
```bash
composer require inforsistemas/rr
```
---
## Classes
### `Request` — Reading input parameters
Fully static class. POST always takes precedence over GET.
```php
use RR\Request;
use RR\ParamType;
// Required param — throws Exception if missing
$id = Request::requiredParam('id', ParamType::INT);
// Optional param — returns default if missing
$search = Request::optionalParam('q', ParamType::ALPHA, '');
// Required array param — throws Exception if missing
$items = Request::requiredParamArray('items');
// Detect AJAX/fetch/API client requests
if (Request::detectAjaxRequest()) {
// respond with JSON
}
```
**`ParamType` enum values:** `INT`, `DOUBLE`, `ALPHA`, `ALPHANUM`
---
### `Response` — Building and sending responses
Singleton accessed via `Response::getInstance()`. All builder methods are chainable and static.
```php
use RR\Response;
// Send a plain HTML response
Response::body('<h1>Hello</h1>')->send();
// Append/prepend to the body buffer
Response::bodyPrepend('<header>...</header>');
Response::bodyAppend('<footer>...</footer>');
Response::send();
// Set a custom header
Response::header('X-Custom', 'value')->send();
// Send a JSON response
Response::json(['status' => 'ok', 'data' => $result])->send();
// JSONP response
Response::json($data, 'myCallback')->send();
// Redirect (303 by default)
// Automatically returns JSON {"location": "..."} for AJAX requests
Response::redirect('/dashboard');
Response::redirect('/login', 302);
// Redirect back to the previous page (HTTP_REFERER)
Response::back();
// Terminate execution after sending
Response::send()->done();
```
---
### `Flash` — Session-based flash messages
Singleton accessed via `Flash::getInstance()`. Messages are written to the session and available on the **next** request.
```php
use RR\Flash;
use RR\MessageType;
// Add messages (shorthand methods)
Flash::addInfo('Profile updated.');
Flash::addSuccess('Order placed successfully!');
Flash::addWarning('Your session will expire soon.');
Flash::addError('Invalid credentials.');
// Add a message with an explicit type
Flash::addMessage('Something happened.', MessageType::INFO);
// Read messages on the next request
if (Flash::hasMessageType(MessageType::ERROR)) {
$errors = Flash::getError();
}
$infos = Flash::getInfos();
$successes = Flash::getSuccess();
$warnings = Flash::getWarning();
$errors = Flash::getError();
// Get all messages of any type
$messages = Flash::getMessages(MessageType::SUCCESS);
```
**`MessageType` enum values:** `INFO`, `SUCCESS`, `WARNING`, `ERROR`
---
## Typical usage pattern
```php
use RR\Request;
use RR\Response;
use RR\Flash;
use RR\ParamType;
// Controller action
function store(): void
{
try {
$name = Request::requiredParam('name', ParamType::ALPHA);
$price = Request::requiredParam('price', ParamType::DOUBLE);
// ... save to DB ...
Flash::addSuccess('Item created successfully.');
Response::redirect('/items');
} catch (\Exception $e) {
Flash::addError('Missing required fields.');
Response::back();
}
}
```
---
## License
See [composer.json](composer.json) for author information.
+1 -1
View File
@@ -1,5 +1,5 @@
{ {
"name": "urfat/rr", "name": "inforsistemas/rr",
"description": "Requests and responses", "description": "Requests and responses",
"type": "library", "type": "library",
"authors": [ "authors": [
+183 -62
View File
@@ -1,51 +1,47 @@
<?php <?php
namespace RR; namespace RR;
/**
* Class to handle flash messages.
* A Flash message is a message registered in this request and
* it's shown in the next request
*/
class Flash{ class Flash{
private static $_instance; private static $_instance;
// Message types and shortcuts const FLASH_SESSION_NAME = 'flashes';
const INFO = 'i';
const SUCCESS = 's';
const WARNING = 'w';
const ERROR = 'e';
const SESSION_NAME = 'flashes'; public $flashes = Array();
public $nextFlashes = Array();
const defaultType = self::INFO;
protected $msgTypes = [
self::ERROR => 'error',
self::WARNING => 'warning',
self::SUCCESS => 'success',
self::INFO => 'info',
];
public $old_messages = Array();
public $new_messages = Array();
public $_flashes = Array();
//SINGLETON============================================== //SINGLETON==============================================
private function __construct(){ private function __construct(): void
{
if(session_id() == '' || !isset($_SESSION)) { if(session_id() == '' || !isset($_SESSION)) {
session_start(); session_start();
} }
if( isset($_SESSION['flashes']) ){
$this->old_messages = $_SESSION['flashes']; if( isset($_SESSION[self::FLASH_SESSION_NAME]) ){
$this->flashes = $_SESSION[self::FLASH_SESSION_NAME];
} }
unset( $_SESSION[self::SESSION_NAME] );
// The messages are in memory. So clear the $_SESSION
$_SESSION[self::FLASH_SESSION_NAME] = Array();
} }
private static function newObj(){ private static function newObj(): Flash
{
if (!isset( self::$_instance )) { if (!isset( self::$_instance )) {
self::$_instance = new Flash(); self::$_instance = new Flash();
} }
return self::$_instance; return self::$_instance;
} }
public static function getInstance(){ public static function getInstance(): Flash
{
if (!isset(self::$_instance)) { if (!isset(self::$_instance)) {
return self::newObj(); return self::newObj();
} }
@@ -54,64 +50,189 @@ class Flash{
//======================================================= //=======================================================
public function merge(){ /**
$this->_flashes = array_merge_recursive($this->new_messages, $this->old_messages); * Load the stored messages for this session to the $_SESSION varible
} * to be used on the next request
*
public function load_to_session(){ * @return Flash
$_SESSION[self::SESSION_NAME] = $this->new_messages; */
} private function loadToSession(): Flash
{
public static function add_message($type = self::defaultType, $message){
$instance = self::getInstance(); $instance = self::getInstance();
$instance->new_messages[$type] = $message; $_SESSION[self::FLASH_SESSION_NAME] = array_merge_recursive($_SESSION[self::FLASH_SESSION_NAME], $instance->nextFlashes);
$instance->load_to_session(); $instance->nextFlashes = Array();
return $this;
} }
public static function add_info($message){ /**
* Add a message to the session with the INFO type
*
* @param string $message
*
* @return Flash
*/
public static function addInfo(string $message): Flash
{
$instance = self::getInstance(); $instance = self::getInstance();
$instance->new_messages[self::INFO] = $message; $instance->nextFlashes[MessageType::INFO->value][] = $message;
$instance->load_to_session(); $instance->loadToSession();
return $instance;
} }
public static function add_error($message){ /**
* Add a message from a specific type on the session
*
* @param MessageType $type
* @param string $message
*
* @return Flash
*/
public static function addMessage(string $message, MessageType $type = MessageType::INFO): Flash
{
$instance = self::getInstance(); $instance = self::getInstance();
$instance->new_messages[self::ERROR] = $message; $instance->nextFlashes[$type->value][] = $message;
$instance->load_to_session(); $instance->loadToSession();
return $instance;
} }
public static function get_errors(){ /**
if(!self::has_errors()){ * Add a message to the session with the SUCCESS type
*
* @param string $message
*
* @return Flash
*/
public static function addSuccess(string $message): Flash
{
$instance = self::getInstance();
$instance->nextFlashes[MessageType::SUCCESS->value][] = $message;
$instance->loadToSession();
return $instance;
}
/**
* Add a message to the session with the WARNING type
*
* @param string $message
*
* @return Flash
*/
public static function addWarning(string $message): Flash
{
$instance = self::getInstance();
$instance->nextFlashes[MessageType::WARNING->value][] = $message;
$instance->loadToSession();
return $instance;
}
/**
* Add a message to the session with the ERROR type
*
* @param string $message
*
* @return Flash
*/
public static function addError(string $message): Flash
{
$instance = self::getInstance();
$instance->nextFlashes[MessageType::ERROR->value][] = $message;
$instance->loadToSession();
return $instance;
}
/**
* Check if there is messages from a type on the collection
*
* @param MessageType $type
*
* @return bool
*/
public static function hasMessageType(MessageType $type = MessageType::INFO): bool
{
$instance = self::getInstance();
if(array_key_exists($type->value, $instance->flashes)){
return true;
}
return false;
}
/**
* Return all messages from a type
*
* @param MessageType $type
*/
public static function getMessages(MessageType $type = MessageType::INFO): Array
{
if(!self::hasMessageType($type)){
return Array(); return Array();
} }
return self::getInstance()->_flashes[self::ERROR];
return self::getInstance()->flashes[$type->value];
} }
public static function get_infos(){ /**
if(!self::has_info()){ * Return all info messages registered on the collection
*
* @return Array
*/
public static function getInfos(): Array
{
if(!self::hasMessageType(MessageType::INFO)){
return Array(); return Array();
} }
return self::getInstance()->_flashes[self::INFO];
return self::getInstance()->flashes[MessageType::INFO->value];
} }
public static function has_errors(){ /**
$instance = self::getInstance(); * Return all success messages registered on the collection
$instance->merge(); *
if( array_key_exists(self::ERROR, $instance->_flashes ) ){ * @return Array
return true; */
}else{ public static function getSuccess(): Array
return false; {
if(!self::hasMessageType(MessageType::SUCCESS)){
return Array();
} }
return self::getInstance()->flashes[MessageType::SUCCESS->value];
} }
public static function has_info(){ /**
$instance = self::getInstance(); * Return all warning messages registered on the collection
$instance->merge(); *
if( array_key_exists(self::INFO, $instance->_flashes ) ){ * @return Array
return true; */
}else{ public static function getWarning(): Array
return false; {
if(!self::hasMessageType(MessageType::WARNING)){
return Array();
} }
return self::getInstance()->flashes[MessageType::WARNING->value];
}
/**
* Return all error messages registered on the collection
*
* @return Array
*/
public static function getError(): Array
{
if(!self::hasMessageType(MessageType::ERROR)){
return Array();
}
return self::getInstance()->flashes[MessageType::ERROR->value];
} }
} }
+19
View File
@@ -0,0 +1,19 @@
<?php
namespace RR;
enum MessageType: string
{
case INFO = 'info';
case SUCCESS = 'success';
case WARNING = 'warning';
case ERROR = 'error';
}
enum MessageTypeShort: string
{
case INFO = 'i';
case SUCCESS = 's';
case WARNING = 'w';
case ERROR = 'e';
}
+11
View File
@@ -0,0 +1,11 @@
<?php
namespace RR;
enum ParamType
{
case INT; // integer
case DOUBLE; // double
case ALPHA; // alpha
case ALPHANUM; // alphanum
}
+197 -37
View File
@@ -2,16 +2,92 @@
namespace RR; namespace RR;
class Request{ use RR\ParamType;
const PARAM_ALPHA = 'alpha'; /**
const PARAM_ALPHANUM = 'alphanum'; * Class to abstract the input (GET PARAMS, POST, etc...) value grabbing
* POST always have precedence
*/
class Request
{
const PARAM_INT = 'integer'; /**
const PARAM_DOUBLE = 'double'; * Search a param on the GET/POST array
* If the key is not found we throw a `required_param_not_found` Exception
*
* @param strig $paramname
* @param ParamType $type
* @param Array $options
*
* @return mixed
* @throws \Exception
*/
public static function requiredParam(
string $parname,
ParamType $type = ParamType::INT,
$options = Array()
): mixed {
if (isset($_POST[$parname])) {
$param = $_POST[$parname];
} else if (isset($_GET[$parname])) {
$param = $_GET[$parname];
} else {
throw new \Exception('required_param_not_found');
}
/*$param = self::filter($type, $param);
public static function requiredParam($parname, $type, $options = Array(), $default = ''){ foreach ($options as $key => $option){
// POST has precedence. self::$key($param, $option);
}*/
return $param;
}
/**
* Search a array param on the GET/POST array
* If the key is not found, we throw a `required_param_not_found` Exception
*
* @param strig $paramname
* @param ParamType $type
* @param Array $options
*
* @return mixed
* @throws \Exception
*/
public static function requiredParamArray(
string $parname,
$options = Array()
): mixed {
if (isset($_POST[$parname])) {
$param = $_POST[$parname];
} else if (isset($_GET[$parname])) {
$param = $_GET[$parname];
} else {
throw new \Exception('required_param_not_found');
}
return $param;
}
/**
* Search a array param on the GET/POST array
* If the key is not found we return the default value
*
* If the default $value type is not the same of the $type
* Wwe throw a `default_value_type_missmatch` Exception
*
* @param strig $paramname
* @param ParamType $type
* @param mixed $default
* @param Array $options
*
* @return mixed
* @throws \Exception
*/
public static function optionalParam(
string $parname,
ParamType $type = ParamType::INT,
mixed $default = "",
$options = Array(),
): mixed {
if (isset($_POST[$parname])) { if (isset($_POST[$parname])) {
$param = $_POST[$parname]; $param = $_POST[$parname];
} else if (isset($_GET[$parname])) { } else if (isset($_GET[$parname])) {
@@ -19,53 +95,137 @@ class Request{
} else { } else {
return $default; return $default;
} }
return $param;
}
$param = self::filter($type, $param); /**
* NOT IMPLEMENTED YET
* Search a array param on the GET/POST array
*
* @param strig $paramname
* @param Array $default
* @param Array $options
*
* @todo Implement
*
* @return Array
*/
public static function optionalParamArray(
string $parname,
Array $default = Array(),
$options = Array()
): Array{
// Not implemented yet
return $default;
}
foreach ($options as $key => $option){ /**
self::$key($param, $option); * Try to detect if the request came from AJAX/FETCH/POSTMAN
* in this cases, we might want to return something else
*
* @return bool
*/
public static function detectAjaxRequest(): bool
{
if (
isset($_SERVER['HTTP_X_REQUESTED_WITH']) &&
strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest'
) {
return true;
} }
return $param;
if (
isset($_SERVER['CONTENT_TYPE']) &&
str_contains(
strtolower($_SERVER['CONTENT_TYPE']),
'application/json'
)
) {
return true;
}
// Detecta ferramentas conhecidas
$userAgent = strtolower($_SERVER['HTTP_USER_AGENT'] ?? '');
if (
str_contains($userAgent, 'postman') ||
str_contains($userAgent, 'curl') ||
str_contains($userAgent, 'insomnia') ||
str_contains($userAgent, 'python') ||
str_contains($userAgent, 'java')
) {
return true;
}
// Headers comuns de navegadores modernos
if (
isset($_SERVER['HTTP_SEC_FETCH_SITE']) ||
isset($_SERVER['HTTP_SEC_FETCH_MODE']) ||
isset($_SERVER['HTTP_REFERER'])
) {
return false;
}
return false;
} }
public static function requiredParamArray($parname, $type, $default = '', $options = Array() ){ /**
*
} * @param string $paramname
* @param ParamType $type
public static function optionalParam(){ * @param bool $cast
*
} * @todo Implement casting
* @todo Review this logic, I don'r remember what it does
public static function optionalParamArray(){ *
* @return mixed
} private static function filter(
$paramname = "",
private static function filter($type, $paramname){ ParamType $type = ParamType::INT,
$cast = true
): mixed {
switch ($type){ switch ($type){
case 'integer': case ParamType::INT:
if(is_integer($paramname)){ if(is_integer($paramname)){
return $paramname; return $paramname;
} }
break; break;
case 'double': case ParamType::DOUBLE:
if(is_double($paramname)){ if(is_double($paramname)){
return $paramname; return $paramname;
} }
break; break;
case 'alpha': case ParamType::ALPHA:
return $paramname;
break;
case ParamType::ALPHANUM:
return $paramname;
break;
} }
} }
*/
/* /**
* Validations * NOT IMPLEMENTED YET
* Check if the numeric value is in a number range
*
* Examples:
* From 10 to 35 = inRange($value, "10-35")
* From 45 to infinity = inRange($value, "45-")
* All numbers below 0 = inRange($value, "-0")
*
* @todo Not Implemented yet
*
* @param int|float $value
* @param string $range
*
* @return bool
*/ */
private static function minVal(){ private static function inRange(
int|float $value,
string $range
): bool {
return true;
} }
private static function maxVal(){ }
}
}
+157 -45
View File
@@ -1,42 +1,35 @@
<?php <?php
namespace RR; namespace RR;
/* /**
* * Class to handle the return the content/buffer and properties of a request
*
*/ */
class Response{ class Response{
private static $_instance; private static $_instance;
/*
*
*/
public $_headers = Array(); public $_headers = Array();
/* public $_body = "";
*
*/
public $_body = Array();
/*
*
*/
public $_code = 200; public $_code = 200;
//SINGLETON============================================== //SINGLETON==============================================
private function __construct(){ private function __construct()
{
} }
private static function newObj(){ private static function newObj() : Response
{
if (!isset( self::$_instance )) { if (!isset( self::$_instance )) {
self::$_instance = new Response(); self::$_instance = new Response();
} }
return self::$_instance; return self::$_instance;
} }
public function getInstance(){ public static function getInstance() : Response
{
if (!isset(self::$_instance)) { if (!isset(self::$_instance)) {
return self::newObj(); return self::newObj();
} }
@@ -45,13 +38,30 @@ class Response{
//======================================================= //=======================================================
public static function header($key, $value){ /**
$istance = self::getInstance(); * Add a parameter to what would be sent on the PHP header response
$istance->_headers[$key] = $value; *
return $istance; * @param string $key
* @param string $value
*
* @return Response
*/
public static function header(string $key, string $value): Response
{
$instance = self::getInstance();
$instance->_headers[$key] = $value;
return $instance;
} }
public static function body_prepend($body = null){ /**
* Add content before the current content buffer
*
* @param string|null $body
*
* @return Response
*/
public static function bodyPrepend(string|null $body = null): Response
{
$instance = self::getInstance(); $instance = self::getInstance();
if (null !== $body) { if (null !== $body) {
$instance->_body = (string) $body . $instance->_body; $instance->_body = (string) $body . $instance->_body;
@@ -60,7 +70,15 @@ class Response{
return $instance; return $instance;
} }
public static function body($body = null){ /**
* Set or replace the body content buffer
*
* @param string|null $body
*
* @return Response
*/
public static function body(string|null $body = ""): Response
{
$instance = self::getInstance(); $instance = self::getInstance();
if (null !== $body) { if (null !== $body) {
$instance->_body = (string) $body; $instance->_body = (string) $body;
@@ -69,7 +87,15 @@ class Response{
return $instance; return $instance;
} }
public static function body_append($body = null){ /**
* Add content on the end of the body buffer
*
* @param string|null $body
*
* @return Response
*/
public static function bodyAppend(string|null $body = null): Response
{
$instance = self::getInstance(); $instance = self::getInstance();
if (null !== $body) { if (null !== $body) {
$instance->_body .= (string) $body; $instance->_body .= (string) $body;
@@ -78,61 +104,147 @@ class Response{
return $instance; return $instance;
} }
private function send_headers(){ /**
* Send the header properties to the browser
*
* @return Response
*/
private function sendHeaders(): Response
{
foreach ($this->_headers as $key => $value) { foreach ($this->_headers as $key => $value) {
header($key .': '. $value, false); header($key .': '. $value, true);
} }
return $this; return $this;
} }
private function send_body(){ /**
echo (string) $this->_body; * Print the body buffer to the browser
*
* @return Response
*/
private function sendBody(): Response
{
if(!empty($this->_body)){
echo (string) $this->_body;
}
return $this; return $this;
} }
public static function send(){ /**
$instance = self::getInstance(); * Formats and print a json object/array with the adequate headers
$instance->send_headers(); *
$instance->send_body(); * @param mixed $object
return $instance; * @param string|null $jsonPrefix
} *
* @return Response
public static function json($object, $jsonp_prefix = null){ */
public static function json(mixed $object = Array(), string|null $jsonPrefix = null): Response
{
$instance = self::getInstance(); $instance = self::getInstance();
$instance->body(''); $instance->body('');
$json = json_encode($object); $json = $object;
if (null !== $jsonp_prefix) { if(!is_string($object)){
$json = json_encode($object);
}
if (null !== $jsonPrefix) {
$instance->header('Content-Type', 'text/javascript'); $instance->header('Content-Type', 'text/javascript');
$instance->body("$jsonp_prefix($json);"); $instance->body("$jsonPrefix($json);");
} else { } else {
$instance->header('Content-Type', 'application/json'); $instance->header('Content-Type', 'application/json');
$instance->body($json); $instance->body($json);
} }
return $instance; return $instance;
} }
public static function redirect($url, $code = 302){ /**
* Redirect the request to another URL
*
* @param string $url
* @param int $code
*/
public static function redirect(string $url, int $code = 303): void
{
$instance = self::getInstance(); $instance = self::getInstance();
$instance->_code = $code; $instance->_code = $code;
$instance->header('Location', $url);
$instance->body(''); if(Request::detectAjaxRequest()){
$instance->send(); $instance->json(['location' => $url])->send();
die;
}
## TODO - Try to use the header location with the class dispatch method
header("Location: $url");
die;
//$instance->header('Location: ', $url)->send();
//$instance->send()->done();
} }
public static function back(){ /**
* Try to return to the previos page (HTTP_REFERER)
*
* I don't think is working, and might not make sense, return to a post endpoint with GET?
*
* @param int $code
*
* @return void
*/
public static function back(int $code = 303): void
{
$instance = self::getInstance(); $instance = self::getInstance();
$instance->header('Location: ', $_SERVER['HTTP_REFERER']); $instance->_code = $code;
if(isset($_SERVER['HTTP_REFERER'])){
$instance->header('Location', $_SERVER['HTTP_REFERER']);
} else {
$instance->header(
'Location',
(isset($_SERVER['HTTPS']) && $_SERVER['HTTPS']
=== 'on' ? "https" : "http") .
"://" . $_SERVER['HTTP_HOST'] .
$_SERVER['REQUEST_URI']
);
}
$instance->body(''); $instance->body('');
$instance->send(); $instance->send();
die;
} }
function done(){ /**
* Send all the info stored on the object
* - Send headers
* - Send body
* - Set response code
*
* @return Response
*/
public static function send(): Response
{
$instance = self::getInstance();
http_response_code($instance->_code);
$instance->sendHeaders();
$instance->sendBody();
return $instance;
}
/**
* Kill the current execution.
*
* After all the content is sent, there is no sense on keep the process
*
* @return void
*/
public function done(): void
{
die; die;
} }
} }