W
webmized
Alfa
Programador
¡Usuario con pocos negocios! ¡Utiliza siempre saldo de Forobeta!
Estimados Lectores de ForoBeta,
Hace unos días fui citado en mi casa de estudios porque me "pillaron" burlando las reglas del negocio del sistema de la universidad, pero para cosas simples o mejor digamos tontas como por ejemplo inscribirme antes de la fecha que me toca o meter materias que aún no estan desbloqueadas para mi, pues mi ética no me falla tanto como para editarme las notas :devilish:, me gusta preservar la ética jejeje
Lo curioso, es que el incidente no me trajo ningún problema en mi casa de estudios (por los pelos), en su lugar me solicitaron dar una asesoría al personal del departamento de sistemas en la cual ayudara a corregir los problemas detectados, entre muchos de los problemas había un problema en paticular que se repetía por todas partes, y es muy común que lo codifiquen las mayoría de las personas que no les gusta trabajar con framework, no todos comenten el error claro.. pero muchos lo cometen…esta no es la primera vez que veo este tipo de problema en código de terceros.
Es una BUENA PRÁCTICA, que si NO trabajas con framework crees una clase que represente a ese formulario en PHP Orientado a Objetos, porque muchas veces solo reciben el $_POST y trabajan directamente sobre lo que les llega por $_POST si validar gran cosa en un código TOP-DOWN para nada orientado a objetos como si de PASCAL se tratara y eso está MAL, es una mala práctica que muchos aplican para salir del paso en formularios que representan o se asocian objetos que forman parte del modelo de negocio.
La validación de los formularios es muy importante, tanto del tipo de dato contenido en un campo como validez del dato dentro del modelo del negocio, veamos en calidad de ejemplo cómo podríamos en PHP sin Framework adoptar buena práctica muy sencilla en nuestro trabajo cotidiano con formularios de cualquier tipo, que nos facilite un poco la vida:
Primero, necesitariamos una “clase base” para todas las clases extendidas que hagamos para representar nuestros formularios.
Dicha clase base debería contener:
dado que todas las variables y nombres de clases o funciones suelo colocarlas en inglés, llamaremos a la clase BaseFormObject:
Ahora que tenemos nuestra clase base, necesitamos una clase que herede todo de la base y que implementemos en nuestro código a probar:
para poder probar necesitamos un formulario html, tened en cuenta que el nombre del formulario debe llevar el mismo nombre de la clase implementada, y además el nombre de cada campo debe tener el siguiente formato 'ExampleForm[field]', esto con la finalidad de que me llegue todo el POST relacionado al formulario dentro de un Array Key con el nombre 'ExampleForm' :
Al hacer POST de nuestro formulario el siguente codigo se encargaria de mostrar en pantalla lo que sucede con la validación y la aplicación de los filtros:
Cabe destacar que el código anterior es solo un ejemplo para que apartir de este, los programadores que NO usan Framework adopten buenas practicas en el manejo de formularios si aún no lo hacen, y si quieren realizar algún aporte al codigo presentado anteriormente, por favor enviarlo al siguiente repositorio:
https://github.com/webmized/php-snippets
Por último, quiero mencionar que me viene a la mente que incluso si necesitan enlazar un formulario a una base de datos puede agregar a la clase BaseFormObject un nuevo método que sirva para guardar el registro en la base de datos, por ejemplo:
Y para finalizar podrían crear un filtro que sirva para remover cualquier posible inyección SQL a la base de datos:
Y una vez tengan desarrollado el filtro despues de aplicar buen ingenio y creatividad, solo tendrían que agregarlo a los filtros aplicables :
Bueno, un cordial saludo a todos, esto es todo por el momento y hasta la próxima! 🙂
PD: El tema de la burla al sistema de la universidad, no fue en si una intrusión, fue una burla al modelo de negocio que este sistema supone debido a la ausencia de validación en sus formularios y es lo que quiero hacer ver con estos ejemplos de manejar los Formularios como objetos PHP, este ejemplo es extensible y pueden no solo validar tipos de datos incluso pueden agregar filtro y reglas mas complejas con esta METODOLOGIA.
Hace unos días fui citado en mi casa de estudios porque me "pillaron" burlando las reglas del negocio del sistema de la universidad, pero para cosas simples o mejor digamos tontas como por ejemplo inscribirme antes de la fecha que me toca o meter materias que aún no estan desbloqueadas para mi, pues mi ética no me falla tanto como para editarme las notas :devilish:, me gusta preservar la ética jejeje
Lo curioso, es que el incidente no me trajo ningún problema en mi casa de estudios (por los pelos), en su lugar me solicitaron dar una asesoría al personal del departamento de sistemas en la cual ayudara a corregir los problemas detectados, entre muchos de los problemas había un problema en paticular que se repetía por todas partes, y es muy común que lo codifiquen las mayoría de las personas que no les gusta trabajar con framework, no todos comenten el error claro.. pero muchos lo cometen…esta no es la primera vez que veo este tipo de problema en código de terceros.
Es una BUENA PRÁCTICA, que si NO trabajas con framework crees una clase que represente a ese formulario en PHP Orientado a Objetos, porque muchas veces solo reciben el $_POST y trabajan directamente sobre lo que les llega por $_POST si validar gran cosa en un código TOP-DOWN para nada orientado a objetos como si de PASCAL se tratara y eso está MAL, es una mala práctica que muchos aplican para salir del paso en formularios que representan o se asocian objetos que forman parte del modelo de negocio.
La validación de los formularios es muy importante, tanto del tipo de dato contenido en un campo como validez del dato dentro del modelo del negocio, veamos en calidad de ejemplo cómo podríamos en PHP sin Framework adoptar buena práctica muy sencilla en nuestro trabajo cotidiano con formularios de cualquier tipo, que nos facilite un poco la vida:
Primero, necesitariamos una “clase base” para todas las clases extendidas que hagamos para representar nuestros formularios.
Dicha clase base debería contener:
- Validaciones de tipo de dato Básicas
- Filtros Básicos
- Un método que cargue la información a sus respectivos atributos, y devuelva error si algún atributo no existe.
- Un método que se encargue de ejecutar todas las validaciones y aplicar todos los filtros, y que devuelva true si todo está OK, o false si algo anda mal.
- Un método que devuelva todos los mensajes de error
- Un método que devuelva mensajes de error de un atributo especifico, si este posee error.
dado que todas las variables y nombres de clases o funciones suelo colocarlas en inglés, llamaremos a la clase BaseFormObject:
PHP:
<?php
abstract class BaseFormObject {
/*
*
* Este código es solo un ejemplo de libre uso, modifiquelo cuanto quiera y uselo para lo que quiera.
*
* Información IMPORTANTE sobre las [REGLAS]:
* Los metodos que representen reglas de formulario deben ser espeficificados al momentos de añadir la regla
* de la siguiete forma $form->addRule(['attribute' => 'attribute1', 'rule' => 'miregla'])
* y tenga en cuenta que el codigo accede al medoto correspondiente a la regla anteponiendo el prefijo "rule",
* de la siguiente manera dentro del metodo validate $this->ruleMiregla con solo la primera letra del nombre de la
* regla en mayuscula.
*
* POR LO TANTO, si ud desea agregar una nueva regla siga el mismo patro para el nombre del método que
* la representa: protected function ruleMiregla($attribute){...} donde a través del parametro attribute
* llegara el nombre del atributo a validar.
*
* POR ULTIMO, si el metodo que usted cree para representar una regla, cumple la condicion de error, añada el
* mensaje de error de la siguiente manera:
*
* $this->$_formErrors[$this->{$attribute}] = $this->attributeLabels($attribute)." "."debe cumplir con X condición";
*
* donde en esa linea de codigo simplemente estamos cargando el mensaje de error en un array donde deben estar los
* errores, para luego mostrarlos al usuario en el formulario, el valor que se carga no es mas que el label definido
* para el atributo concatenado a un string complementario que permita entender la natturaleza el error.
*
* Información IMPORTANTE sobre los [FILTROS]:
*
* Los filtros aplican la misma metodología antes descrita para las REGLAS, solo que no se manejan mensajes de
* error, sustituye un valor dado de un atributo por el valor con el filtro ya aplicado.
*
* Es de notar que los filtros en el método validate() se ejecutan antes que la comprobación de las reglas.
*
*/
/*
* las reglas a validar se almacenarán durante la línea de ejecución en este array vacío por defecto de la siguiente
* manera:
* [['attribute' => 'attribute1', 'rule' => 'required'], ['attribute' => 'attribute1', 'rule' => 'integer']]
*
*/
protected $_formRules = [];
/*
* los filtros a aplicar se almacenarán durante la línea de ejecución en este array vacío por defecto de la
* siguiente manera:
* [['attribute' => 'attribute1', 'filter' => 'uppercase'], ['attribute' => 'attribute1', 'filter' => 'trim']]
*
*/
protected $_formFilters = [];
// array para almacenar las etiquetas de los atributos atributo => etiqueta
protected $_attributeLabels = [];
/*
* los errores encontrados al validar las reglas se almacenarán en este array vacío de la siguiete manera para luego
* ser mostrados al usuario:
*
* ['attribute1' => ['mensaje del error 1', 'mensaje del error N...'],
* 'attribute34' => ['mensaje del error 1', 'mensaje del error N...'],]
*
*/
protected $_formErrors = [];
//metodo para registrar errores presentes en un campo del formulario
protected function addError($attribute, $msg)
{
if (!isset($this->_formErrors[$attribute])) {
$this->_formErrors[$attribute] = [];
}
$this->_formErrors[$attribute][] = $msg;
}
public function getErrors($attribute = null){
if($attribute != null){
if (!property_exists($this,$attribute)) {
throw new Exception("El atributo {$attribute} no existe");
}
if(isset($this->_formErrors[$attribute])){
return $this->_formErrors[$attribute];
}else{
return [];
}
}else{
return $this->_formErrors;
}
}
public function attributeLabels($attribute)
{
// attribute => label
$attributeLabels = $this->_attributeLabels;
return ((isset($attributeLabels[$attribute]) ? $attributeLabels[$attribute] : null));
}
//devuelve un error si el tipo de dato no es entero
protected function ruleInteger($attribute)
{
if (is_numeric($this->{$attribute})) {
if(fmod($this->{$attribute}, 1) != 0){
$this->addError($attribute, $this->attributeLabels($attribute) . " " . "debe ser un número entero");
}
}else{
$this->addError($attribute, $this->attributeLabels($attribute) . " " . "debe ser un número entero");
}
}
//devuelve un error si el tipo de dato no es double
protected function ruleNumeric($attribute)
{
if (!is_numeric($this->{$attribute})) {
$this->addError($attribute,
$this->attributeLabels($attribute) . " " . "debe ser un número");
}
}
//devuelve un error de datos faltantes si la validación no se cumple
protected function ruleRequired($attribute)
{
if (empty($this->{$attribute})) {
$this->addError($attribute, $this->attributeLabels($attribute) . " " . "es requerido");
}
}
//asegura que lo que contenga el atributo no tenga espacio en blanco a los extremos
protected function filterTrim($attribute)
{
$this->{$attribute} = trim($this->{$attribute});
}
//asegura que lo que contenda el atributo se encuentre en mayuscula
protected function filterUppercase($attribute)
{
$this->{$attribute} = strtoupper($this->{$attribute});
}
//asegura que lo que contenga el atributo se encuentre en minuscula
protected function filterLowercase($attribute)
{
$this->{$attribute} = strtolower($this->{$attribute});
}
//metodo para agregar filtros
protected function addRule($rule)
{
if (!is_array($rule)) {
throw new Exception("Malformación de regla de formulario, una regla debe ser un array con la siguiente
forma ['attribute' => attributeName, 'rule' => ruleName]");
}
if (!array_key_exists('attribute', $rule) || !array_key_exists('rule', $rule)) {
throw new Exception("Malformación de regla de formulario, una regla representada en un array debe
tener obligatoriamente la llave attribute y la llave rule");
}
if (count($rule) < 2 || count($rule) > 2) {
throw new Exception("Malformación de regla de formulario, una regla puede tener solo
2 posiciones en el array");
}
return $this->_formRules[] = $rule;
}
//metodo para agregar filtros
protected function addFilter($filter)
{
if (!is_array($filter)) {
throw new Exception("Malformación de filtro de formulario, un filtro debe ser un array con la siguiente
forma ['attribute' => attributeName, 'filter' => filterName]");
}
if (!array_key_exists('attribute', $filter) || !array_key_exists('filter', $filter)) {
throw new Exception("Malformación de filtro de formulario, un filtro representado en un array debe tener
obligatoriamente la llave attribute y la llave filter");
}
if (count($filter) < 2 || count($filter) > 2) {
throw new Exception("Malformación de filtro de formulario, un filtro puede tener solo 2
posiciones en el array");
}
return $this->_formFilters[] = $filter;
}
//metodo que carga el POST a los atributos de la clase
public function load($array_attributes)
{
if (isset($array_attributes[get_class($this)])) {
if (is_array($array_attributes[get_class($this)])) {
foreach ($array_attributes[get_class($this)] as $attribute => $value) {
$attribute = trim($attribute);
if (property_exists($this,$attribute)) {
$this->{$attribute} = $value;
} else {
throw new Exception("el atributo {$attribute} no existe");
}
}
return true;
}
} else {
throw new Exception("No fue recibido ningún dato");
}
return false;
}
//aqui definiriamos las reglas en las clases que hereden de esta clase
protected function rules(){
}
//aqui definiriamos los filtros en las clases que hereden de esta clase
protected function filters(){
}
//este metodo ejecuta todas las validaciones y todos los filtros
public function validate()
{
$this->rules();
$this->filters();
//aplicar todos los filtros
foreach ($this->_formFilters as $filterToApply) {
$attributeToEval = $filterToApply['attribute'];
if (!property_exists($this,$attributeToEval)) {
throw new Exception("El atributo {$attributeToEval} no existe");
}
$methodTobeUsed = "filter" . ucfirst($filterToApply['filter']);
if (!method_exists($this, $methodTobeUsed)) {
throw new Exception("El método {$methodTobeUsed} no existe, no se puede filtrar
el atributo {$attributeToEval}");
}
$this->{$methodTobeUsed}($attributeToEval);
}
//validar todas las reglas
foreach ($this->_formRules as $ruleToEval) {
$attributeToEval = $ruleToEval['attribute'];
if (!property_exists($this,$attributeToEval)) {
throw new Exception("El atributo {$attributeToEval} no existe");
}
$methodTobeUsed = "rule" . ucfirst($ruleToEval['rule']);
if (!method_exists($this, $methodTobeUsed)) {
throw new Exception("El método {$methodTobeUsed} no existe, no se puede validar
el atributo {$attributeToEval}");
}
$this->{$methodTobeUsed}($attributeToEval);
}
//si el atributo _formErrors tiene valores, entonces el formulario no es valido.
if(count($this->_formErrors) > 0 ){
return false;
}
return true;
}
}
Ahora que tenemos nuestra clase base, necesitamos una clase que herede todo de la base y que implementemos en nuestro código a probar:
PHP:
<?php
include 'BaseFormObject.php';
class ExampleForm extends BaseFormObject
{
public $field1;
public $field2;
public $field3;
public $field4;
public $field5;
//definimos los labels
protected $_attributeLabels = [
'field1' => 'Campo 1',
'field2' => 'Campo 2',
'field3' => 'Campo 3',
'field4' => 'Campo 4',
'field5' => 'Campo 5',
];
//agregamos las reglas deseadas
protected function rules(){
$this->addRule(['attribute' => 'field1', 'rule' =>'integer']);
$this->addRule(['attribute' => 'field4', 'rule' =>'numeric']);
$this->addRule(['attribute' => 'field5', 'rule' =>'required']);
}
//agregamos los filtros deseados
protected function filters(){
$this->addFilter(['attribute' => 'field5', 'filter' =>'uppercase']);
$this->addFilter(['attribute' => 'field3', 'filter' =>'trim']);
}
}
para poder probar necesitamos un formulario html, tened en cuenta que el nombre del formulario debe llevar el mismo nombre de la clase implementada, y además el nombre de cada campo debe tener el siguiente formato 'ExampleForm[field]', esto con la finalidad de que me llegue todo el POST relacionado al formulario dentro de un Array Key con el nombre 'ExampleForm' :
HTML:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Tratar formularios como objetos en php</title>
</head>
<body>
<form name="ExampleForm" method="post" action="example_results.php">
<input type="text" name="ExampleForm[field1]"> <br>
<input type="text" name="ExampleForm[field2]"> <br>
<input type="text" name="ExampleForm[field3]"> <br>
<input type="text" name="ExampleForm[field4]"> <br>
<input type="text" name="ExampleForm[field5]"> <br>
<button type="submit">Enviar</button>
</form>
</body>
</html>
Al hacer POST de nuestro formulario el siguente codigo se encargaria de mostrar en pantalla lo que sucede con la validación y la aplicación de los filtros:
PHP:
<?php
include 'ExampleForm.php';
if(!isset($_POST)){
throw new Exception("No fue recibido ningún POST");
}else{
header('Content-Type: text/html; charset=utf-8');
$formObject = new ExampleForm();
$formObject->load($_POST);
echo "Validar formulario <br>";
var_dump($formObject->validate());
echo "Errores encontrados <br>";
var_dump($formObject->getErrors());
echo "Field1: {$formObject->field1} <br>";
echo "Field2: {$formObject->field2} <br>";
echo "Field3: {$formObject->field3} <br>";
echo "Field4: {$formObject->field4} <br>";
echo "Field5: {$formObject->field5} <br>";
}
Cabe destacar que el código anterior es solo un ejemplo para que apartir de este, los programadores que NO usan Framework adopten buenas practicas en el manejo de formularios si aún no lo hacen, y si quieren realizar algún aporte al codigo presentado anteriormente, por favor enviarlo al siguiente repositorio:
https://github.com/webmized/php-snippets
Por último, quiero mencionar que me viene a la mente que incluso si necesitan enlazar un formulario a una base de datos puede agregar a la clase BaseFormObject un nuevo método que sirva para guardar el registro en la base de datos, por ejemplo:
PHP:
public function save()
{
$queryFields = [];
$queryValues = [];
$publicAttributes = [];
$reflect = new ReflectionClass($this);
$reflectProps = $reflect->getProperties(ReflectionProperty::IS_PUBLIC);
foreach($reflectProps as $reflectProp){
$props[] = $reflectProp->name;
}
foreach($publicAttributes as $publicAttribute){
$queryFields[] = $publicAttribute;
$queryValues[] = "'".$this->{$publicAttribute}."'";
}
$query = "INSERT INTO table_name (".implode(',', $queryFields).")
VALUES (".implode(',', $queryValues).")";
}
Y para finalizar podrían crear un filtro que sirva para remover cualquier posible inyección SQL a la base de datos:
PHP:
abstract class BaseFormObject {
protected function filterRemovesql($attribute){
......
}
}
Y una vez tengan desarrollado el filtro despues de aplicar buen ingenio y creatividad, solo tendrían que agregarlo a los filtros aplicables :
PHP:
class ExampleForm extends BaseFormObject {
...
protected function filters(){
$this->addFilter(['attribute' => 'field5', 'filter' =>'uppercase']);
$this->addFilter(['attribute' => 'field3', 'filter' =>'trim']);
$this->addFilter(['attribute' => 'field1', 'filter' =>'removesql']);
$this->addFilter(['attribute' => 'field2', 'filter' =>'removesql']);
$this->addFilter(['attribute' => 'field3', 'filter' =>'removesql']);
$this->addFilter(['attribute' => 'field4', 'filter' =>'removesql']);
$this->addFilter(['attribute' => 'field5', 'filter' =>'removesql']);
}
...
}
Bueno, un cordial saludo a todos, esto es todo por el momento y hasta la próxima! 🙂
PD: El tema de la burla al sistema de la universidad, no fue en si una intrusión, fue una burla al modelo de negocio que este sistema supone debido a la ausencia de validación en sus formularios y es lo que quiero hacer ver con estos ejemplos de manejar los Formularios como objetos PHP, este ejemplo es extensible y pueden no solo validar tipos de datos incluso pueden agregar filtro y reglas mas complejas con esta METODOLOGIA.
Última edición: