|
|
| (No se muestran 54 ediciones intermedias del mismo usuario) |
| Línea 1: |
Línea 1: |
| | == Introdución == | | == Introdución == |
| | | | |
| | + | '''<u>IMPORTANTE – API</u> empregada no módulo''' |
| | + | |
| | + | : Nestes apuntes explícanse diferentes formas de realizar operacións contra unha base de datos MySQL desde PHP, empregando principalmente **mysqli** e **PDO (PHP Data Objects)**. |
| | + | : As explicacións e exemplos realizados con **mysqli**, tanto na súa versión procedemental como orientada a obxectos, mantéñense nos apuntes con carácter **informativo e comparativo**, para coñecer as diferentes posibilidades que ofrece PHP para acceder a MySQL. |
| | + | |
| | + | : Non obstante, a partir deste tema a forma de acceso a bases de datos que se empregará como referencia no módulo será **PDO**, utilizando o controlador **PDO_MySQL**. |
| | + | : Polo tanto, **nos exercicios, prácticas e exames nos que se solicite realizar operacións contra unha base de datos desde PHP, deberá empregarse PDO**, salvo que no enunciado se indique expresamente o contrario. |
| | + | : Recoméndase, polo tanto, prestar especial atención aos exemplos identificados como **“Empregando PDO”**, xa que serán os que se tomarán como referencia para a avaliación. |
| | + | |
| | + | |
| | + | |
| | + | * Estes apuntes están actualizados para PHP 8.5. PDO traballa por defecto en modo de excepcións, polo que os erros producidos nas operacións contra a base de datos poden xerar unha PDOException, que poderá ser xestionada mediante try-catch. |
| | + | |
| | + | |
| | + | <br /> |
| | * Como comentamos no [http://wiki.cifprodolfoucha.es/index.php?title=PHP_Introduci%C3%B3n_acceso_a_base_de_datos punto anterior], para conectarnos a un xestor de base de datos específico imos ter que ter instalados os drivers correspondentes. | | * Como comentamos no [http://wiki.cifprodolfoucha.es/index.php?title=PHP_Introduci%C3%B3n_acceso_a_base_de_datos punto anterior], para conectarnos a un xestor de base de datos específico imos ter que ter instalados os drivers correspondentes. |
| | | | |
| Línea 19: |
Línea 34: |
| | * Existen tres APIs principais para conectar a un servidor de bases de datos MySQL: | | * Existen tres APIs principais para conectar a un servidor de bases de datos MySQL: |
| | | | |
| − | :* Extensión MySQL de PHP | + | :* Extensión MySQL de PHP (eliminada a partires da versión PHP 7.0) |
| | :* Extensión mysqli de PHP | | :* Extensión mysqli de PHP |
| − | :* Obxectos de Datos de PHP (PDO) | + | :* Obxectos de Datos de PHP (PDO) => '''A QUE IMOS EMPREGAR''' |
| − | | |
| | | | |
| − | * Para conectarnos imos utilizar as funcións mysql'''i''' (mysql improved) que foron introducidas a partires de Mysql 4.1
| |
| − | : A extensión mysqli ven <u>incluída nas versiones PHP 5 e posteriores</u>.
| |
| | | | |
| | + | * A extensión mysqli ven <u>incluída nas versiones PHP 5 e posteriores</u>. |
| | : Algunha das melloras de dita extensión con respecto á extensión mysql: | | : Algunha das melloras de dita extensión con respecto á extensión mysql: |
| | | | |
| Línea 33: |
Línea 46: |
| | :* Soporte para [http://php.net/manual/es/pdo.transactions.php Transaccións] | | :* Soporte para [http://php.net/manual/es/pdo.transactions.php Transaccións] |
| | | | |
| | + | == Activar a extensión mysqli e pdo== |
| | | | |
| − | : Dita extensión tamén ten unha versión procedural, de tal forma que imos poder chamar ás funcións de dúas maneiras diferentes:
| + | * Se instalamos XAMPP ou calquera das versións para outros S.O. normalmente a extensión xa ven habilitada por defecto. |
| − | | + | : Se temos unha instalación personalizada no que instalamos separadamente todo, teremos que habilitala. |
| − | Exemplo de función cun estilo procedimental que pecha unha conexión a unha base de datos ($link é un identificador cando se fai a conexión contra a base de datos):
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | mysqli_close($link);
| |
| − | </syntaxhighlight>
| |
| − | | |
| − | Exemplo de función cun estilo obxectual que pecha unha conexión a unha base de datos:
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | mysqli::close();
| |
| − | </syntaxhighlight>
| |
| − | | |
| − | | |
| − | * Aquí tedes un cadro comparativo obtido de php.net onde se ve o comentado anteriormente: | |
| − | | |
| − | [[Imagen:Php_BD_2.jpg|600px|center]]
| |
| − | | |
| − | | |
| − | | |
| − | <u>Nota:</u> Máis información [http://php.net/manual/es/mysqli.overview.php neste enlace].
| |
| − | | |
| − | | |
| − | == Activar a extensión mysqli==
| |
| | | | |
| − | * Se instalamos XAMPP ou calquera das versións para outros S.O. normalmente a extensión se ven habilitada por defecto.
| |
| − | : Se temos unha instalación personalizada no que instalamos separadamente todo, teremos que habilitala.
| |
| | | | |
| | + | : Xa vimos anteriormente que PDO permite conexión a múltiples bases de datos, pero tamén engade unha capa más para a conexión de base de datos, aínda que hoxe en día, a diferenza de rendemento é mínima. |
| | | | |
| | <br /> | | <br /> |
| Línea 90: |
Línea 82: |
| | extension_dir = "ext" | | extension_dir = "ext" |
| | </syntaxhighlight> | | </syntaxhighlight> |
| | + | :: O que fai dita liña é informar a PHP que busque as extensións no cartafol relativo ext, é dicir, nun cartafol chamado ext dentro do cartafol onde se atopa o executable de php. |
| | | | |
| | | | |
| Línea 241: |
Línea 234: |
| | <br /> | | <br /> |
| | | | |
| − | == Visualización de errores == | + | == Visualización de erros == |
| | + | |
| | + | * Ao principio é habitual cometer erros durante as probas de programación. |
| | + | : Ata que aprendamos a utilizar un depurador (debugger), cando se produce un erro en PHP pode aparecer unha páxina en branco, mostrarse só unha parte da páxina ou aparecer unha mensaxe de erro que indique o problema. |
| | | | |
| − | * Al principio, vais a cometer muchos errores durante la prueba de programas. | + | * Durante o desenvolvemento é recomendable configurar PHP para que mostre os erros. Isto pode facerse modificando as directivas '''display_errors''' e '''error_reporting''' do ficheiro ''php.ini''. |
| − | : PHP no va a mostrar nada y simplemente se va a quedar la página en blanco.
| |
| | | | |
| | + | : Se non se quere modificar a configuración de PHP, tamén se poden engadir temporalmente ao principio do ficheiro PHP as seguintes instrucións: |
| | | | |
| − | * Para saber donde está el error, se puede cambiar las directivas display_errors y error_reporting del php.ini, pero si no queréis cambiar los archivos de configuración podéis poner, al principio de la página PHP lo siguiente:
| + | :<syntaxhighlight lang="php" enclose="div"> |
| − | :<syntaxhighlight lang="java" enclose="div" highlight="" > | |
| | ini_set('display_errors', 1); | | ini_set('display_errors', 1); |
| | ini_set('display_startup_errors', 1); | | ini_set('display_startup_errors', 1); |
| − | error_reporting(E_ALL); | + | error_reporting(E_ALL); </syntaxhighlight> |
| − | </syntaxhighlight> | |
| | | | |
| | + | : Desta forma PHP mostrará os erros, avisos e advertencias que se produzan durante a execución do programa. |
| | | | |
| − | : <u>Recordar quitarlo al acabar de programar la página.</u> | + | : '''IMPORTANTE:''' <u>Estas opcións deben utilizarse unicamente durante o desenvolvemento e as probas. Nun servidor en produción non se deben mostrar os erros ao usuario.</u> |
| | | | |
| | + | : Hai que ter en conta que estas instrucións non permiten detectar determinados erros que se producen antes de que o programa comece a executarse, como algúns erros de sintaxe. Nestes casos será necesario consultar o rexistro de erros do servidor ou activar a visualización de erros na configuración de PHP (''php.ini''). |
| | | | |
| − | <br /> | + | * Outra ferramenta moi útil é o inspector ou as ferramentas para desenvolvedores '''do navegador'''. Pódense abrir normalmente coa tecla '''F12'''. |
| − | == Conexión == | + | |
| | + | : Desde estas ferramentas pódense revisar, entre outras cousas: |
| | + | :* O código HTML xerado. |
| | + | :* As peticións realizadas ao servidor. |
| | + | :* Os códigos de estado HTTP, por exemplo un erro '''500 Internal Server Error'''. |
| | + | :* As mensaxes da consola de JavaScript. |
| | + | |
| | + | : Con todo, hai que ter en conta que o navegador '''non pode mostrar o código PHP orixinal''', xa que este se executa no servidor. O navegador só recibe o resultado xerado polo programa PHP. |
| | + | |
| | + | [[Imagen:Mysql_visualuzar_erros_1.png|400px|center]] |
| | + | |
| | + | |
| | + | |
| | + | |
| | + | * <u>Outra ferramenta é engadir ao VS Code a extensión que nos permita facer 'debuger' da aplicación</u>. [https://wiki.cifprodolfoucha.es/index.php?title=PHP_Habilitar_depuraci%C3%B3n_en_VS_Code Tedes nesta misma wiki o procedemento para facelo na UD0 de instalación]. |
| | + | |
| | + | |
| | + | |
| | + | * E lembrar que tamén podedes consultar o arquivo de '''error.log''' que se atopa en '''/var/log/apache2/error.log''' coa orde: tail -n 100 '''/var/log/apache2/error.log''' (sendo 100 o número de liñas que queredes consultar do arquivo empezando polo final) |
| | + | |
| | + | <br /> |
| | + | |
| | + | == Conexión == |
| | | | |
| | * Partimos da base de que xa temos instalado: | | * Partimos da base de que xa temos instalado: |
| | :* O xestor de bases de datos Mysql | | :* O xestor de bases de datos Mysql |
| | :* Unha base de datos creada. Neste manual vai ter de nome '''php'''. Facer que o 'default collation' sexa '''utf8mb4_spanish2_ci''' (máis información sobre o charset/collation [http://blog.unreal4u.com/2012/08/sobre-collation-y-charset-en-mysql/ neste enlace]. | | :* Unha base de datos creada. Neste manual vai ter de nome '''php'''. Facer que o 'default collation' sexa '''utf8mb4_spanish2_ci''' (máis información sobre o charset/collation [http://blog.unreal4u.com/2012/08/sobre-collation-y-charset-en-mysql/ neste enlace]. |
| − | :* Un usuario / password que teña todos os permisos sobre a base de datos creada. Neste manual o usuario/password creado son '''user_php/user_php'''. | + | :* Un usuario con permisos suficientes para realizar as operacións necesarias sobre a base de datos. Neste manual o usuario/password creado son '''user_php/user_php'''. |
| | | | |
| | | | |
| | : Todo o anterior xa está indicado no [http://wiki.cifprodolfoucha.es/index.php?title=PHP_Requirimentos_previos_para_acceso_a_datos punto anterior da wiki]. | | : Todo o anterior xa está indicado no [http://wiki.cifprodolfoucha.es/index.php?title=PHP_Requirimentos_previos_para_acceso_a_datos punto anterior da wiki]. |
| | | | |
| − | === Apertura da conexión ===
| |
| − | * Utilizando procedementos: [https://secure.php.net/manual/es/function.mysqli-connect.php mysqli_connect]
| |
| − | * Utilizando obxectos: [https://secure.php.net/manual/es/mysqli.construct.php mysql::_construct]
| |
| | | | |
| − | * Sintaxe (se indica a sintaxe do construtor e polo tanto será equivalente a facer un 'new' da clase mysqli):
| |
| | | | |
| − | ::* Orientada a obxectos: | + | '''NOTA IMPORTANTE:''' |
| − | :::<syntaxhighlight lang="java" enclose="div" highlight="" >
| + | |
| − | mysqli::__construct ([ string $host = ini_get("mysqli.default_host") [, string $username = ini_get("mysqli.default_user") [, string $passwd = ini_get("mysqli.default_pw") [, string $dbname = "" [, int $port = ini_get("mysqli.default_port") [, string $socket = ini_get("mysqli.default_socket") ]]]]]] )
| + | Nos seguintes apartados explícanse distintas formas de realizar operacións contra unha base de datos MySQL desde PHP: utilizando '''mysqli''' de forma procedemental, '''mysqli''' orientado a obxectos e '''PDO'''. |
| − | </syntaxhighlight>
| + | |
| | + | Os exemplos realizados con '''mysqli''' mantéñense nos apuntes con carácter '''informativo e comparativo'''. |
| | | | |
| − | ::* Por procedementos:
| + | '''Nos exames, exercicios e prácticas avaliables deberase empregar PDO, salvo que no enunciado se indique expresamente o contrario.''' |
| − | :::<syntaxhighlight lang="java" enclose="div" highlight="" >
| + | |
| − | mysqli mysqli_connect ([ string $host = ini_get("mysqli.default_host") [, string $username = ini_get("mysqli.default_user") [, string $passwd = ini_get("mysqli.default_pw") [, string $dbname = "" [, int $port = ini_get("mysqli.default_port") [, string $socket = ini_get("mysqli.default_socket") ]]]]]] )
| + | A versión de PHP empregada como referencia nestes apuntes e nos exames será '''PHP 8.5'''. |
| − | </syntaxhighlight>
| |
| | | | |
| | | | |
| − | : Todos eles son optativos, pero normalmente indicaremos o nome de host, login, password e base de datos.
| |
| − | : Os parámetros son:
| |
| − | :* Host a conectar (cadea): Pode ser o nome ou a dirección IP. Normalmente imos poñer 'localhost' (se o xestor de bases de datos está instalado no mesmo equipo que o servidor web).
| |
| − | :* Nome do usuario con acceso á base de datos (cadea): Nome do usuario que ten acceso á base de datos.
| |
| − | :* Password (cadea): Password do usuario.
| |
| − | :* Dbname (cadea): Nome da base de datos sobre a que imos traballar.
| |
| − | :* Porto (numérico): Porto no que está escoitando o xestor de base de datos. No caso de mysql é o 3306.
| |
| − | :* Socket (cadea): Socket ou tubería para conectar có xestor de bases de datos. En Linux é un tipo especial de arquivo que serve para 'transmitir' información, neste caso, cara o xestor de bases de datos.
| |
| | | | |
| | + | <br /> |
| | + | === Apertura da conexión === |
| | | | |
| − | * Aplicado ao noso caso, teremos a seguinte orde:
| + | ::* '''Utilizando PDO''': |
| − | :* Por procedementos: | + | :::<syntaxhighlight lang="php" enclose="div" highlight="" > |
| − | :::<syntaxhighlight lang="java" line enclose="div" highlight="" > | + | public PDO::__construct( |
| − | $conex = mysqli_connect('localhost','user_php','user_php','php'); | + | string $dsn, |
| | + | ?string $username = null, |
| | + | #[\SensitiveParameter] ?string $password = null, |
| | + | ?array $options = null |
| | + | ) |
| | </syntaxhighlight> | | </syntaxhighlight> |
| | + | :: No caso de PDO é obrigatorio indicar o parámetro dsn (Data Source Name), que identifica o controlador e os datos necesarios para realizar a conexión. No noso caso, ao empregar MySQL, indicaremos o servidor, a base de datos e o xogo de caracteres. |
| | + | |
| | + | |
| | + | |
| | + | : Un exemplo: |
| | + | :::<syntaxhighlight lang="php enclose="div" highlight="" > |
| | + | $dsn = 'mysql:host=localhost;dbname=php;charset=utf8mb4'; |
| | + | |
| | + | $opciones = [ |
| | + | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, |
| | + | PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, |
| | + | PDO::ATTR_EMULATE_PREPARES => false |
| | + | ]; |
| | | | |
| | + | $conex = new PDO($dsn, $usuario, $password, $opciones); |
| | | | |
| − | :* Orientado a obxectos:
| |
| − | :::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | $conex = new mysqli('localhost','user_php','user_php','php');
| |
| | </syntaxhighlight> | | </syntaxhighlight> |
| | | | |
| | + | :: Como vemos modificamos varios atributos da conexión no array $opciones. Podemos consultar todas as opcións no [https://www.php.net/manual/en/pdo.setattribute.php seguinte enlace]. |
| | + | :* Neste caso modificamos o atributo ATTR_ERRMODE e damos o valor ERRMODE_EXCEPTION. |
| | + | :: O que indica dito atributo é o seguinte: |
| | + | :::<syntaxhighlight lang="php" enclose="div" highlight="" > |
| | + | Excepción PDO::ERRMODE_EXCEPTION Fai que PDO lance unha PDOException cando se produce un erro. É o modo predeterminado desde PHP 8.0 e será o que empregaremos no módulo. |
| | | | |
| − | * En caso de erro, $conex é un obxecto que represente a conexión á base de datos.
| + | Silencioso PDO::ERRMODE_SILENT Non lanza erros. Simplemente devolve false se hai un fallo. Hai que comprobar manualmente cada operación. |
| | + | Aviso (Warning) PDO::ERRMODE_WARNING Xera un warning de PHP cando ocorre un erro, pero o script continúa. Obsoleta a partires da versión 8.5. Non empregar. |
| | + | </syntaxhighlight> |
| | + | :* PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC: Indica que cando recollamos datos da base de datos os imos recoller nun array asociativo. Se sabemos empregar a POO podemos optar por FETCH_OBJECT. |
| | + | :* PDO::ATTR_EMULATE_PREPARES => false: Isto fai que as sentenzas prepared sexan xestionadas polo xestor de base de datos e non polo php. |
| | | | |
| | | | |
| | <br /> | | <br /> |
| − | ==== Control de errores ==== | + | :: Fixarse que a conexión a estamos a facer contra Mysql. |
| | + | :: Unha das vantaxes de PDO é que proporciona unha interface común para traballar con diferentes xestores de bases de datos. No módulo utilizaremos exclusivamente PDO_MySQL, pero a modo informativo existen outros controladores. |
| | + | :: Se queremos conectarnos con outra base de datos poderíamos poñer os seguintes valores: |
| | + | :::<syntaxhighlight lang="php" enclose="div" highlight="1" > |
| | + | mysql mysql:host=localhost;dbname=php;charset=utf8mb4 Conecta a unha base de datos MySQL ou MariaDB |
| | + | pgsql pgsql:host=localhost;port=5432;dbname=instituto;user=user_pg;password=1234 Conecta a unha base de datos PostgreSQL |
| | + | sqlite sqlite:/var/www/basedatos.db Conecta a unha base de datos SQLite (sen servidor) |
| | + | sqlsrv sqlsrv:Server=localhost;Database=instituto Conecta a Microsoft SQL Server |
| | + | oci oci:dbname=xe;charset=UTF8 Conecta a Oracle |
| | + | ibm ibm:DRIVER={IBM DB2 ODBC DRIVER};DATABASE=db;HOSTNAME=host;PORT=50000;PROTOCOL=TCPIP;UID=user;PWD=pass; Conecta a IBM DB2 |
| | + | firebird firebird:dbname=localhost:/ruta/a/base.fdb Conecta a Firebird |
| | + | dblib dblib:host=localhost;dbname=instituto Conecta a Sybase ou Microsoft SQL Server antigo |
| | + | informix informix:host=host;service=puerto;database=db;server=servidor;protocol=onsoctcp;UID=user;PWD=pass; Conecta a Informix |
| | + | odbc odbc:mi_dsn Usa unha fonte de datos ODBC definida no sistema |
| | + | </syntaxhighlight> |
| | + | |
| | | | |
| − | * '''En versións de PHP igual ou superior á 8.1''': Neste caso, debemos empregar a forma try ... catch da programación orientada a obxectos:
| |
| | | | |
| − | :* Versión con chamada por procedemento teremos que chamar á función [http://www.w3schools.com/php/func_mysqli_connect_error.asp mysqli_connect_error()] desta forma:
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | try {
| |
| − | $conex = mysqli_connect('localhost', 'usuario_inexistente', 'contrasena_incorrecta', 'base_de_datos');
| |
| − | } catch (mysqli_sql_exception $e) {
| |
| − | die("Erro de conexión: " . $e->getMessage());
| |
| − | }
| |
| − | </syntaxhighlight>
| |
| | | | |
| − | :* Versión con chamada empregando obxectos: | + | <br /> |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" > | + | '''NOTA:''' O indicado a continuación inclúese a nivel informativo. Nos exames empregarase PDO. |
| − | try {
| + | |
| − | $conex = new mysqli('localhost', 'usuario_inexistente', 'contrasena_incorrecta', 'base_de_datos');
| + | * Utilizando procedementos: [https://secure.php.net/manual/es/function.mysqli-connect.php mysqli_connect] |
| − | } catch (mysqli_sql_exception $e) {
| + | * Utilizando obxectos: [https://secure.php.net/manual/es/mysqli.construct.php mysql::_construct] |
| − | die("Erro de conexión: " . $e->getMessage());
| + | |
| − | }
| + | * Sintaxe (se indica a sintaxe do construtor e polo tanto será equivalente a facer un 'new' da clase mysqli): |
| | + | |
| | + | ::* '''Orientada a obxectos''': |
| | + | :::<syntaxhighlight lang="php" enclose="div" highlight="" > |
| | + | mysqli::__construct ([ string $host = ini_get("mysqli.default_host") [, string $username = ini_get("mysqli.default_user") [, string $passwd = ini_get("mysqli.default_pw") [, string $dbname = "" [, int $port = ini_get("mysqli.default_port") [, string $socket = ini_get("mysqli.default_socket") ]]]]]] ) |
| | </syntaxhighlight> | | </syntaxhighlight> |
| | | | |
| − | :<u>Nota:</u> | + | ::* '''Por procedementos''': |
| − | :* Normalmente ao usuario non se lle vai amosar o erro que devolve o xestor de base de datos e dito erro sóese gardar nunha táboa interna para o administrador e que poida ver cal foi o erro. | + | :::<syntaxhighlight lang="php" enclose="div" highlight="" > |
| − | :* Podemos facer un único try-catch que envolva toda a páxina PHP | + | mysqli mysqli_connect ([ string $host = ini_get("mysqli.default_host") [, string $username = ini_get("mysqli.default_user") [, string $passwd = ini_get("mysqli.default_pw") [, string $dbname = "" [, int $port = ini_get("mysqli.default_port") [, string $socket = ini_get("mysqli.default_socket") ]]]]]] ) |
| − | :: Ou facer un try-catch por cada bloque php que poida xerar unha excepción. | + | </syntaxhighlight> |
| | | | |
| | | | |
| | + | : Todos eles son optativos, pero normalmente indicaremos o nome de host, login, password e base de datos. |
| | + | : Os parámetros son: |
| | + | :* Host a conectar (cadea): Pode ser o nome ou a dirección IP. Normalmente imos poñer 'localhost' (se o xestor de bases de datos está instalado no mesmo equipo que o servidor web). |
| | + | :* Nome do usuario con acceso á base de datos (cadea): Nome do usuario que ten acceso á base de datos. |
| | + | :* Password (cadea): Password do usuario. |
| | + | :* Dbname (cadea): Nome da base de datos sobre a que imos traballar. |
| | + | :* Porto (numérico): Porto no que está escoitando o xestor de base de datos. No caso de mysql é o 3306. |
| | + | :* Socket (cadea): Socket ou tubería para conectar có xestor de bases de datos. En Linux é un tipo especial de arquivo que serve para 'transmitir' información, neste caso, cara o xestor de bases de datos. |
| | | | |
| | | | |
| | <br /> | | <br /> |
| − | * Por exemplo, imos modificar o password do usuario por un que non exista e veremos o resultado:
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="2" >
| |
| − | try {
| |
| − | $conex = mysqli_connect('localhost','user_php','password_incorrecta','php');
| |
| − | } catch (mysqli_sql_exception $e) {
| |
| − | die("Erro de conexión: (" . $e->getCode() . ') ' . $e->getMessage());
| |
| − | }
| |
| − | </syntaxhighlight>
| |
| | | | |
| − | : Dará como resultado:
| |
| − | [[Imagen:Php_BD_3.jpg|400px|center]]
| |
| | | | |
| − | | + | * Aplicado ao noso caso, teremos a seguinte orde: |
| − | | + | :* Por procedementos: |
| − | <br />
| + | :::<syntaxhighlight lang="php" line enclose="div" highlight="" > |
| − | * '''En versións de PHP inferior á 8.1''' | |
| − | :* En caso de ter unha versión PHP anterior á PHP 5.2.9 e 5.3.0 ou estamos a utilizar a versión de chamadas por procedementos, teremos que chamar á función [http://www.w3schools.com/php/func_mysqli_connect_error.asp mysqli_connect_error()] desta forma: | |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="2" > | |
| | $conex = mysqli_connect('localhost','user_php','user_php','php'); | | $conex = mysqli_connect('localhost','user_php','user_php','php'); |
| − | if (mysqli_connect_error()){
| |
| − | die('Erro de Conexión (' . mysqli_connect_errno() . ') ' . mysqli_connect_error());
| |
| − | }
| |
| | </syntaxhighlight> | | </syntaxhighlight> |
| | | | |
| | | | |
| − | :* En caso de ter unha versión PHP posterior á PHP 5.2.9 e 5.3.0 e estar utilizando a forma orientada a obxectos: | + | :* Orientado a obxectos: |
| − | | + | :::<syntaxhighlight lang="php" line enclose="div" highlight="" > |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="2" > | |
| | $conex = new mysqli('localhost','user_php','user_php','php'); | | $conex = new mysqli('localhost','user_php','user_php','php'); |
| − | if ($conex->connect_error) {
| |
| − | die('Erro de Conexión (' . $conex->connect_errno . ') ' . $conex->connect_error);
| |
| − | }
| |
| | </syntaxhighlight> | | </syntaxhighlight> |
| | | | |
| | + | :: $conex é un obxecto que represente a conexión á base de datos. |
| | | | |
| − | <br />
| |
| − | : Como vemos mostramos por un lado a mensaxe de erro que devolve o xestor de bases de datos e o número de erro asociado a esa mensaxe.
| |
| − | : Podemos consultar os número de erro e mensaxes asociadas [http://dev.mysql.com/doc/refman/5.6/en/error-messages-server.html neste enlace].
| |
| − | :<u>Nota:</u> Normalmente ao usuario non se lle vai amosar o erro que devolve o xestor de base de datos e dito erro sóese gardar nunha táboa interna para o administrador e que poida ver cal foi o erro.
| |
| | | | |
| | | | |
| | | | |
| | <br /> | | <br /> |
| | + | ==== Control de errores ==== |
| | | | |
| − | === Gardando os datos de conexión nun lugar seguro ===
| + | * '''En versións de PHP igual ou superior á 8.1''': Neste caso, debemos empregar a forma try ... catch da programación orientada a obxectos. '''ESTE É A FORMA QUE IMOS EMPREGAR NOS EXAMES''': |
| | | | |
| − | * Como é obvio os datos de base de datos, usuario e password son moi importantes e non debemos permitir que alguén poida descubrilos. | + | :* Versión con chamada empregando pdo: |
| | + | :::<syntaxhighlight lang="php" enclose="div" highlight="" > |
| | + | try { |
| | + | $dsn = 'mysql:host=localhost;dbname=php;charset=utf8mb4'; |
| | + | |
| | + | $usuario = 'user_php'; |
| | + | $password = 'user_php'; |
| | | | |
| − | * O que faremos será o seguinte:
| + | $opciones = [ |
| | + | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, |
| | + | PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, |
| | + | PDO::ATTR_EMULATE_PREPARES => false |
| | + | ]; |
| | | | |
| − | :* Definiremos en forma de constantes ditos datos nun arquivo de include.
| + | $conex = new PDO($dsn, $usuario, $password, $opciones); |
| − | :* Gardaremos dito arquivo '''fora''' do cartafol de acceso do noso sitio web.
| |
| | | | |
| − | * O primeiro punto, definir en forma de constante e nun arquivo de include os datos da conexión:
| + | } |
| | + | catch (PDOException $e) { |
| | + | die("Error de conexión PDO: (" . $e->getCode() . ") " . $e->getMessage()); |
| | + | } |
| | + | } catch (Throwable $e) { |
| | + | die('Erro xeral: ' . $e->getMessage()); |
| | + | } |
| | | | |
| − | '''Arquivo: bd.inc.php''':
| + | </syntaxhighlight> |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | <?php
| |
| | | | |
| − | define ('SERVIDOR','localhost');
| |
| − | define ('USUARIO','user_php');
| |
| − | define ('PASSWORD','user_php');
| |
| − | define ('BD','php');
| |
| − | </syntaxhighlight>
| |
| | | | |
| − | :* Faremos un '''require''' deste arquivo en todas as páxinas que necesiten unha conexión á base de datos. Desta forma, se necesitamos cambiar algo soamente o cambiaríamos nun arquivo. | + | :<u>Nota:</u> |
| | + | :* Normalmente ao usuario non se lle vai amosar o erro que devolve o xestor de base de datos e dito erro sóese gardar nunha táboa interna ou nun arquivo de log para o administrador e que poida ver cal foi o erro. |
| | + | :* Podemos facer un único try-catch que envolva toda a páxina PHP ou facer un try-catch por cada bloque php que poida xerar unha excepción. |
| | + | :* Os atributos: |
| | + | ::*PDO::ERRMODE_EXCEPTION: Valor predeterminado a partires da versión 8.0, pero o imos poñer de todas formas. |
| | + | ::*PDO::FETCH_ASSOC: Cando consultemos datos devolverá os mesmos en forma de array asociativo. Se poñemos PDO::FETCH_OBJ obteremos os datos como propiedades de clase coma $row->nome. |
| | + | ::*ATTR_EMULATE_PREPARES => false : Fai que as sentenzas prepared (o veremos posteriormente) as faga o xestor de base de datos. |
| | | | |
| | | | |
| − | * O segundo punto consiste en descubrir cal é a ruta física á que apunta o noso servidor web e colocar o arquivo de include 'fora' desa ruta.
| + | : Por exemplo: |
| − | :* Temos [http://wiki.cifprodolfoucha.es/index.php?title=DRUPAL_Instalaci%C3%B3n#Instalaci.C3.B3n_do_Apache neste punto da Wiki] información sobre como funciona Apache e como saber a onde apunta o servidor web no seu arquivo de configuración. | + | ::<syntaxhighlight lang="php" enclose="div" highlight="" > |
| | + | try { |
| | | | |
| − | :* Temos varias opcións: | + | $dsn = 'mysql:host=localhost;dbname=php;charset=utf8mb4'; |
| − | ::* Facendo uso da [http://php.net/manual/es/language.constants.predefined.php constante __DIR__].
| |
| − | ::* Facendo uso do array global [http://php.net/manual/es/reserved.variables.server.php $_SERVER] e dentro deste a entrada $_SERVER['DOCUMENT_ROOT'].
| |
| | | | |
| − | :Se facemos un echo destas variables dentro dunha páxina do noso sitio web, obteremos unha ruta, coma por exemplo: | + | $opciones = [ |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" > | + | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, |
| − | /var/www/html/
| + | PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, |
| − | </syntaxhighlight>
| + | PDO::ATTR_EMULATE_PREPARES => false |
| | + | ]; |
| | | | |
| − | :<u>Nota:</u> Se o facemos coa constante __DIR__ e a páxina que está a facer o echo se atopa dentro dun cartafol, tamén aparecerá o cartafol (por exemplo /var/www/html/cartafol/).
| + | $conex = new PDO($dsn, $usuario, $password, $opciones); |
| | | | |
| | + | } catch (PDOException $e) { |
| | + | die("Erro de conexión: (" . $e->getCode() . ') ' . $e->getMessage()); |
| | + | } |
| | + | } catch (Throwable $e) { |
| | + | die('Erro xeral: ' . $e->getMessage()); |
| | + | } |
| | + | </syntaxhighlight> |
| | | | |
| | | | |
| − | :* Isto quere dicir que o servidor web (e polo tanto calquera usuario de Internet) vai poder acceder as páxinas que se atopan no cartafol /var/www/html e calquera dentro del.
| |
| | | | |
| − | :: Unha medida de seguridade é colocar o arquivo de include 'fora' desta ruta. Por exemplo, podemos crear un arquivo no cartafol '''/var/www/confBD''' | + | <br /> |
| | + | : <u>Nota</u>Lembrar que cando se produce un erro non se debe amosar ao usuario nin o getCode() nin o getMessage(). Isto soamente é necesario en depuración, non en producción. |
| | | | |
| − | :: A dito cartafol ten que poder acceder o usuario que utiliza o Apache para acceder aos cartafoles a nivel do S.O. (no caso de Apache soe ser www-data, podedes miralo no [http://wiki.cifprodolfoucha.es/index.php?title=DRUPAL_Instalaci%C3%B3n#Instalaci.C3.B3n_do_Apache punto da wiki indicado anteriormente]). | + | : Dará como resultado: |
| | + | [[Imagen:Php_BD_3.jpg|400px|center]] |
| | | | |
| − | <gallery caption="Creando o cartafol onde se vai gardar o arquivo de configuración" widths="350" heights="300px" perrow="2">
| |
| − | Image:Php_BD_4.jpg| Facemos un '''cd /var/www'''. Creamos o cartafol: '''sudo mkdir confBD'''. <u>Movemos o arquivo de include creado no paso anterior ao cartafol:</u> '''mv /cartafol_arquivo_include/bd.inc.php /var/www/confBD'''.
| |
| − | Image:Php_BD_5.jpg| Cambiamos os permisos: '''sudo chmod -R 550 /var/www/confBD''' e cambiamos o propietario: '''sudo chown -R www-data:www-data /var/www/confBD'''. <u>Nota:</u> O usuario e grupo pode variar dependendo do S.O. En Linux podedes sabelo mirando [http://wiki.cifprodolfoucha.es/index.php?title=DRUPAL_Instalaci%C3%B3n#Arquivos_de_configuraci.C3.B3n este enlace da wiki]. En Windows o podedes saber [http://httpd.apache.org/docs/2.0/platform/windows.html#winsvc neste enlace].
| |
| − | </gallery>
| |
| | | | |
| | | | |
| − | :* Agora, calquera páxina que faga uso destes datos terá que facer o seguinte require:
| |
| | | | |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="6" >
| + | <br /> |
| − | <?php
| + | '''NOTA:''' O indicado a continuación inclúese a nivel informativo. Nos exames empregarase PDO. |
| | | | |
| − | //error_reporting(0); // Inabilita os erros de Apache se temos activada a directiva display_erros no php.ini
| + | :* Por procedementos: |
| | + | ::<syntaxhighlight lang="php" enclose="div" highlight="" > |
| | + | try { |
| | + | $conex = mysqli_connect('localhost', 'usuario_inexistente', 'contrasena_incorrecta', 'base_de_datos'); |
| | + | } catch (mysqli_sql_exception $e) { |
| | + | die("Erro de conexión: " . $e->getMessage()); |
| | + | } |
| | + | } catch (Throwable $e) { |
| | + | die('Erro xeral: ' . $e->getMessage()); |
| | + | } |
| | | | |
| | + | </syntaxhighlight> |
| | | | |
| − | require ('/var/www/confBD/bd.inc.php');
| |
| − | $conex = new mysqli(SERVIDOR,USUARIO,PASSWORD,BD);
| |
| | | | |
| − | </syntaxhighlight>
| |
| | | | |
| | + | :* Versión con chamada empregando obxectos: |
| | + | ::<syntaxhighlight lang="php" enclose="div" highlight="" > |
| | + | try { |
| | + | $conex = new mysqli('localhost', 'usuario_inexistente', 'contrasena_incorrecta', 'base_de_datos'); |
| | + | } catch (mysqli_sql_exception $e) { |
| | + | die("Erro de conexión: " . $e->getMessage()); |
| | + | } |
| | + | } catch (Throwable $e) { |
| | + | die('Erro xeral: ' . $e->getMessage()); |
| | + | } |
| | | | |
| − | * Se queremos facer o mesmo pero sen utilizar rutas absolutas (por exemplo se estamos nun aloxamento compartido):
| + | </syntaxhighlight> |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="6-7" >
| |
| − | <?php
| |
| | | | |
| − | //error_reporting(0); // Inabilita os erros de Apache se temos activada a directiva display_erros no php.ini
| + | <br /> |
| | + | * Por exemplo, imos modificar o password do usuario por un que non exista e veremos o resultado: |
| | + | ::<syntaxhighlight lang="php" line enclose="div" highlight="2" > |
| | + | try { |
| | + | $conex = mysqli_connect('localhost','user_php','password_incorrecta','php'); |
| | + | } catch (mysqli_sql_exception $e) { |
| | + | die("Erro de conexión: (" . $e->getCode() . ') ' . $e->getMessage()); |
| | + | } |
| | + | } catch (Throwable $e) { |
| | + | die('Erro xeral: ' . $e->getMessage()); |
| | + | } |
| | | | |
| | + | </syntaxhighlight> |
| | | | |
| − | $ruta = substr($_SERVER['DOCUMENT_ROOT'],0,strpos($_SERVER['DOCUMENT_ROOT'],'html')) . 'confBD'; // RUTA al sitio web sin /html pero con /confBD
| |
| − | require ($ruta.'/bd.inc.php');
| |
| | | | |
| − | $conex = new mysqli(SERVIDOR,USUARIO,PASSWORD,BD);
| + | <br /> |
| | | | |
| − | </syntaxhighlight>
| + | === Gardando os datos de conexión nun lugar seguro === |
| | | | |
| − | ===O xogo de caracteres===
| + | * Como é obvio os datos de base de datos, usuario e password son moi importantes e non debemos permitir que alguén poida descubrilos. |
| | | | |
| − | * Debemos ter en conta que unha páxina web está 'aberta' a todo o mundo que se conecte a ela. | + | * O que faremos será o seguinte: |
| − | : Cada persoa en diferentes países terá diferente xogos de caracteres no que cada carácter está relacionado cun código numérico. | |
| | | | |
| − | * Se gardamos como dato dentro da base de datos unha cadea cun 'ñ' entre os seus datos, dito carácter estará asociado a un código nun xogo de caracteres. Se o usuario que vai cargar a páxina o carga cun xogo de caracteres diferente non vai poder visualizalo. | + | :* Definiremos en forma de constantes ditos datos nun arquivo de include. |
| | + | :* Gardaremos dito arquivo '''fora''' do cartafol de acceso do noso sitio web. |
| | | | |
| | + | * O primeiro punto, definir en forma de constante e nun arquivo de include os datos da conexión: |
| | | | |
| − | * Cando creamos aplicacións en PHP con acceso a base de datos teremos que ter en conta os seguintes xogos de caracteres: | + | '''Arquivo: bd.php''': |
| | + | ::<syntaxhighlight lang="php" line enclose="div" highlight="" > |
| | + | <?php |
| | + | |
| | + | define ('SERVIDOR','localhost'); |
| | + | define ('USUARIO','user_php'); |
| | + | define ('PASSWORD','user_php'); |
| | + | define ('BD','php'); |
| | + | </syntaxhighlight> |
| | + | |
| | + | :* Faremos un '''require''' deste arquivo en todas as páxinas que necesiten unha conexión á base de datos. Desta forma, se necesitamos cambiar algo soamente o cambiaríamos nun arquivo. |
| | | | |
| − | :* Xogo de caracteres da base de datos (no noso caso Mysql): É o que se coñece como [https://dev.mysql.com/doc/refman/5.5/en/charset-charsets.html '''Charset/Collation'''].
| |
| − | [[Imagen:Php_BD_7.jpg|300px|center]]
| |
| − | ::: Se vos fixades cando creamos a base de datos en Mysql, escollemos o charset (primeira columna) e o collation (segunda columna).
| |
| − | ::: Como indicamos antes, cando creamos a base de datos o charset/collation indicado foi '''utf8mb4/utf8mb4_spanish2_ci'''.
| |
| − | ::: O charset é como mysql garda internamente os caracteres e os relaciona cun código numérico e o collation indica como comparar e ordenar o texto. Podedes consultar [http://blog.unreal4u.com/2012/08/sobre-collation-y-charset-en-mysql/ esta blog] para obter máis información.
| |
| | | | |
| − | :* Xogo de caracteres da páxina HTML. | + | * O segundo punto consiste en descubrir cal é a ruta física á que apunta o noso servidor web e colocar o arquivo de include 'fora' desa ruta. |
| | + | :* Temos [http://wiki.cifprodolfoucha.es/index.php?title=DRUPAL_Instalaci%C3%B3n#Instalaci.C3.B3n_do_Apache neste punto da Wiki] información sobre como funciona Apache e como saber a onde apunta o servidor web no seu arquivo de configuración. |
| | | | |
| − | ::: Na páxina web indicamos coa etiqueta <meta> o xogo de caracteres para que o navegador web poida visualizalos correctamente. | + | :* Temos varias opcións: |
| | + | ::* Facendo uso da [http://php.net/manual/es/language.constants.predefined.php constante __DIR__]. |
| | + | ::* Facendo uso do array global [http://php.net/manual/es/reserved.variables.server.php $_SERVER] e dentro deste a entrada $_SERVER['DOCUMENT_ROOT']. |
| | | | |
| − | ::: No caso que nos ocupa teremos que poñer: | + | :Se facemos un echo destas variables dentro dunha páxina do noso sitio web, obteremos unha ruta, coma por exemplo: |
| − | :::* Versións inferiores a Html 5: <meta http-equiv="Content-Type" content="text/html; <b>charset=UTF-8"</b> /> | + | ::<syntaxhighlight lang="php" line enclose="div" highlight="" > |
| − | :::* Html 5: <meta <b>charset="UTF-8"</b>>
| + | /var/www/html/ |
| | + | </syntaxhighlight> |
| | | | |
| − | ::: Máis información en [http://www.w3schools.com/html/html_charset.asp w3schools]. | + | :<u>Nota:</u> Se o facemos coa constante __DIR__ e a páxina que está a facer o echo se atopa dentro dun cartafol, tamén aparecerá o cartafol (por exemplo /var/www/html/cartafol/). |
| | | | |
| − | :* No código PHP ao conectar coa base de datos.
| |
| − | :: Para iso teremos que facer uso da [https://secure.php.net/manual/es/mysqli.set-charset.php función set_charset].
| |
| − | :: En caso de éxito devolve true ou false en caso de existir algún problema.
| |
| | | | |
| − | ::: Cando conectemos coa base de datos teremos que indicarlle que imos utilizar o xogo de caracteres utf8mb4 con esta liña:
| |
| − | :::* Versión procedimental
| |
| − | ::::<syntaxhighlight lang="java" line enclose="div" highlight="3" >
| |
| − | try {
| |
| − | $conex = mysqli_connect('localhost', 'user_php', 'user_php', 'php');
| |
| − | mysqli_set_charset($conex,'utf8mb4');
| |
| − | } catch (mysqli_sql_exception $e) {
| |
| − | die("Erro de conexión: " . $e->getMessage());
| |
| − | }
| |
| − | </syntaxhighlight>
| |
| | | | |
| | + | :* Isto quere dicir que o servidor web (e polo tanto calquera usuario de Internet) vai poder acceder as páxinas que se atopan no cartafol /var/www/html e calquera dentro del. |
| | + | |
| | + | :: Unha medida de seguridade é colocar o arquivo de include 'fora' desta ruta. Por exemplo, podemos crear un arquivo no cartafol '''/var/www/confBD''' |
| | + | |
| | + | :: A dito cartafol ten que poder acceder o usuario que utiliza o Apache para acceder aos cartafoles a nivel do S.O. (no caso de Apache soe ser www-data, podedes miralo no [http://wiki.cifprodolfoucha.es/index.php?title=DRUPAL_Instalaci%C3%B3n#Instalaci.C3.B3n_do_Apache punto da wiki indicado anteriormente]). |
| | | | |
| − | :::* Versión orientada a obxectos
| + | <gallery caption="Creando o cartafol onde se vai gardar o arquivo de configuración" widths="350" heights="300px" perrow="2"> |
| − | ::::<syntaxhighlight lang="java" line enclose="div" highlight="3" >
| + | Image:Php_BD_4.jpg| Facemos un '''cd /var/www'''. Creamos o cartafol: '''sudo mkdir confBD'''. <u>Movemos o arquivo de include creado no paso anterior ao cartafol:</u> '''mv /cartafol_arquivo_include/bd.inc.php /var/www/confBD'''. |
| − | try {
| + | Image:Php_BD_5.jpg| Cambiamos os permisos: '''sudo chmod -R 550 /var/www/confBD''' e cambiamos o propietario: '''sudo chown -R www-data:www-data /var/www/confBD'''. <u>Nota:</u> O usuario e grupo pode variar dependendo do S.O. En Linux podedes sabelo mirando [http://wiki.cifprodolfoucha.es/index.php?title=DRUPAL_Instalaci%C3%B3n#Arquivos_de_configuraci.C3.B3n este enlace da wiki]. En Windows o podedes saber [http://httpd.apache.org/docs/2.0/platform/windows.html#winsvc neste enlace]. |
| − | $conex = new mysqli('localhost', 'user_php', 'user_php', 'php');
| + | </gallery> |
| − | $conex->set_charset('utf8mb4');
| |
| − | } catch (mysqli_sql_exception $e) {
| |
| − | die("Erro de conexión: " . $e->getMessage());
| |
| − | }
| |
| | | | |
| − | </syntaxhighlight>
| |
| | | | |
| | + | :* Agora, calquera páxina que faga uso destes datos terá que facer o seguinte require: |
| | | | |
| | + | :* No caso de PDO: |
| | + | ::<syntaxhighlight lang="php" line enclose="div" highlight="3,7,8" > |
| | + | <?php |
| | | | |
| | + | require '/var/www/confBD/bd.php'; |
| | | | |
| − | * '''E FUNDAMENTAL QUE NOS TRES SITIOS ESTEA POSTO O MESMO TIPO DO XOGO DE CARACTERES'''.
| + | try { |
| | | | |
| − | ===Peche da conexión=== | + | $dsn = sprintf( |
| | + | 'mysql:host=%s;dbname=%s;charset=utf8mb4', |
| | + | SERVIDOR, |
| | + | BD |
| | + | ); |
| | | | |
| − | * Despois de facer as diferentes operacións sobre a base de datos, teremos que pechar a conexión.
| + | $opciones = [ |
| − | : Para iso temos que chamar á [http://php.net/manual/es/mysqli.close.php función mysqil::close]. | + | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, |
| | + | PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, |
| | + | PDO::ATTR_EMULATE_PREPARES => false |
| | + | ]; |
| | | | |
| − | <u>Nota:</u> En todas as páxinas se opta por facer un único try-catch, pero dependendo de se se quere saber a sección onde foi o erro, se podería facer varios try-catch.
| + | $conex = new PDO( |
| | + | $dsn, |
| | + | USUARIO, |
| | + | PASSWORD, |
| | + | $opciones |
| | + | ); |
| | | | |
| − | :::* Versión procedimental | + | } catch (PDOException $e) { |
| − | ::::<syntaxhighlight lang="java" enclose="div" highlight="19-21" >
| + | die('Erro de conexión coa base de datos.'); |
| | + | } |
| | + | </syntaxhighlight> |
| | + | |
| | + | '''IMPORTANTE:''' Este será o modelo de conexión que se empregará como referencia nos exames. |
| | + | |
| | + | |
| | + | |
| | + | |
| | + | :* No caso de orientado a obxectos: |
| | + | ::<syntaxhighlight lang="php" line enclose="div" highlight="6" > |
| | <?php | | <?php |
| − | try { // O try se pecha na sección catch do final
| |
| − | $conex = mysqli_connect('localhost', 'user_php', 'user_php', 'php');
| |
| − | mysqli_set_charset($conex,'utf8mb4');
| |
| − | ?>
| |
| − | ..............
| |
| − | // Operacións contra a base de datos e amosar información ao usuario
| |
| − | ..............
| |
| | | | |
| | + | //error_reporting(0); // Inabilita os erros de Apache se temos activada a directiva display_erros no php.ini |
| | + | |
| | + | |
| | + | require ('/var/www/confBD/bd.php'); |
| | + | $conex = new mysqli(SERVIDOR,USUARIO,PASSWORD,BD); |
| | + | |
| | + | </syntaxhighlight> |
| | + | |
| | + | |
| | + | |
| | + | * Se queremos facer o mesmo pero sen utilizar rutas absolutas (por exemplo se estamos nun aloxamento compartido): |
| | + | ::<syntaxhighlight lang="php" line enclose="div" highlight="6-7" > |
| | <?php | | <?php |
| − | } // Fin do try do principio da páxina
| |
| − | catch (mysqli_sql_exception $e) {
| |
| − | die("Erro mysql: " . $e->getMessage());
| |
| − | }
| |
| − | catch (Expcetion $e) {
| |
| − | die("Erro xeral: " . $e->getMessage());
| |
| − | }
| |
| − | finally{
| |
| − | if(!empty($conex)) {
| |
| − | mysqli_close($conex);
| |
| − | }
| |
| − | }
| |
| − | ?>
| |
| − | </syntaxhighlight>
| |
| | | | |
| − | :::* Versión orientada a obxectos
| + | $ruta = substr($_SERVER['DOCUMENT_ROOT'],0,strpos($_SERVER['DOCUMENT_ROOT'],'html')) . 'confBD'; // RUTA al sitio web sin /html pero con /confBD |
| − | ::::<syntaxhighlight lang="java" enclose="div" highlight="19-21" >
| + | require ($ruta.'/bd.php'); |
| − | <?php
| + | |
| − | try {
| + | </syntaxhighlight> |
| − | $conex = new mysqli('localhost', 'user_php', 'user_php', 'php');
| + | |
| − | $conex->set_charset('utf8mb4');
| |
| − | ?>
| |
| | | | |
| − | ..............
| + | <br /> |
| − | // Operacións contra a base de datos e amosar información ao usuario
| + | ===O xogo de caracteres=== |
| − | ..............
| |
| | | | |
| − | <?php
| + | * Debemos ter en conta que unha páxina web está 'aberta' a todo o mundo que se conecte a ela. |
| − | } // Fin do try do principio da páxina
| + | : Cada persoa en diferentes países terá diferente xogos de caracteres no que cada carácter está relacionado cun código numérico. |
| − | catch (mysqli_sql_exception $e) {
| |
| − | die("Erro mysql: " . $e->getMessage());
| |
| − | }
| |
| − | catch (Expcetion $e) {
| |
| − | die("Erro xeral: " . $e->getMessage());
| |
| − | }
| |
| − | finally{
| |
| − | if(!empty($conex)){
| |
| − | $conex->close();
| |
| − | }
| |
| − | }
| |
| − | ?>
| |
| | | | |
| − | </syntaxhighlight>
| + | * Se gardamos como dato dentro da base de datos unha cadea cun 'ñ' entre os seus datos, dito carácter estará asociado a un código nun xogo de caracteres. Se o usuario que vai cargar a páxina o carga cun xogo de caracteres diferente non vai poder visualizalo. |
| | | | |
| − | == Cambiando de base de datos activa ==
| |
| | | | |
| − | * Cando creamos a cadea de conexión, un dos parámetros que enviamos foi a base de datos na que iamos traballar. | + | * Cando creamos aplicacións en PHP con acceso a base de datos teremos que ter en conta os seguintes xogos de caracteres: |
| | | | |
| − | : Podemos non enviar ese parámetro ou querer cambiar de base de datos dentro dunha páxina php. | + | :* Xogo de caracteres da base de datos (no noso caso Mysql): É o que se coñece como [https://dev.mysql.com/doc/refman/5.5/en/charset-charsets.html '''Charset/Collation''']. |
| | + | [[Imagen:Php_BD_7.jpg|300px|center]] |
| | + | ::: Se vos fixades cando creamos a base de datos en Mysql, escollemos o charset (primeira columna) e o collation (segunda columna). |
| | + | ::: Como indicamos antes, cando creamos a base de datos o charset/collation indicado foi '''utf8mb4/utf8mb4_spanish2_ci'''. |
| | + | ::: O charset é como mysql garda internamente os caracteres e os relaciona cun código numérico e o collation indica como comparar e ordenar o texto. Podedes consultar [http://blog.unreal4u.com/2012/08/sobre-collation-y-charset-en-mysql/ esta blog] para obter máis información. |
| | | | |
| − | : Se queremos escoller outra diferente teremos que facer uso da [http://php.net/manual/es/mysqli.select-db.php función mysqli::select_db]. | + | :* Xogo de caracteres da páxina HTML. |
| | | | |
| − | : Por exemplo: | + | ::: Na páxina web indicamos coa etiqueta <meta> o xogo de caracteres para que o navegador web poida visualizalos correctamente. |
| − | :* Versión procedimental:
| |
| − | ::<syntaxhighlight lang="java" enclose="div" highlight="3,5" > | |
| − | <?php
| |
| − | try {
| |
| − | $conex = mysqli_connect('localhost', 'user_php', 'user_php'); // Non indicamos o nome da bd
| |
| − | mysqli_set_charset($conex,'utf8mb4');
| |
| − | mysqli_select_db($conex, "php"); // O segundo parámetro indica o nome da base de datos
| |
| | | | |
| − | ?>
| + | ::: No caso que nos ocupa teremos que poñer: |
| − | ..............
| + | :::* Html 5 e posteriores: <meta <b>charset="UTF-8"</b>> |
| − | // Operacións contra a base de datos e amosar información ao usuario
| |
| − | ..............
| |
| | | | |
| − | <?php
| + | ::: Máis información en [http://www.w3schools.com/html/html_charset.asp w3schools]. |
| − | } // Fin do try do principio da páxina
| |
| − | catch (mysqli_sql_exception $e) {
| |
| − | die("Erro mysql: " . $e->getMessage());
| |
| − | }
| |
| − | catch (Expcetion $e) {
| |
| − | die("Erro xeral: " . $e->getMessage());
| |
| − | }
| |
| − | finally{
| |
| − | if(!empty($conex)){
| |
| − | mysqli_close($conex);
| |
| − | }
| |
| − | }
| |
| − | ?>
| |
| | | | |
| | + | :* No código PHP ao conectar coa base de datos. |
| | + | ::: Cando conectemos coa base de datos teremos que indicarlle que imos utilizar o xogo de caracteres utf8mb4 con esta liña: |
| | | | |
| − | </syntaxhighlight> | + | :::* Versión PDO: |
| − | | + | ::::<syntaxhighlight lang="php" line enclose="div" highlight="5" > |
| | + | require ('/var/www/confBD/bd.php'); |
| | | | |
| − | <br />
| |
| − | :* Versión orientada a obxectos:
| |
| − | ::<syntaxhighlight lang="java" enclose="div" highlight="3,5" >
| |
| − | <?php
| |
| | try { | | try { |
| − | $conex = new mysqli('localhost', 'user_php', 'user_php'); // Non indicamos o nome da bd | + | $dsn = sprintf( |
| − | $conex->set_charset('utf8mb4');
| + | 'mysql:host=%s;dbname=%s;charset=utf8mb4', |
| − | $conex->select_db('php'); // O parámetro indica o nome da base de datos | + | SERVIDOR, |
| | + | BD |
| | + | ); |
| | | | |
| − | ?>
| + | $opciones = [ |
| | + | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, |
| | + | PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, |
| | + | PDO::ATTR_EMULATE_PREPARES => false |
| | + | ]; |
| | | | |
| − | ..............
| + | $conex = new PDO( |
| − | // Operacións contra a base de datos e amosar información ao usuario
| + | $dsn, |
| − | ..............
| + | USUARIO, |
| | + | PASSWORD, |
| | + | $opciones |
| | + | ); |
| | | | |
| − | <?php
| + | </syntaxhighlight> |
| − | } // Fin do try do principio da páxina
| |
| − | catch (mysqli_sql_exception $e) {
| |
| − | die("Erro mysql: " . $e->getMessage());
| |
| − | }
| |
| − | catch (Expcetion $e) {
| |
| − | die("Erro xeral: " . $e->getMessage());
| |
| − | }
| |
| − | finally{
| |
| − | if(!empty($conex)){
| |
| − | $conex->close();
| |
| − | }
| |
| − | }
| |
| − | ?>
| |
| − | </syntaxhighlight> | |
| | | | |
| − | == Consulta ==
| + | * '''É importante empregar unha configuración coherente de Unicode en toda a aplicación. Neste módulo empregaremos utf8mb4 en MySQL e na conexión PDO, e UTF-8 nas páxinas HTML.'''. |
| | | | |
| − | * Para conectarnos primeiro teremos que ter a consulta SQL.
| |
| − | * Indicar que o [https://es.wikipedia.org/wiki/SQL SQL (Structure Query Language)] é a linguaxe utilizada para obter datos dunha base de datos relacional.
| |
| | | | |
| − | : Consulta SQL: '''SELECT col1, col2 FROM TABOA_1'''
| |
| | | | |
| | | | |
| − | * Funcións para realizar consultas a base de datos e funcións relacionadas: | + | :::* Versión procedimental |
| | + | :: Para iso teremos que facer uso da [https://secure.php.net/manual/es/mysqli.set-charset.php función set_charset]. |
| | + | :: En caso de éxito devolve true ou false en caso de existir algún problema. |
| | | | |
| − | :* [http://php.net/manual/es/mysqli.query.php mysqli::query]: Realiza a consulta | + | ::::<syntaxhighlight lang="php" line enclose="div" highlight="4" > |
| − | :* [http://php.net/manual/es/class.mysqli-result.php mysqli_result]: En caso de realizar unha consulta de tipo 'select' este obxecto representa o conxunto de resultados obtidos.
| + | try { |
| − | ::* [http://php.net/manual/es/mysqli-result.num-rows.php int $mysqli_result->num_rows]: Devolve o número de filas devoltas en caso de realizar unha consulta.
| + | require ('/var/www/confBD/bd.php'); |
| − | ::* Para recuperar os datos podemos empregar:
| + | $conex = mysqli_connect(SERVIDOR, USUARIO, PASSWORD, BD); |
| − | :::* [http://php.net/manual/es/mysqli-result.fetch-assoc.php array mysqli_result::fetch_assoc(void)]: Devolve nun '''array asociativo''' unha das filas do resultado da consulta e 'pasa' á fila seguinte. Cando remate e xa non teña máis filas devolve NULL. A clave do array asociativo é cada un dos nomes das columnas que conforman a consulta.
| + | mysqli_set_charset($conex,'utf8mb4'); |
| − | :::* [https://www.php.net/manual/en/mysqli-result.fetch-object.php mysqli_result::fetch_object(void)]: Devolve nun '''array de obxectos''' todas as filas do resultado da consulta e 'pasa' á fila seguinte. Cando remate e xa non teña máis filas devolve NULL. Cada obxecto terá como propiedades cada unha das columnas que conforman a consulta (distingue maiúsculas de minúsculas).
| + | } catch (mysqli_sql_exception $e) { |
| − | ::* [http://php.net/manual/es/mysqli-result.free.php void mysqli_result::free ( void )] Libera da memoria o conxunto de resultados.
| + | die("Erro de conexión: " . $e->getMessage()); |
| − | ::* [http://php.net/manual/es/mysqli.error.php mysqli::error]: Devolve en forma de cadea o último erro xerado polo xestor de base de datos Mysql.
| + | } |
| − | ::* [http://php.net/manual/es/mysqli.errno.php mysqli::errno]: Devolve o último código de erro devolto polo xestor Mysql
| |
| − | | |
| − | | |
| − | * O proceso para realizar unha consulta é o seguinte:
| |
| − | :* Abrimos a conexión á base de datos.
| |
| − | :* A idea é chamar ao procedemento query enviando como parámetro a consulta. | |
| − | ::* Versión procedimental:
| |
| − | :::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | $result=mysqli_query($conex, 'SELECT col1, col2 FROM TABOA_1');
| |
| | </syntaxhighlight> | | </syntaxhighlight> |
| − | <u>Nota:</u> $conex é a conexión aberta feita no paso anterior.
| |
| | | | |
| − | ::* Versión orientada a obxectos:
| |
| − | :::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | $result=$conex->query('SELECT col1, col2 FROM TABOA_1');
| |
| − | </syntaxhighlight>
| |
| | | | |
| − | ::* $result vai ter o conxunto de filas que devolva o resultado. Podemos velo como unha táboa formada polas filas do resultado e polas columnas indicadas no select. | + | :::* Versión orientada a obxectos: |
| − | ::: Se a consulta está mal feita (por exemplo, seleccionamos unha táboa que non existe) <u>devolverá false</u>. | + | ::::<syntaxhighlight lang="php" line enclose="div" highlight="4" > |
| − | ::: Polo tanto, o seguinte será comprobar se temos datos
| + | try { |
| | + | require ('/var/www/confBD/bd.php'); |
| | + | $conex = new mysqli(SERVIDOR,USUARIO,PASSWORD,BD); |
| | + | $conex->set_charset('utf8mb4'); |
| | + | } catch (mysqli_sql_exception $e) { |
| | + | die("Erro de conexión: " . $e->getMessage()); |
| | + | } |
| | | | |
| − | :::* Versión procedimental:
| + | </syntaxhighlight> |
| − | ::::<syntaxhighlight lang="java" line enclose="div" highlight="1" >
| |
| − | if ($result && (mysqli_num_rows($result)>0)){
| |
| − | // Veñen datos
| |
| | | | |
| − | }
| |
| − | else {
| |
| − | if(mysqli_errno($conex)){
| |
| − | echo "Houbo un erro na chamada ao xestor...";
| |
| − | // O seguinte non tería que amosarse ao usuario final da aplicación. Posto para desenvolvemento.
| |
| − | echo "<div>";
| |
| − | printf("Erro no Mysql. Número %d. Mensaxe: %s", mysqli_errno($conex), mysqli_error($conex));
| |
| − | echo "<div>";
| |
| − | }
| |
| − | else{
| |
| − | echo "Non hai datos que amosar!!!!";
| |
| − | }
| |
| − | }
| |
| − | </syntaxhighlight>
| |
| | | | |
| | | | |
| − | :::* Versión orientada a obxectos:
| |
| − | ::::<syntaxhighlight lang="java" line enclose="div" highlight="1" >
| |
| − | if ($result && ($result->num_rows>0)){
| |
| − | // Veñen datos
| |
| | | | |
| − | }
| + | <br /> |
| − | else {
| + | ===Peche da conexión=== |
| − | if($conex->errno){
| |
| − | echo "Houbo un erro na chamada ao xestor...";
| |
| − | // O seguinte non tería que amosarse ao usuario final da aplicación. Posto para desenvolvemento.
| |
| − | echo "<div>";
| |
| − | printf("Erro no Mysql. Número %d. Mensaxe: %s", $conex->errno, $conex->error);
| |
| − | echo "<div>";
| |
| − | }
| |
| − | else{
| |
| − | echo "Non hai datos que amosar!!!!";
| |
| − | }
| |
| − | }
| |
| − | </syntaxhighlight>
| |
| | | | |
| − | :* Como vemos, en caso de erro comprobamos se o xestor devolveu un código de erro, indicativo de que a sentenza SQL está mal feita. A información do código de erro non debe amosarse a un usuario final. Normalmente, se pon o echo de 'Non hai datos que amosar' e se estamos a desenvolver a aplicación e non devolve datos, faríamos un echo temporal do código de erro do xestor ou un echo da sentenza SQL, podendo executala no xestor Mysql para ver o erro.
| + | * Despois de facer as diferentes operacións sobre a base de datos, teremos que pechar a conexión. |
| | + | : Para iso temos que chamar á [http://php.net/manual/es/mysqli.close.php función mysqil::close] nas versión orientada a obxectos e procedimental. No caso de PDO chega con igual a null o obxecto $conex. |
| | | | |
| | + | :<u>Nota:</u> Neste exemplo partimos que estamos a empregar a verión PHP 8.1 ou superior. En todas as páxinas se opta por facer un único try-catch, pero dependendo de se se quere saber a sección onde foi o erro, se podería facer varios try-catch. |
| | | | |
| | | | |
| | + | :* '''Versión PHP 8.1 ou superior''' |
| | | | |
| − | :* Agora necesitaremos percorrer dende o primeiro rexistro ata o último amosando o resultado.
| |
| − | ::: Isto o faremos cun bucle, chamando á [http://php.net/manual/es/mysqli-result.fetch-assoc.php función mysqli_result::fetch_assoc(void)].
| |
| − | ::: Debemos entender que, inicialmente, despois de facer a consulta, temos un punteiro que está 'apuntando' xusto antes do primeiro rexistro de resultados. O que fai a función anterior é mover ese punteiro ao seguinte rexistro (a primeira vez apuntará ao primeiro do conxunto de resultados) e devolve nun array a fila á que está 'apuntando'.
| |
| − | ::: Se volvemos a chamar, volve a moverse ao seguinte rexistro e así ata ao final no que devolve false.
| |
| | | | |
| | + | :::* Versión PDO: |
| | + | : En PDO non existe un método close(). Para pechar explicitamente unha conexión podemos asignar null á variable que contén o obxecto PDO. Se non o facemos, PHP pechará automaticamente a conexión ao rematar a execución do script. |
| | | | |
| − | ::* Versión procedimental:
| + | ::::<syntaxhighlight lang="php" enclose="div" highlight="36" > |
| − | ::::<syntaxhighlight lang="java" line enclose="div" highlight="3-5" > | + | <?php |
| − | if ($result && (mysqli_num_rows($resullt)>0)){
| |
| − | // Veñen datos
| |
| − | while($row = mysqli_fetch_assoc($result)) {
| |
| − | printf("%s-%s",htmlentities($row["col1"]),htmlentities($row["col2"]));
| |
| − | }
| |
| − | }
| |
| − | else {
| |
| − | echo 'Non hai datos ou a consulta está mal feita';
| |
| − | }
| |
| − | </syntaxhighlight> | |
| | | | |
| − | ::* Versión orientada a obxectos:
| + | require ('/var/www/confBD/bd.php'); |
| − | ::::<syntaxhighlight lang="java" line enclose="div" highlight="3-5" >
| |
| − | if ($result && ($result->num_rows>0)){
| |
| − | // Veñen datos
| |
| − | while($row = $result->fetch_assoc()) {
| |
| − | printf("%s-%s",htmlentities($row["col1"]),htmlentities($row["col2"]));
| |
| − | }
| |
| − | }
| |
| − | else {
| |
| − | echo 'Non hai datos ou a consulta está mal feita';
| |
| − | }
| |
| − | </syntaxhighlight>
| |
| | | | |
| | + | try { |
| | | | |
| − | :* Ao rematar de percorrer o resultado, liberaremos a memoria chamando á [http://php.net/manual/es/mysqli-result.free.php función free()] | + | $dsn = sprintf( |
| | + | 'mysql:host=%s;dbname=%s;charset=utf8mb4', |
| | + | SERVIDOR, |
| | + | BD |
| | + | ); |
| | | | |
| − | ::* Versión procedimental: | + | $opciones = [ |
| − | :::<syntaxhighlight lang="java" line enclose="div" highlight="6" > | + | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, |
| − | if ($result && (mysqli_num_rows($result)>0)){
| + | PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, |
| − | // Veñen datos | + | PDO::ATTR_EMULATE_PREPARES => false |
| − | while($row = mysqli_fetch_assoc($result)) {
| + | ]; |
| − | printf("%s-%s",htmlentities($row["col1"]),htmlentities($row["col2"])); | |
| − | }
| |
| − | mysqli_free_result($result); // Liberamos da memoria os recursos
| |
| − | }
| |
| − | else {
| |
| − | echo 'Non hai datos ou a consulta está mal feita';
| |
| − | }
| |
| − | </syntaxhighlight>
| |
| | | | |
| − | ::* Versión orientada a obxectos:
| + | $conex = new PDO( |
| − | :::<syntaxhighlight lang="java" line enclose="div" highlight="6" >
| + | $dsn, |
| − | if ($result && ($result->num_rows>0)){
| + | USUARIO, |
| − | // Veñen datos
| + | PASSWORD, |
| − | while($row = $result->fetch_assoc()) {
| + | $opciones |
| − | printf("%s-%s",htmlentities($row["col1"]),htmlentities($row["col2"]));
| + | ); |
| − | }
| |
| − | $result->free(); // Liberamos da memoria os recursos
| |
| − | }
| |
| − | else {
| |
| − | echo 'Non hai datos ou a consulta está mal feita';
| |
| − | }
| |
| − | </syntaxhighlight>
| |
| | | | |
| | + | // .............. |
| | + | // Operaciones contra a base de datos e amosar información ao usuario |
| | + | // .............. |
| | | | |
| − | :* Pechamos a conexión. | + | } catch (PDOException $e) { |
| | + | die("Error PDO: " . $e->getMessage()); |
| | + | } catch (Throwable $e) { |
| | + | die("Error general: " . $e->getMessage()); |
| | + | } |
| | | | |
| − | ::* Versión procedimental:
| + | $conex = null; // Poderíamos non poñer nada |
| − | :::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | mysqli_close($conex); // Pechamos a conexión
| |
| − | </syntaxhighlight>
| |
| | | | |
| − | ::* Versión orientada a obxectos:
| |
| − | :::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | $conex->close(); // Pechamos a conexión
| |
| − | </syntaxhighlight>
| |
| | | | |
| | + | </syntaxhighlight> |
| | | | |
| | | | |
| | | | |
| | | | |
| − | === Caso Práctico 1 === | + | :::* Versión procedimental |
| − | : Imos crear unha consulta que nos devolva todos os libros da táboa LIBROS dentro dunha táboa:
| + | ::::<syntaxhighlight lang="php" enclose="div" highlight="21-25" > |
| − | : Para non ter as consultas no medio do código PHP das diferentes páxinas, imos definila no '''arquivo bd.inc.php''' creado no punto anterior.
| + | <?php |
| | | | |
| − | ::<syntaxhighlight lang="java" enclose="div" highlight="" >
| + | require ('/var/www/confBD/bd.php'); |
| − | define ('CONSULTAR_LIBROS','select id,titulo from libros order by titulo');
| |
| − | </syntaxhighlight>
| |
| | | | |
| | + | try { // O try se pecha na sección catch do final |
| | + | $conex = mysqli_connect(SERVIDOR, USUARIO, PASSWORD, BD); |
| | + | mysqli_set_charset($conex,'utf8mb4'); |
| | + | ?> |
| | + | .............. |
| | + | // Operacións contra a base de datos e amosar información ao usuario |
| | + | .............. |
| | | | |
| − | * '''Normas nos nomes das constantes''':
| |
| − | :* Comenzarán cun verbo que vai poder ser: CONSULTAR, INSERIR, MODIFICAR e ELIMINAR
| |
| − | :* Seguirá cun guión baixo.
| |
| − | :* En caso de consultar os datos dunha única táboa se indicará o nome da táboa. En caso de consultar datos de varias táboas se indicará con varias palabras separadas con guión baixo que indiquen o obxectivo da consulta.
| |
| − |
| |
| − |
| |
| − |
| |
| − |
| |
| − | * Aplicado ao exemplo que estamos a seguir...
| |
| − | :<u>Nota</u> Lembrar activar as directivas para amosar erros e quitalas cando rematedes a páxina.
| |
| − |
| |
| − | ==== Solución Caso Práctico 1 ====
| |
| − |
| |
| − | '''Arquivo: consultar_libros.php'''
| |
| − | ::<syntaxhighlight lang="java" enclose="div" highlight="27-40" >
| |
| | <?php | | <?php |
| − | /* Lembrar quitar isto ao probar o scripts sen erros */ | + | } // Fin do try do principio da páxina |
| − | ini_set('display_errors', 1);
| + | catch (mysqli_sql_exception $e) { |
| − | ini_set('display_startup_errors', 1);
| + | die("Erro mysql: " . $e->getMessage()); |
| − | error_reporting(E_ALL);
| + | } |
| | + | catch (Expcetion $e) { |
| | + | die("Erro xeral: " . $e->getMessage()); |
| | + | } |
| | + | finally{ |
| | + | if(!empty($conex)) { |
| | + | mysqli_close($conex); |
| | + | } |
| | + | } |
| | + | ?> |
| | + | </syntaxhighlight> |
| | | | |
| − | require ('/var/www/confBD/bd.inc.php'); | + | :::* Versión orientada a obxectos |
| | + | ::::<syntaxhighlight lang="php" enclose="div" highlight="21-25" > |
| | + | <?php |
| | + | require ('/var/www/confBD/bd.php'); |
| | | | |
| | try { | | try { |
| − | $conex = new mysqli(SERVIDOR,USUARIO,PASSWORD); // Non indicamos o nome da bd | + | $conex = new mysqli(SERVIDOR, USUARIO, PASSWORD, BD); |
| | $conex->set_charset('utf8mb4'); | | $conex->set_charset('utf8mb4'); |
| − | $conex->select_db(BD); // O parámetro indica o nome da base de datos
| |
| − |
| |
| | ?> | | ?> |
| | | | |
| − | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
| + | .............. |
| − | "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
| + | // Operacións contra a base de datos e amosar información ao usuario |
| − | <html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es">
| + | .............. |
| − | <head>
| |
| − | <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
| |
| − | <title>Operacións BD</title>
| |
| − | </head>
| |
| − | | |
| − | <body>
| |
| − | <?php
| |
| − |
| |
| − | $result=$conex->query(CONSULTAR_LIBROS);
| |
| − | if ($result && $result->num_rows>0){
| |
| − | echo "<table border='1'>";
| |
| − | while($row = $result->fetch_assoc()) {
| |
| − | echo '<tr>';
| |
| − | printf("<td>%s</td>",$row["titulo"]);
| |
| − | echo '</tr>';
| |
| − | }
| |
| − | echo '</table>';
| |
| − | $result->free(); // Liberamos da memoria os recursos
| |
| − | }
| |
| − | else {
| |
| − | echo "Non hai datos que amosar!!!!";
| |
| − | }
| |
| − |
| |
| − | ?>
| |
| − | </body>
| |
| − | </html>
| |
| | | | |
| | <?php | | <?php |
| − | } // Pecha o try do principio da páxina | + | } // Fin do try do principio da páxina |
| | catch (mysqli_sql_exception $e) { | | catch (mysqli_sql_exception $e) { |
| | die("Erro mysql: " . $e->getMessage()); | | die("Erro mysql: " . $e->getMessage()); |
| Línea 905: |
Línea 853: |
| | finally{ | | finally{ |
| | if(!empty($conex)){ | | if(!empty($conex)){ |
| − | $conex->close();
| + | $conex->close(); |
| | } | | } |
| | } | | } |
| Línea 913: |
Línea 861: |
| | | | |
| | | | |
| − | * Resultado:
| |
| − | [[Imagen:Php_BD_8.jpg|300px|center]]
| |
| | | | |
| | + | <br /> |
| | + | |
| | + | == Cambiando de base de datos activa == |
| | | | |
| − | * Fixarse como neste exemplo, aínda que a consulta devolve o id do libro, como non o necesitamos non o amosamos ao usuario, xa que dito dato é un código interno que utilizamos como clave primaria. | + | * Cando realizamos unha conexión con PDO podemos indicar no '''DSN''' a base de datos coa que queremos traballar: |
| | | | |
| | + | :<syntaxhighlight lang="php"> |
| | + | $dsn = 'mysql:host=localhost;dbname=php;charset=utf8mb4'; </syntaxhighlight> |
| | | | |
| | + | : Neste caso, a base de datos seleccionada inicialmente será '''php'''. |
| | | | |
| − | * O método fetch_assoc() move un punteiro interno á seguinte fila de resultados. | + | * Se durante a execución do programa necesitamos traballar con outra base de datos temos, entre outras, dúas posibilidades: |
| − | : Se queremos modificar dito punteiro e situarnos en calquera fila do resultado, debemos facer uso da [http://php.net/manual/es/mysqli-result.data-seek.php función mysqli_data_seek]· | + | :* Crear unha nova conexión PDO indicando a nova base de datos no DSN. |
| | + | :* Cambiar a base de datos activa da conexión actual mediante a sentenza SQL '''USE'''. |
| | | | |
| | + | === Crear unha nova conexión PDO === |
| | | | |
| | + | * Podemos crear un novo obxecto PDO indicando outra base de datos no DSN: |
| | | | |
| | + | :<syntaxhighlight lang="php"> |
| | | | |
| | + | <?php |
| | | | |
| − | <br />
| + | try { |
| − | === Caso Práctico 2 ===
| |
| | | | |
| − | * Agora imos facer unha consulta á táboa AUTORES recuperando todos os autores ordenados por apelidos e os amosaremos en forma de lista desordenada no que cada elemento da lista será enlace có seguinte formato: <enlace href='detalle_autor.php?id=XXX'>Apelidos , nome </enlace>
| + | $opciones = [ |
| − | : Sendo XXX a columna id_autor.
| + | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, |
| | + | PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, |
| | + | PDO::ATTR_EMULATE_PREPARES => false |
| | + | ]; |
| | | | |
| − | : <u>Nota:</u> Se queremos facer máis seguro o paso de información entre páxinas, poderíamos empregar un formulario e que cada autor tivera un button con un value igual á clave primaria. De tal forma que ao premer nel, o formulario envaría á seguinte páxina o id do autor premido e dita información non ser vería na URL. Por exemplo: <button type="submit" name="enviar" value="valor_a_enviar">Texto a amosar</button>
| + | // Conexión inicial á base de datos php |
| | + | $dsn = 'mysql:host=localhost;dbname=php;charset=utf8mb4'; |
| | | | |
| | + | $conex = new PDO( |
| | + | $dsn, |
| | + | 'user_php', |
| | + | 'user_php', |
| | + | $opciones |
| | + | ); |
| | | | |
| | + | // Creamos unha nova conexión á base de datos outra_db |
| | + | $dsn = 'mysql:host=localhost;dbname=outra_db;charset=utf8mb4'; |
| | | | |
| | + | $conex = new PDO( |
| | + | $dsn, |
| | + | 'user_php', |
| | + | 'user_php', |
| | + | $opciones |
| | + | ); |
| | | | |
| − | <br />
| + | } catch (PDOException $e) { |
| − | ==== Solución Caso Práctico 2 ====
| |
| | | | |
| − | : No '''arquivo bd.inc.php''' creado no punto anterior. | + | die('Erro relacionado coa base de datos: ' . $e->getMessage()); |
| − | ::<syntaxhighlight lang="java" enclose="div" highlight="" >
| |
| − | define ('CONSULTAR_AUTORES','SELECT id,apelidos,nome FROM autores order by apelidos,nome');
| |
| − | </syntaxhighlight>
| |
| | | | |
| | + | } catch (Throwable $e) { |
| | | | |
| | + | die('Erro xeral: ' . $e->getMessage()); |
| | | | |
| − | <br />
| + | } |
| − | '''Páxina consultar_autores.php'''
| + | ?> |
| − | :<syntaxhighlight lang="java" enclose="div" highlight="28-42" >
| |
| − | <?php
| |
| − | /* Lembrar quitar isto ao probar o scripts sen erros */
| |
| − | ini_set('display_errors', 1);
| |
| − | ini_set('display_startup_errors', 1);
| |
| − | error_reporting(E_ALL);
| |
| | | | |
| − | require ('/var/www/confBD/bd.inc.php');
| + | </syntaxhighlight> |
| | | | |
| − | try {
| + | : Ao crear o segundo obxecto PDO estamos realizando unha nova conexión na que a base de datos seleccionada inicialmente será '''outra_db'''. |
| − | $conex = new mysqli('localhost', 'user_php', 'user_php'); // Non indicamos o nome da bd
| |
| − | $conex->set_charset('utf8mb4');
| |
| − | $conex->select_db('php'); // O parámetro indica o nome da base de datos
| |
| | | | |
| | + | === Cambiar a base de datos activa con USE === |
| | | | |
| − | ?>
| + | * Se queremos manter a mesma conexión e cambiar a base de datos activa podemos executar a sentenza SQL '''USE''': |
| − | | |
| − | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
| |
| − | "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
| |
| − | <html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es">
| |
| − | <head>
| |
| − | <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
| |
| − | <title>Operacións BD</title>
| |
| − | </head>
| |
| | | | |
| − | <body>
| + | :<syntaxhighlight lang="php"> |
| − | <?php
| |
| − |
| |
| − | $result=$conex->query(CONSULTAR_AUTORES);
| |
| − | if ($result && $result->num_rows>0){
| |
| − | // Veñen datos
| |
| − | echo "<ul>";
| |
| − | while($row = mysqli_fetch_assoc($result)) {
| |
| − | echo "<li>";
| |
| − | printf("<a href='detalle_autor.php?id=%s'>%s</a>",$row["id"],$row["apelidos"].', '.$row['nome']);
| |
| − | echo "</li>";
| |
| − | }
| |
| − | echo "</ul>";
| |
| − | $result->free(); // Liberamos da memoria os recursos
| |
| − | }
| |
| − | else {
| |
| − | echo "Non hai datos que amosar!!!!";
| |
| − | }
| |
| − |
| |
| − | ?>
| |
| − | </body>
| |
| − | </html>
| |
| | | | |
| | <?php | | <?php |
| − | }
| |
| − | catch (mysqli_sql_exception $e) {
| |
| − | die("Erro mysql: " . $e->getMessage());
| |
| − | }
| |
| − | catch (Expcetion $e) {
| |
| − | die("Erro xeral: " . $e->getMessage());
| |
| − | }
| |
| − | finally{
| |
| − | if(!empty($conex)){
| |
| − | $conex->close();
| |
| − | }
| |
| − | }
| |
| − | ?>
| |
| − | </syntaxhighlight>
| |
| | | | |
| | + | try { |
| | | | |
| | + | $dsn = 'mysql:host=localhost;dbname=php;charset=utf8mb4'; |
| | | | |
| | + | $opciones = [ |
| | + | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, |
| | + | PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, |
| | + | PDO::ATTR_EMULATE_PREPARES => false |
| | + | ]; |
| | | | |
| − | <br />
| + | $conex = new PDO( |
| − | === Consulta enviando parámetros de tipo numérico ===
| + | $dsn, |
| | + | 'user_php', |
| | + | 'user_php', |
| | + | $opciones |
| | + | ); |
| | + | |
| | + | // A base de datos activa inicialmente é php |
| | + | |
| | + | $conex->exec('USE outra_db'); |
| | + | |
| | + | // A partir deste momento a base de datos activa é outra_db |
| | + | |
| | + | } catch (PDOException $e) { |
| | + | |
| | + | die('Erro relacionado coa base de datos: ' . $e->getMessage()); |
| | + | |
| | + | } catch (Throwable $e) { |
| | + | |
| | + | die('Erro xeral: ' . $e->getMessage()); |
| | + | |
| | + | } |
| | + | ?> |
| | + | |
| | + | </syntaxhighlight> |
| | | | |
| − | <u>Nota:</u> Lembra que no paso de datos temos que ter en conta os posibles 'ataques' que poidan intentar facerse. Repasar o dado [http://wiki.cifprodolfoucha.es/index.php?title=PHP_Paso_de_datos_con_formularios#Seguridade_no_uso_dos_formularios na wiki anteriormente].
| + | : A sentenza '''USE''' cambia a base de datos predeterminada da conexión actual. Esta seguirá sendo a base de datos activa ata que se execute outra sentenza '''USE''' ou se peche a conexión. |
| | | | |
| − | : Neste curso '''sempre''' que se vaia a enviar un dato ao xestor Mysql aplicarémoslle a [https://www.php.net/manual/es/mysqli.real-escape-string.php función mysqli_real_escape_string]. O ideal sería empregar procedementos almacenados como veremos posteriormente. | + | : O usuario empregado para realizar a conexión deberá ter os permisos necesarios sobre a nova base de datos. |
| | | | |
| | + | * Podemos comprobar cal é a base de datos activa empregando: |
| | | | |
| − | * Cando facemos unha consulta ou operación contra base de datos (alta, baixa e modificación) imos ter que 'enviar' datos.
| + | :<syntaxhighlight lang="php"> |
| − | : A forma de facelo é utilizando a consulta SQL cun parámetro e substituíndo dito parámetro polo valor que queiramos enviar.
| + | $stmt = $conex->query('SELECT DATABASE()'); |
| | | | |
| − | * Así: SELECT col1, col2 FROM TABOA_1 WHERE col1='''%d'''
| + | echo $stmt->fetchColumn(); </syntaxhighlight> |
| | | | |
| − | : No exemplo, col1 é de tipo numérico. | + | : Neste caso, despois de executar: |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" > | |
| − | define ('CONSULTA_CON_PARAM_NUMERICO','SELECT col1, col2 FROM TABOA_1 WHERE col1=%d');
| |
| | | | |
| − | $query = sprintf(CONSULTA_CON_PARAM_NUMERICO,valor_numérico_a_enviar); | + | :<syntaxhighlight lang="php"> |
| − | </syntaxhighlight> | + | $conex->exec('USE outra_db'); </syntaxhighlight> |
| | | | |
| − | : Agora facemos o mesmo código que no exemplo anterior. | + | : o código anterior devolvería: |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" > | |
| − | ......
| |
| − | $result=$conex->query(CONSULTA_CON_PARAM_NUMERICO);
| |
| − | ......
| |
| − | </syntaxhighlight>
| |
| | | | |
| | + | :<syntaxhighlight lang="text"> |
| | + | outra_db </syntaxhighlight> |
| | | | |
| | + | '''IMPORTANTE:''' O nome da base de datos empregado na sentenza `USE` non debería obterse directamente de datos introducidos polo usuario. Neste módulo empregaremos nomes de bases de datos definidos previamente no código. |
| | | | |
| | <br /> | | <br /> |
| − | ==== Caso práctico: Enviando parámetros de tipo numérico ====
| |
| | | | |
| − | * Seguindo con exemplo, imos facer que cando o usuario seleccione un libro dentro dunha combobox, aparezan os exemplares do mesmo.
| + | == Optimización e seguridade: Sentenzas preparadas == |
| − | : Para iso teremos que:
| + | |
| − | :* Crear a sentencia SQL correspondente enviando un parámetro que vai ser o código do libro seleccionado.
| + | * '''NOTA:''' A explicación de como se fan consultas ven no seguinte punto, pero adianto agora a explicación de como en PDO se empregan sentenzas prepared. |
| − | :* Obter dito dato. Isto o faremos utilizando un formulario cun método POST.
| + | |
| − | :* Modificar o código da páxina anterior (Consultar_Libros.php) e facer que os libros aparezan nunha combobox (dropdownlist ou <select> en html). | |
| − | :* Facer que cando o usuario escolla un libro 'active' o formulario e polo tanto provoque o 'submit' do mesmo amosando os exemplares do libro seleccionado.
| |
| − | :* Manter seleccionado o libro na combo.
| |
| | | | |
| | | | |
| − | <u>Nota:</u> Ao principio ides ter problema de tipo 'non amosa ningún dato'. O primeiro que tedes que descartar é que a consulta SQL estea ben e devolva datos, polo que vos aconsello que fagades un 'echo' de dita consulta e a executedes no MysqlWorkBench para ver que realmente devolve algún resultado.
| + | * Unha '''sentenza preparada''' permite separar a instrución SQL dos datos que se van empregar nela. |
| | | | |
| | + | : Primeiro prepárase a sentenza SQL indicando mediante '''marcadores''' os valores que se proporcionarán posteriormente. Despois asócianse os datos a eses marcadores e execútase a sentenza. |
| | | | |
| | + | : Por exemplo: |
| | | | |
| − | * Crearemos unha nova páxina de nome '''consultar_exemplares_por_libro.php'''.
| + | ::<syntaxhighlight lang="sql" enclose="div"> |
| | + | SELECT col1 |
| | + | FROM TABOA1 |
| | + | WHERE col2 = ? </syntaxhighlight> |
| | | | |
| − | : Modificamos o código para que aparezan os libros dentro da etiqueta <select> de html:
| + | : Neste caso a sentenza ten un parámetro, representado polo carácter '''?'''. O valor correspondente proporcionarase posteriormente no momento de executar a sentenza. |
| − | : Neste caso, na combo imos gardar o código do libro xa que cando escollamos un, necesitamos obter dito código para facer outra consulta buscando os exemplares dese libro... | |
| | | | |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" > | + | : Unha mesma sentenza preparada pode executarse varias veces empregando valores diferentes. |
| − | <?php
| |
| − | /* Lembrar quitar isto ao probar o scripts sen erros */
| |
| − | ini_set('display_errors', 1);
| |
| − | ini_set('display_startup_errors', 1);
| |
| − | error_reporting(E_ALL);
| |
| | | | |
| − | require ('/var/www/confBD/bd.inc.php');
| + | * PDO permite utilizar dous tipos de marcadores: |
| | | | |
| − | try {
| + | :* '''Marcadores posicionais''', representados mediante `?`: |
| − | $conex = new mysqli(SERVIDOR, USUARIO, PASSWORD,BD); // Indico o nome da bd
| |
| − | $conex->set_charset('utf8mb4');
| |
| | | | |
| | + | ::<syntaxhighlight lang="sql" enclose="div"> |
| | + | SELECT nome, apelido |
| | + | FROM alumnos |
| | + | WHERE idade > ? </syntaxhighlight> |
| | | | |
| − | ?>
| + | :* '''Marcadores con nome''', precedidos por `:`: |
| | | | |
| − | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
| + | ::<syntaxhighlight lang="sql" enclose="div"> |
| − | <html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es"> | + | SELECT nome, apelido |
| − | <head>
| + | FROM alumnos |
| − | <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
| + | WHERE idade > :idade </syntaxhighlight> |
| − | <title>Operacións BD</title>
| |
| − | </head>
| |
| | | | |
| − | <body>
| + | : Nos exemplos dos apuntes empregaremos preferentemente '''marcadores con nome''', xa que normalmente fan que o código sexa máis lexible. |
| − | <?php
| |
| − |
| |
| − | $result=$conex->query(CONSULTAR_LIBROS);
| |
| − | if ($result && $result->num_rows>0){
| |
| − | echo "<select name='lstLibros'>";
| |
| − | echo "<option value='-1'></option>";
| |
| − | while($row = $result->fetch_assoc()) {
| |
| − | printf("<option value='%d'>%s</option>",htmlentities($row["id"]),htmlentities($row["titulo"]));
| |
| − | }
| |
| − | echo "</select>";
| |
| − | $result->free(); // Liberamos da memoria os recursos
| |
| − | }
| |
| − | else {
| |
| − | echo "Non hai datos que amosar!!!!";
| |
| − | }
| |
| − |
| |
| − | ?>
| |
| − | </body>
| |
| − | </html>
| |
| | | | |
| − | <?php
| + | === Vantaxes das sentenzas preparadas === |
| − | } // Pecha o try da parte superior da páxina
| + | |
| − | catch (mysqli_sql_exception $e) {
| + | * As sentenzas preparadas presentan, entre outras, as seguintes vantaxes: |
| − | die("Erro mysql: " . $e->getMessage());
| + | |
| − | }
| + | :* '''Rendemento''': cando unha mesma sentenza se executa repetidamente con distintos valores, a base de datos pode reutilizar parte do traballo realizado durante a preparación da sentenza, como a análise e optimización da consulta. Polo tanto, poden resultar especialmente útiles cando unha mesma operación se executa moitas veces. |
| − | catch (Expcetion $e) {
| + | |
| − | die("Erro xeral: " . $e->getMessage());
| + | :* '''Seguridade''': os datos enviados mediante parámetros son tratados como valores e non como parte da instrución SQL. Isto axuda a evitar ataques de '''inxección SQL'''. |
| − | }
| + | |
| − | finally{
| + | : '''IMPORTANTE:''' As sentenzas preparadas protexen os '''valores enviados mediante parámetros''', pero isto non significa que calquera consulta sexa automaticamente segura. |
| − | if(!empty($conex)){
| |
| − | $conex->close();
| |
| − | }
| |
| − | }
| |
| − | ?>
| |
| − | </syntaxhighlight>
| |
| | | | |
| − | :* Liña 32: Engadimos unha entrada en branco un valor -1 no seu value, para 'obrigar' a escoller un valor e que por defecto non apareza cun libro seleccionado. | + | : Por exemplo, non debemos construír unha consulta concatenando directamente datos recibidos do usuario: |
| − | : Dependerá da aplicación, podedes ter que implementar variantes como ter unha entrada que indique 'TODAS' ou facer que por defecto estea seleccionada algunha das entradas... | |
| | | | |
| − | : Resultado: | + | ::<syntaxhighlight lang="php" enclose="div"> |
| − | [[Imagen:Php_BD_9.jpg|300px|center]] | + | $sql = "SELECT * FROM alumnos WHERE nome = '" . $_POST['nome'] . "'"; </syntaxhighlight> |
| | | | |
| | + | : A forma correcta será empregar un parámetro: |
| | | | |
| | + | ::<syntaxhighlight lang="php" enclose="div"> |
| | + | $sql = 'SELECT * FROM alumnos WHERE nome = :nome'; |
| | | | |
| | + | $stmt = $conex->prepare($sql); |
| | | | |
| − | * '''Arquivo bd.inc.php'''
| + | $stmt->execute([ |
| − | : Definimos nunha constate a consulta que vai obter os exemplares do libro seleccionado:
| + | 'nome' => $_POST['nome'] |
| − | ::<syntaxhighlight lang="java" enclose="div" highlight="" >
| + | ]); </syntaxhighlight> |
| − | define ('CONSULTAR_EXEMPLARES_POR_LIBRO','select libro_id,id_exemplar,imaxe,prezo,estado_id from exemplares where libro_id=%d order by estado_id');
| |
| − | </syntaxhighlight>
| |
| − | :: Como vemos imos enviar o código por medio dun parámetro de tipo numérico (usaremos a función sprintf).
| |
| − | : Definimos nunha constante onde se atopan as imaxes relacionados cos libros:
| |
| − | ::<syntaxhighlight lang="java" enclose="div" highlight="" >
| |
| − | define ('CARTAFOL_IMAXES_WEB','http://www.meusitio.es/IMAXES/'); // Debedes cambiar www.meusitioweb.es pola vosa URL ou se non tedes configurado un DNS ou arquivo hosts poñede http::/localhost/IMAXES/
| |
| − | </syntaxhighlight> | |
| − | :: Teredes que cambiar a url pola vosa.
| |
| | | | |
| | + | : Os parámetros dunha sentenza preparada representan '''valores'''. Non poden utilizarse directamente para substituír nomes de táboas, nomes de columnas, palabras reservadas SQL ou outras partes estruturais dunha consulta. |
| | | | |
| | + | : Máis información: |
| | + | : [https://www.php.net/manual/es/pdo.prepared-statements.php Sentenzas preparadas con PDO] |
| | | | |
| − | * Para facer que cando se seleccione un elemento do lista se 'active' o formulario, existen múltiples formas, unha delas pode ser:
| + | === PHP empregando PDO === |
| − | ::: echo "<select name='lstLibros' <b>onchange='this.form.submit()'</b>>";
| |
| − | ::: Será necesario 'envolver' a etiqueta <select> cun <form>, con method='post' e action apuntando á mesma páxina.
| |
| | | | |
| − | :* O código que vai amosar os exemplares do libro seleccionado será o seguinte: | + | '''IMPORTANTE:''' Nos exames, exercicios e prácticas avaliables empregaremos '''PDO''' para realizar as operacións contra a base de datos, salvo que no enunciado se indique expresamente o contrario. |
| | | | |
| − | :::<syntaxhighlight lang="java" line enclose="div" highlight="42,44,47-56" >
| + | * Para preparar unha sentenza empregamos o método: |
| − | <?php
| |
| − | /* Lembrar quitar isto ao probar o scripts sen erros */
| |
| − | ini_set('display_errors', 1);
| |
| − | ini_set('display_startup_errors', 1);
| |
| − | error_reporting(E_ALL);
| |
| | | | |
| − | require ('/var/www/confBD/bd.inc.php');
| + | ::<syntaxhighlight lang="php" enclose="div"> |
| | + | PDO::prepare() </syntaxhighlight> |
| | | | |
| − | try {
| + | * Este método devolve un obxecto da clase `PDOStatement`, sobre o que posteriormente chamaremos ao método: |
| − | $conex = new mysqli(SERVIDOR, USUARIO, PASSWORD,BD); // Indico o nome da bd
| |
| − | $conex->set_charset('utf8mb4');
| |
| − | ?>
| |
| | | | |
| − | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
| + | ::<syntaxhighlight lang="php" enclose="div"> |
| − | <html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es"> | + | PDOStatement::execute() </syntaxhighlight> |
| − | <head>
| |
| − | <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
| |
| − | <title>Operacións BD</title>
| |
| − | </head>
| |
| | | | |
| − | <body>
| + | : Máis información: |
| − | <?php
| + | :* [https://www.php.net/manual/es/pdo.prepare.php PDO::prepare()] |
| − |
| + | :* [https://www.php.net/manual/es/pdostatement.execute.php PDOStatement::execute()] |
| − | $result=$conex->query(CONSULTAR_LIBROS);
| |
| − | if ($result && $result->num_rows>0){
| |
| − | printf("<form action='%s' method='post'>",htmlentities($_SERVER['PHP_SELF']));
| |
| − | echo "<select name='lstLibros' onChange='this.form.submit();'>";
| |
| − | echo "<option value='-1'></option>";
| |
| − | while($row = $result->fetch_assoc()) {
| |
| − | printf("<option value='%d'>%s</option>",htmlentities($row["id"]),htmlentities($row["titulo"]));
| |
| − | }
| |
| − | echo "</select>";
| |
| − | printf("</form>");
| |
| − | $result->free(); // Liberamos da memoria os recursos
| |
| − | }
| |
| − | else {
| |
| − | echo "Non hai datos que amosar!!!!";
| |
| − | }
| |
| − | ?>
| |
| − | <?php
| |
| − | // CONSULTAMOS OS EXEMPLARES DUN LIBRO SELECCIONADO NA COMBO
| |
| − | if (!empty($_POST['lstLibros']) && filter_var($_POST['lstLibros'],FILTER_VALIDATE_INT) && $_POST['lstLibros']!=-1){
| |
| − | echo "<div style='margin-top:40px'>";
| |
| − | $query=sprintf(CONSULTAR_EXEMPLARES_POR_LIBRO,$conex->real_escape_string($_POST['lstLibros']));
| |
| − | $result=$conex->query($query);
| |
| − | if ($result && ($result->num_rows>0)){
| |
| − | echo "<table border='1'>";
| |
| − | echo "<th>Num. Exemplar</th><th>Imaxe</th><th>Prezo</th><th>Estado</th>";
| |
| − | while($row = $result->fetch_assoc()) {
| |
| − | echo '<tr>';
| |
| − | printf("<td>%d</td><td><img alt='%s' src='%s' width='42' height='42' /></td><td>%.2f</td><td>%d</td>",
| |
| − | htmlentities($row["id_exemplar"]),htmlentities($row["imaxe"]),
| |
| − | CARTAFOL_IMAXES_WEB . htmlentities($row["imaxe"]),htmlentities($row["prezo"]),htmlentities($row["estado_id"]));
| |
| − | echo '</tr>';
| |
| − | }
| |
| − | echo '</table>';
| |
| − | $result->free(); // Liberamos da memoria os recursos
| |
| − | }
| |
| − | else {
| |
| − | echo "Non hai datos que amosar!!!!";
| |
| − | }
| |
| − | echo "</div>";
| |
| − | }
| |
| − | // $conex->close(); // Non fai falla pechar a conexión xa que esta se pecha ao final do páxina web. Noutras páxinas pode ser necesario
| |
| − |
| |
| − | ?>
| |
| | | | |
| − | </body>
| + | ==== Exemplo ==== |
| − | </html>
| |
| | | | |
| − | <?php
| + | * Supoñamos que queremos inserir datos na táboa `alumnos`: |
| − | } // Pecha o try da parte superior da páxina
| + | |
| − | catch (mysqli_sql_exception $e) {
| + | ::<syntaxhighlight lang="sql" enclose="div"> |
| − | die("Erro mysql: " . $e->getMessage());
| + | INSERT INTO alumnos (nome, apelido) |
| − | }
| + | VALUES (:nome, :apelido) </syntaxhighlight> |
| − | catch (Expcetion $e) {
| + | |
| − | die("Erro xeral: " . $e->getMessage());
| + | * O código PHP podería ser o seguinte: |
| − | }
| + | |
| − | finally{
| + | ::<syntaxhighlight lang="php" line enclose="div"> |
| − | if(!empty($conex)){
| |
| − | $conex->close();
| |
| − | }
| |
| − | }
| |
| − | ?>
| |
| − | </syntaxhighlight> | |
| | | | |
| − | ::* Liña 42: Comprobamos que veñan datos no formulario (que se escollera algún exemplar da lista) e que sexa de tipo numérico. Tamén comprobamos se seleccionamos o elemento baleiro (valor -1 na combo).
| + | <?php |
| − | ::* Liña 44: Definimos a consulta a realizar contra a base de datos, utilizando a constante definida e substituíndo o parámetro polo código do libro seleccionado.
| |
| − | ::* Liñas 47-52: Construímos a táboa para que amose os datos. Fixarse que na imaxe temos que definir o atributo 'alt' para que cumpra o estándar XHTML, polo que enviamos dúas veces o nome do arquivo. No dato que vai no atributo 'src' enviamos o nome do arquivo xunto coa URL onde se atopan as imaxes.
| |
| | | | |
| − | ::* Liña 62: Non pechamos a conexión xa que esta se pecha ao final de todo. Sempre que se abra unha conexión temos que ter o código que a pecha. Pode suceder que na mesma páxina abramos e pechemos varias veces a conexión ao introducir código con include.
| + | $conex = null; |
| | | | |
| | + | try { |
| | | | |
| | + | $dsn = 'mysql:host=localhost;dbname=php;charset=utf8mb4'; |
| | | | |
| | + | $opciones = [ |
| | + | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, |
| | + | PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, |
| | + | PDO::ATTR_EMULATE_PREPARES => false |
| | + | ]; |
| | | | |
| − | <br />
| + | $conex = new PDO( |
| − | * Intenta agora modificar o código anterior e fai que o select manteña seleccionado o libro cando aparecen os exemplares.
| + | $dsn, |
| | + | 'user_php', |
| | + | 'user_php', |
| | + | $opciones |
| | + | ); |
| | | | |
| | + | // Preparamos a sentenza |
| | + | $sql = 'INSERT INTO alumnos (nome, apelido) |
| | + | VALUES (:nome, :apelido)'; |
| | | | |
| | + | $stmt = $conex->prepare($sql); |
| | | | |
| | + | // Indicamos os datos e executamos a sentenza |
| | + | $nome = 'Ana'; |
| | + | $apelido = 'López'; |
| | | | |
| | + | $stmt->execute([ |
| | + | 'nome' => $nome, |
| | + | 'apelido' => $apelido |
| | + | ]); |
| | | | |
| − | <br />
| + | } catch (PDOException $e) { |
| − | : Posible solución:
| |
| − | :::<syntaxhighlight lang="java" line enclose="div" highlight="7-10" >
| |
| − | $result=$conex->query(CONSULTAR_LIBROS);
| |
| − | if ($result && $result->num_rows>0){
| |
| − | printf("<form action='%s' method='post'>",htmlentities($_SERVER['PHP_SELF']));
| |
| − | echo "<select name='lstLibros' onChange='this.form.submit();'>";
| |
| − | echo "<option value='-1'></option>";
| |
| − | while($row = $result->fetch_assoc()) {
| |
| − | $cadea = '';
| |
| − | if(isset($_POST['lstLibros']) && $row['id']==$_POST['lstLibros']){ // Se o libro seleccionado é igual ao id da fila, o seleccionamos en html
| |
| − | $cadea="selected='selected'";
| |
| − | }
| |
| | | | |
| − | printf("<option value='%d' %s>%s</option>",htmlentities($row["id"]),$cadea,htmlentities($row["titulo"]));
| + | // Durante o desenvolvemento podemos amosar a mensaxe de erro. |
| − | }
| + | // Nunha aplicación en produción non se debería mostrar ao usuario. |
| − | echo "</select>";
| + | printf('Erro PDO: %s', $e->getMessage()); |
| − | printf("</form>");
| |
| − | $result->free(); // Liberamos da memoria os recursos
| |
| − | }
| |
| − | else {
| |
| − | echo "Non hai datos que amosar!!!!";
| |
| − | }
| |
| | | | |
| − | </syntaxhighlight>
| + | } catch (Throwable $e) { |
| | | | |
| | + | printf('Erro xeral: %s', $e->getMessage()); |
| | | | |
| | + | } finally { |
| | | | |
| | + | $conex = null; |
| | | | |
| | + | } |
| | + | ?> |
| | | | |
| | + | </syntaxhighlight> |
| | | | |
| − | <br />
| + | ==== Executar varias veces a mesma sentenza ==== |
| − | * O código completo sería o seguinte:
| |
| | | | |
| − | '''Arquivo: bd.inc.php''':
| + | * Unha das vantaxes das sentenzas preparadas é que podemos preparar a instrución unha vez e executala varias veces con valores diferentes. |
| | | | |
| − | :::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| + | ::<syntaxhighlight lang="php" enclose="div"> |
| − | <?php
| + | $sql = 'INSERT INTO alumnos (nome, apelido) |
| | + | VALUES (:nome, :apelido)'; |
| | | | |
| − | define ('SERVIDOR','localhost');
| + | $stmt = $conex->prepare($sql); |
| − | define ('USUARIO','user_php');
| |
| − | define ('PASSWORD','user_php');
| |
| − | define ('BD','php');
| |
| | | | |
| | + | // Primeiro alumno |
| | + | $stmt->execute([ |
| | + | 'nome' => 'Ana', |
| | + | 'apelido' => 'López' |
| | + | ]); |
| | | | |
| − | define ('CONSULTAR_LIBROS','select id,titulo from libros order by titulo');
| + | // Segundo alumno |
| − | define ('CONSULTAR_AUTORES','SELECT id,apelidos,nome FROM autores order by apelidos,nome');
| + | $stmt->execute([ |
| − | define ('CONSULTAR_EXEMPLARES_POR_LIBRO','select libro_id,id_exemplar,imaxe,prezo,estado_id'
| + | 'nome' => 'Brais', |
| − | . ' from exemplares ' // Necesita un espazo en branco antes do from para concatenar có anterior
| + | 'apelido' => 'Pérez' |
| − | . ' where libro_id=%d order by titulo'); // Necesita un espazo en branco antes do where para concatenar có anterior
| + | ]); |
| − | define ('CARTAFOL_IMAXES_WEB','http://localhost/IMAXES/');
| |
| | | | |
| | + | // Terceiro alumno |
| | + | $stmt->execute([ |
| | + | 'nome' => 'Lucía', |
| | + | 'apelido' => 'García' |
| | + | ]); |
| | </syntaxhighlight> | | </syntaxhighlight> |
| | | | |
| | + | : A sentenza SQL prepárase unha vez, pero execútase tres veces con valores diferentes. |
| | | | |
| − | '''Arquivo: consultar_exemplares_por_libro.php''':
| |
| | | | |
| − | :::<syntaxhighlight lang="java" line enclose="div" highlight="" > | + | * '''Exercicio proposto:''' Intenta crear unha páxina PHP que engada á táboa 'autores' os seguintes datos: |
| − | <?php | + | :* Benito Pérez Galdós |
| − | /* Lembrar quitar isto ao probar o scripts sen erros */
| + | :* Julio Verne |
| | + | |
| | + | |
| | + | |
| | + | |
| | + | <br /> |
| | + | : '''Solución exercicio:''' |
| | + | ::<syntaxhighlight lang="php" enclose="div"> |
| | + | <?php |
| | ini_set('display_errors', 1); | | ini_set('display_errors', 1); |
| | ini_set('display_startup_errors', 1); | | ini_set('display_startup_errors', 1); |
| | error_reporting(E_ALL); | | error_reporting(E_ALL); |
| | | | |
| − | require ('/var/www/confBD/bd.inc.php'); | + | require ('/var/www/confBD/bd.php'); |
| | | | |
| | try { | | try { |
| − | $conex = new mysqli(SERVIDOR, USUARIO, PASSWORD,BD); // Indico o nome da bd
| |
| − | $conex->set_charset('utf8mb4');
| |
| | | | |
| − | ?>
| + | $dsn = sprintf( |
| | + | 'mysql:host=%s;dbname=%s;charset=utf8mb4', |
| | + | SERVIDOR, |
| | + | BD |
| | + | ); |
| | | | |
| − | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
| + | $opciones = [ |
| − | <html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es">
| + | PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, |
| − | <head>
| + | PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, |
| − | <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> | + | PDO::ATTR_EMULATE_PREPARES => false |
| − | <title>Operacións BD</title> | + | ]; |
| − | </head> | |
| | | | |
| − | <body> | + | $conex = new PDO( |
| − | <?php
| + | $dsn, |
| − |
| + | USUARIO, |
| − | $result=$conex->query(CONSULTAR_LIBROS);
| + | PASSWORD, |
| − | if ($result && $result->num_rows>0){ | + | $opciones |
| − | printf("<form action='%s' method='post'>",htmlentities($_SERVER['PHP_SELF']));
| + | ); |
| − | echo "<select name='lstLibros' onChange='this.form.submit();'>";
| |
| − | echo "<option value='-1'></option>";
| |
| − | while($row = $result->fetch_assoc()) {
| |
| − | $cadea = '';
| |
| − | if(isset($_POST['lstLibros']) && $row['id']==$_POST['lstLibros']){ // Se o libro seleccionado é igual ao id da fila, o seleccionamos en html
| |
| − | $cadea="selected='selected'";
| |
| − | }
| |
| | | | |
| − | printf("<option value='%d' %s>%s</option>",htmlentities($row["id"]),$cadea,htmlentities($row["titulo"]));
| + | echo '<br />Conecta'; |
| − | }
| |
| − | echo "</select>";
| |
| − | printf("</form>");
| |
| − | $result->free(); // Liberamos da memoria os recursos
| |
| − | }
| |
| − | else {
| |
| − | echo "Non hai datos que amosar!!!!";
| |
| − | }
| |
| − | ?> | |
| − | <?php
| |
| − | // CONSULTAMOS OS EXEMPLARES DUN LIBRO SELECCIONADO NA COMBO
| |
| − | if (!empty($_POST['lstLibros']) && filter_var($_POST['lstLibros'],FILTER_VALIDATE_INT) && $_POST['lstLibros']!=-1){
| |
| − | echo "<div style='margin-top:40px'>";
| |
| − | $query=sprintf(CONSULTAR_EXEMPLARES_POR_LIBRO,$conex->real_escape_string($_POST['lstLibros']));
| |
| − | $result=$conex->query($query);
| |
| − | if ($result && ($result->num_rows>0)){
| |
| − | echo "<table border='1'>";
| |
| − | echo "<th>Num. Exemplar</th><th>Imaxe</th><th>Prezo</th><th>Estado</th>";
| |
| − | while($row = $result->fetch_assoc()) {
| |
| − | echo '<tr>';
| |
| − | printf("<td>%d</td><td><img alt='%s' src='%s' width='42' height='42' /></td><td>%.2f</td><td>%d</td>",
| |
| − | htmlentities($row["id_exemplar"]),htmlentities($row["imaxe"]),
| |
| − | CARTAFOL_IMAXES_WEB . htmlentities($row["imaxe"]),htmlentities($row["prezo"]),htmlentities($row["estado_id"]));
| |
| − | echo '</tr>';
| |
| − | }
| |
| − | echo '</table>';
| |
| − | $result->free(); // Liberamos da memoria os recursos
| |
| − | }
| |
| − | else {
| |
| − | echo "Non hai datos que amosar!!!!";
| |
| − | }
| |
| − | echo "</div>";
| |
| − | }
| |
| − | // $conex->close(); // Non fai falla pechar a conexión xa que esta se pecha ao final do páxina web. Noutras páxinas pode ser necesario
| |
| − |
| |
| − | ?>
| |
| | | | |
| − | </body> | + | $sql = 'INSERT INTO autores (nome, apelidos) |
| − | </html>
| + | VALUES (:nome, :apelidos)'; |
| | | | |
| − | <?php
| + | $stmt = $conex->prepare($sql); |
| − | } // Pecha o try da parte superior da páxina
| + | $stmt->execute(['nome' => 'Benito', 'apelidos' => 'Pérez Galdós']); |
| − | catch (mysqli_sql_exception $e) {
| + | $stmt->execute(['nome' => 'Julio', 'apelidos' => 'Verne']); |
| − | die("Erro mysql: " . $e->getMessage());
| |
| − | }
| |
| − | catch (Expcetion $e) {
| |
| − | die("Erro xeral: " . $e->getMessage()); | |
| − | }
| |
| − | finally{
| |
| − | if(!empty($conex)){
| |
| − | $conex->close();
| |
| − | }
| |
| − | }
| |
| − | ?>
| |
| − | </syntaxhighlight>
| |
| | | | |
| | + | echo '<br />Datos engadidos'; |
| | | | |
| | + | } catch (PDOException $e) { |
| | + | die("Error PDO: " . $e->getMessage()); |
| | + | } catch (Throwable $e) { |
| | + | die("Error general: " . $e->getMessage()); |
| | + | } |
| | | | |
| | + | $conex = null; // Poderíamos non poñer nada |
| | | | |
| − | <br /> | + | </syntaxhighlight> |
| − | * '''<u>Exercicio proposto:</u>''' Cando aparecen os exemplares do libro seleccionado, fai que apareza enriba da táboa, dentro dun <h3&mt; o nome do libro seleccionado.
| |
| | | | |
| | | | |
| | | | |
| | + | <br /> |
| | + | ==== Asociar explicitamente os parámetros ==== |
| | | | |
| | + | * Tamén podemos asociar os valores aos parámetros antes de executar a sentenza empregando, por exemplo, o método `bindValue()`. |
| | | | |
| | + | ::<syntaxhighlight lang="php" enclose="div"> |
| | + | $sql = 'SELECT nome, apelido |
| | + | FROM alumnos |
| | + | WHERE idade >= :idade'; |
| | | | |
| − | <br />
| + | $stmt = $conex->prepare($sql); |
| − | * Imos realizar <u>unha mellora</u> nos datos que se visualizan.
| |
| − | : Como vemos, aparece o código do estado do libro. O que queremos amosar é a descrición, polo que teremos que facer un Join coa táboa ESTADOS.
| |
| | | | |
| − | '''Arquivo bd.inc.php''':
| + | $stmt->bindValue(':idade', 18, PDO::PARAM_INT); |
| − | :::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | define ('CONSULTAR_EXEMPLARES_POR_LIBRO','select libro_id,id_exemplar,imaxe,prezo,estado_id,estados.descripcion as desc_estado'
| |
| − | . ' from exemplares INNER JOIN estados ON (estados.id=exemplares.estado_id)' // Atención aos espazos en branco no from. Se non os pos quedaría mal a sentenza. Lembra facer un echo da sentenza para ver que todo estea ben
| |
| − | . ' where libro_id=%d order by desc_estado'); // Necesita un espazo en branco antes do where para concatenar có anterior
| |
| − | </syntaxhighlight>
| |
| | | | |
| | + | $stmt->execute(); </syntaxhighlight> |
| | | | |
| | + | : Neste exemplo indicamos explicitamente que o valor asociado ao parámetro `:idade` é de tipo enteiro mediante: |
| | | | |
| − | <br />
| + | ::<syntaxhighlight lang="php" enclose="div"> |
| − | '''Arquivo: consultar_exemplares_por_libro.php''':
| + | PDO::PARAM_INT </syntaxhighlight> |
| − | :::<syntaxhighlight lang="java" line enclose="div" highlight="14" >
| |
| − | ..................
| |
| | | | |
| − | <?php
| + | : Para datos de tipo cadea podemos empregar: |
| − | // CONSULTAMOS OS EXEMPLARES DUN LIBRO SELECCIONADO NA COMBO
| |
| − | if (!empty($_POST['lstLibros']) && filter_var($_POST['lstLibros'],FILTER_VALIDATE_INT) && $_POST['lstLibros']!=-1){
| |
| − | echo "<div style='margin-bottom:40px'>";
| |
| − | $query=sprintf(CONSULTAR_EXEMPLARES_POR_LIBRO,$conex->real_escape_string($_POST['lstLibros']));
| |
| − | $result=$conex->query($query);
| |
| − | if ($result && ($result->num_rows>0)){
| |
| − | echo "<table border='1'>";
| |
| − | echo "<th>Num. Exemplar</th><th>Imaxe</th><th>Prezo</th><th>Estado</th>";
| |
| − | while($row = $result->fetch_assoc()) {
| |
| − | echo '<tr>';
| |
| − | printf("<td>%d</td><td><img alt='%s' src='%s' width='42' height='42' /></td><td>%.2f</td><td>%s</td>",
| |
| − | htmlentities($row["id_exemplar"]),htmlentities($row["imaxe"]),CARTAFOL_IMAXES_WEB . htmlentities($row["imaxe"]),
| |
| − | htmlentities($row["prezo"]),htmlentities($row["desc_estado"]));
| |
| − | echo '</tr>';
| |
| − | }
| |
| − | echo '</table>';
| |
| − | $result->free(); // Liberamos da memoria os recursos
| |
| − | }
| |
| − | else {
| |
| − | echo "Non hai datos que amosar!!!!";
| |
| − | }
| |
| − | echo "</div>";
| |
| − | }
| |
| − |
| |
| − | ?>
| |
| | | | |
| − | ..................
| + | ::<syntaxhighlight lang="php" enclose="div"> |
| | + | PDO::PARAM_STR </syntaxhighlight> |
| | | | |
| − | </syntaxhighlight>
| + | : Nos exemplos máis sinxelos tamén podemos enviar directamente os valores ao método `execute()`: |
| − | | |
| − | * Liña 14: Modificamos o tipo de parámetro que pasa de ser numérico (%d) a cadea (%s) e amosamos a descrición do estado (as dúas columnas do select non deben ter o mesmo nome, descrición do libro e descrición do estado).
| |
| | | | |
| | + | ::<syntaxhighlight lang="php" enclose="div"> |
| | + | $stmt->execute([ |
| | + | 'idade' => 18 |
| | + | ]); </syntaxhighlight> |
| | | | |
| | | | |
| − | * Resultado: | + | * '''IMPORTANTE:''' Se enviamos os parámetros directamente mediante o array de execute(), PDO trátaos por defecto como cadeas (PDO::PARAM_STR). O xestor de base de datos poderá realizar as conversións de tipo necesarias segundo o contexto da consulta e o tipo das columnas. |
| | + | : Se queremos indicar explicitamente o tipo dun parámetro, podemos empregar bindValue() ou bindParam() xunto cunha constante como PDO::PARAM_INT ou PDO::PARAM_STR. |
| | + | : Pode darse o caso de que teñamos unha columna na base de datos definida como numérica o o valor enviado sexa cadea e o interprete de forma erronéa (por exemplo con Limit de SQL que espara un número) |
| | | | |
| − | [[Imagen:Php_BD_12.jpg|400px|center]]
| |
| | | | |
| | | | |
| Línea 1484: |
Línea 1290: |
| | | | |
| | <br /> | | <br /> |
| − | '''EXERCICIO PROPOSTO''': | + | * '''Exercicio Proposto:''' Empregando esta forma de paso de parámetros (indicando o tipo) agrega dos novos libros tendo en conta que a columna id é autonumérica e non tes que enviar o dato. |
| | | | |
| − | : Crea unha páxina de nome '''consultar_exemplares_por_estado.php''' no que aparezan en forma de radiobutton os estados da táboa ESTADOS.
| |
| − | : Ao seleccionar un estado, amosaranse en forma de lista ordenada os exemplares (libro_id,num_exemplar,prezo) ordenados por id (quen queira pode facer un join coa táboa LIBROS e amosar o título en vez do id e ordenar os resultados polo título).
| |
| − | : O radiobutton debe manterse seleccionado.
| |
| | | | |
| | | | |
| Línea 1494: |
Línea 1297: |
| | | | |
| | | | |
| | + | <br /> |
| | | | |
| | + | === MySQL === |
| | | | |
| − | <br />
| + | '''NOTA:''' O seguinte apartado explica como crear sentenzas preparadas directamente desde MySQL. Inclúese a nivel informativo. Nas operacións realizadas desde PHP nos exames empregaremos '''PDO'''. |
| − | '''POSIBLE SOLUCIÓN''': | |
| | | | |
| − | '''Arquivo bd.inc.php'''
| + | : Máis información: |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" > | + | : [https://dev.mysql.com/doc/refman/8.4/en/sql-prepared-statements.html Prepared Statements en MySQL] |
| − | define ('CONSULTAR_ESTADOS','select id,descripcion from estados order by descripcion');
| |
| − | define ('CONSULTAR_EXEMPLARES_POR_ESTADO','select titulo,id_exemplar,prezo '
| |
| − | . ' from exemplares INNER JOIN libros ON (libros.id = exemplares.libro_id)'
| |
| − | . ' INNER JOIN estados ON (estados.id = exemplares.estado_id)'
| |
| − | . ' where estado_id=%d order by titulo');
| |
| | | | |
| − | </syntaxhighlight>
| + | * Desde o propio xestor MySQL tamén podemos crear e executar sentenzas preparadas mediante: |
| | | | |
| − | : Definimos dúas consultas. Unha para buscar os estados e outra para buscar os exemplares que teñan un estado determinado (pasado como parámetros) | + | :* `PREPARE` |
| | + | :* `EXECUTE` |
| | + | :* `DEALLOCATE PREPARE` |
| | | | |
| | + | * Esta funcionalidade pode resultar útil, por exemplo, para construír SQL dinámico dentro dun '''procedemento almacenado'''. |
| | | | |
| − | '''Arquivo consultar_exemplares_por_estado.php'''
| + | : As sentenzas `PREPARE`, `EXECUTE` e `DEALLOCATE PREPARE` poden empregarse dentro de procedementos almacenados, pero non dentro de funcións almacenadas ou triggers. |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" > | |
| − | <?php
| |
| − | /* Lembrar quitar isto ao probar o scripts sen erros */
| |
| − | ini_set('display_errors', 1);
| |
| − | ini_set('display_startup_errors', 1);
| |
| − | error_reporting(E_ALL);
| |
| | | | |
| − | require ('/var/www/confBD/bd.inc.php');
| + | ==== Exemplo de SQL dinámico ==== |
| | | | |
| − | try {
| + | * Supoñamos que queremos buscar autores empregando dous criterios opcionais: |
| − | $conex = new mysqli(SERVIDOR, USUARIO, PASSWORD,BD); // Indico o nome da bd
| |
| − | $conex->set_charset('utf8mb4');
| |
| − | ?>
| |
| | | | |
| − | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
| + | :* Apelido. |
| − | "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
| + | :* Nome. |
| − | <html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es">
| |
| − | <head>
| |
| − | <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
| |
| − | <title>Operacións BD</title>
| |
| − | </head>
| |
| | | | |
| − | <body>
| + | : Dependendo dos datos recibidos, a condición podería ser: |
| − | <body>
| |
| − | <form action="<?php echo htmlentities($_SERVER['PHP_SELF']) ?>" method='post'>
| |
| | | | |
| − | <?php
| + | ::<syntaxhighlight lang="sql" enclose="div"> |
| − | $cont=0; // Conta o número de estados impresos e cando leve múltiplo de dous salta de liña
| + | WHERE apelido = ? </syntaxhighlight> |
| − | $result=$conex->query(CONSULTAR_ESTADOS);
| |
| − | if ($result && $result->num_rows>0){
| |
| − | while($row = $result->fetch_assoc()) {
| |
| − | $cont++;
| |
| − | if(isset($_POST['rbgrpEstado']) && $row['id']==$_POST['rbgrpEstado']){ // Se volve a cargar a páxina e ven cun estado, o seleccionamos
| |
| − | $cadea="checked='checked'";
| |
| − | }
| |
| − | else{
| |
| − | $cadea='';
| |
| − | }
| |
| − | printf("<input onclick='javascript:this.form.submit()' type='radio' name='rbgrpEstado' value='%d' %s>%s</input>",
| |
| − | $row["id"],$cadea,$row["descripcion"]);
| |
| − | if(($cont%2)==0){ // Cada par cambiamos de liña
| |
| − | echo "<br />";
| |
| − | }
| |
| − | }
| |
| − | $result->free(); // Liberamos da memoria os recursos
| |
| − | }
| |
| − | else {
| |
| − | echo "Non hai estados que amosar!!!!";
| |
| − | }
| |
| − | ?>
| |
| − | </form>
| |
| − |
| |
| − |
| |
| − | <?php
| |
| − | // CONSULTAMOS OS EXEMPLARES DUN ESTADO SELECCIONADO
| |
| − | if (!empty($_POST['rbgrpEstado']) && filter_var($_POST['rbgrpEstado'],FILTER_VALIDATE_INT)){
| |
| − |
| |
| − | $query=sprintf(CONSULTAR_EXEMPLARES_POR_ESTADO,$conex->real_escape_string($_POST['rbgrpEstado']));
| |
| − | $result=$conex->query($query);
| |
| − | if ($result && ($result->num_rows>0)){
| |
| − | echo "<ol>";
| |
| − | while($row = $result->fetch_assoc()) {
| |
| − | echo '<li>';
| |
| − | printf("Título:%s - Num_exemplar:%d - Prezo:%s",$row["titulo"],$row["id_exemplar"],$row["prezo"]);
| |
| − | echo '</li>';
| |
| − | }
| |
| − | echo '</ol>';
| |
| − | $result->free(); // Liberamos da memoria os recursos
| |
| − | }
| |
| − | else {
| |
| − | echo "Non hai datos que amosar!!!!";
| |
| − | }
| |
| − |
| |
| − | }
| |
| − |
| |
| − | ?>
| |
| − |
| |
| − |
| |
| − | </body>
| |
| − | </html> | |
| | | | |
| | + | : ou: |
| | | | |
| − | <?php | + | ::<syntaxhighlight lang="sql" enclose="div"> |
| − | } // Pecha o try da parte superior da páxina
| + | WHERE nome = ? </syntaxhighlight> |
| − | catch (mysqli_sql_exception $e) {
| |
| − | die("Erro mysql: " . $e->getMessage());
| |
| − | }
| |
| − | catch (Expcetion $e) {
| |
| − | die("Erro xeral: " . $e->getMessage());
| |
| − | }
| |
| − | finally{
| |
| − | if(!empty($conex)){
| |
| − | $conex->close();
| |
| − | }
| |
| − | }
| |
| − | ?> | |
| − | </syntaxhighlight> | |
| | | | |
| | + | : ou: |
| | | | |
| | + | ::<syntaxhighlight lang="sql" enclose="div"> |
| | + | WHERE apelido = ? |
| | + | AND nome = ? </syntaxhighlight> |
| | | | |
| | + | : Se non se indica ningún criterio, seleccionaranse todas as filas. |
| | | | |
| − | * Resultado: | + | * Podemos construír dinamicamente a sentenza mantendo os valores separados da instrución SQL: |
| | | | |
| − | [[Imagen:Php_BD_13.jpg|400px|center]]
| + | ::<syntaxhighlight lang="sql" enclose="div"> |
| | + | DELIMITER $$ |
| | | | |
| | + | CREATE PROCEDURE proba( |
| | + | IN _apelido VARCHAR(100), |
| | + | IN _nome VARCHAR(50) |
| | + | ) |
| | + | BEGIN |
| | | | |
| | + | ``` |
| | + | SET @cadea = 'SELECT nome, apelido |
| | + | FROM AUTORES |
| | + | WHERE 1 = 1'; |
| | | | |
| | + | -- Gardamos os valores en variables de usuario, |
| | + | -- xa que EXECUTE ... USING traballa con este tipo de variables |
| | + | SET @p_apelido = _apelido; |
| | + | SET @p_nome = _nome; |
| | | | |
| − | <br />
| + | IF _apelido IS NOT NULL THEN |
| − | === Consulta enviando parámetros de tipo cadea === | + | SET @cadea = CONCAT( |
| | + | @cadea, |
| | + | ' AND apelido = ?' |
| | + | ); |
| | + | END IF; |
| | | | |
| − | * Cando utilicemos datos de tipo cadea, debemos lembrar poñelos entre comillas simples ('dato') na orde SQL.
| + | IF _nome IS NOT NULL THEN |
| | + | SET @cadea = CONCAT( |
| | + | @cadea, |
| | + | ' AND nome = ?' |
| | + | ); |
| | + | END IF; |
| | | | |
| | + | PREPARE stmt FROM @cadea; |
| | | | |
| − | <u>Nota:</u> Lembra que no paso de datos temos que ter en conta os posibles 'ataques' que poidan intentar facerse. Repasar o dado [http://wiki.cifprodolfoucha.es/index.php?title=PHP_Paso_de_datos_con_formularios#Seguridade_no_uso_dos_formularios na wiki anteriormente].
| + | IF _apelido IS NOT NULL AND _nome IS NOT NULL THEN |
| | | | |
| | + | EXECUTE stmt USING @p_apelido, @p_nome; |
| | | | |
| − | * Cando facemos unha consulta ou operación contra base de datos (alta, baixa e modificación) imos ter que 'enviar' datos.
| + | ELSEIF _apelido IS NOT NULL THEN |
| − | : A forma de facelo é utilizando a consulta SQL cun parámetro e substituíndo dito parámetro polo valor que queiramos enviar.
| |
| | | | |
| − | * Así: SELECT col1, col2 FROM TABOA_1 WHERE col2=''''%d''''
| + | EXECUTE stmt USING @p_apelido; |
| | | | |
| − | : No exemplo, col2 é de tipo cadea (por exemplo, varchar ou char).
| + | ELSEIF _nome IS NOT NULL THEN |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | define ('CONSULTA_CON_PARAM_CADEA',"SELECT col1, col2 FROM TABOA_1 WHERE col2='%s'");
| |
| | | | |
| − | $query = sprintf(CONSULTA_CON_PARAM_CADEA,'valor_cadea_a_enviar');
| + | EXECUTE stmt USING @p_nome; |
| − | </syntaxhighlight>
| |
| − | : Fixarse como o parámetro col2 vai entrecomillado e o formato do parámetro é '%s'.
| |
| | | | |
| | + | ELSE |
| | | | |
| − | : Agora facemos o mesmo código que no exemplo anterior.
| + | EXECUTE stmt; |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | ......
| |
| − | $result=$conex->query(CONSULTA_CON_PARAM_CADEA);
| |
| − | ......
| |
| − | </syntaxhighlight>
| |
| | | | |
| | + | END IF; |
| | | | |
| | + | DEALLOCATE PREPARE stmt; |
| | + | ``` |
| | | | |
| | + | END$$ |
| | | | |
| − | <br /> | + | DELIMITER ; </syntaxhighlight> |
| − | ==== Caso práctico: Enviando parámetros de tipo cadea ====
| |
| | | | |
| − | * Imos a aplicalo ao noso exemplo e imos crear unha páxina no que buscaremos libros en base a un nome do autor. | + | * Neste exemplo: |
| − | :: A busca farase utilizando a [http://www.w3schools.com/sql/sql_like.asp orde SQL '''like'''], que busca patróns de cadea. Este operador non distingue maiúsculas de minúsculas. | |
| − | :: Se queremos distinguir maiúsculas de minúsculas utilizaremos o operador [http://stackoverflow.com/questions/14007450/how-do-you-force-mysql-like-to-be-case-sensitive like binary].
| |
| − | :: O operador like o imos aplicar ao nome completo do autor, polo que teremos que concatenar o nome e apelido. Usaremos a función [http://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_concat-ws concat_ws] ou [http://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_concat concat].
| |
| | | | |
| | + | :* `@cadea` almacena a sentenza SQL que se vai construíndo. |
| | + | :* Os valores de `_apelido` e `_nome` '''non se concatenan directamente dentro da instrución SQL'''. |
| | + | :* Cada valor que se necesita engádese mediante un marcador `?`. |
| | + | :* `PREPARE` prepara a sentenza. |
| | + | :* `EXECUTE ... USING` asocia os valores aos marcadores e executa a sentenza. |
| | + | :* `DEALLOCATE PREPARE` libera a sentenza preparada cando xa non se necesita. |
| | | | |
| − | * O usuario introducirá nunha caixa de texto o nome (ou parte del) do autor.
| + | : '''IMPORTANTE:''' É necesario ter en conta os espazos en branco ao construír dinamicamente a sentenza mediante `CONCAT()`. Por exemplo: |
| | | | |
| − | : <u>Nota:</u> Lembra que no paso de datos temos que ter en conta os posibles 'ataques' que poidan intentar facerse. Repasar o dado [http://wiki.cifprodolfoucha.es/index.php?title=PHP_Paso_de_datos_con_formularios#Seguridade_no_uso_dos_formularios na wiki anteriormente]. | + | ::<syntaxhighlight lang="sql" enclose="div"> |
| | + | WHERE 1 = 1 AND apelido = ? </syntaxhighlight> |
| | | | |
| − | :: Mysql incorpora tamén unha función para codificar os caracteres (como espazos en branco, saltos de liña,...[http://php.net/manual/es/mysqli.real-escape-string.php Función string mysqli::real_escape_string ( string $escapestr )]. Devolve a cadea 'codificada'. | + | : debe conter un espazo entre `1 = 1` e `AND`. |
| − | :: Por exemplo:
| |
| − | :::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | $cidade = "'s Hertogenbosch";
| |
| | | | |
| − | /* esta consulta fallará debido a que no escapa $ciudad */
| |
| − | if (!$mysqli->query("SELECT nome FROM CIDADES where nome_cidade = '$cidade'")) {
| |
| − | printf("Erro: %s\n", $mysqli->sqlstate);
| |
| − | }
| |
| | | | |
| − | $cidade = $mysqli->real_escape_string($cidade);
| |
| | | | |
| − | /* esta consulta con $ciudad escapada funcionará */ | + | <br /> |
| − | if (!$mysqli->query("SELECT nome FROM CIDADES where nome_cidade = '$cidade'")) {
| |
| − | printf("Fila inxerida.\n");
| |
| − | }
| |
| | | | |
| − | </syntaxhighlight>
| + | == Operacións contra a base de datos == |
| − | ::Exemplo obtido de php.net
| |
| | | | |
| | + | * [[PHP_UD3_OP_CONSULTA | Operacións de consulta]] |
| | + | * [[PHP_UD3_OP_ALTA | Operacións de alta]] |
| | + | * [[PHP_UD3_OP_BAIXA | Operacións de baixa]] |
| | + | * [[PHP_UD3_OP_MODIFICAR | Operacións de modificación]] |
| | + | * [[PHP_UD3_EXERCICIOS_PROPOSTOS | Exercicios propostos]] |
| | | | |
| − | * Pode suceder que un mesmo autor teña varios libros. Como imos a amosar os datos dentro dun grid no que na primeira columna vai ser o nome do autor e a segunda o nome do libro, vai repetirse o nome do autor como se amosa na seguinte imaxe: | + | <br /> |
| | + | * [[PHP_UD3_TRANSACCIONS | Transaccións]] |
| | | | |
| − | [[Imagen:Php_BD_10.jpg|500px|center]]
| |
| | | | |
| − | :: Para evitalo, teremos que gardar en cada iteración unha vez 'impresa' a liña do autor, o nome do autor e comparalo cando carguemos a seguinte fila de datos. Se o nome do autor da nova liña de datos é igual ao anterior, entón non o debemos de amosar.
| + | <br /> |
| | | | |
| − | :: O código sería algo parecido a isto:
| + | == Permisos mínimos == |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | $autor=''; // A utilizamos para que non se repita o autor na táboa cando hai varios libros do mesmo autor
| |
| − | $last_autor=''; // A utilizamos para que non se repita o autor na táboa cando hai varios libros do mesmo autor
| |
| − | while($row = $result->fetch_assoc()) { // PROCESAMOS AS FILAS DO RESULTADO DA CONSULTA
| |
| − | if ($last_autor==$row["nome_autor"]){ // Comprobamos se o novo autor é igual ao anterior procesado
| |
| − | $autor=''; // Se é igual entón quere dicir que estamos a ler unha fila co mesmo autor que a anterior. Polo tanto non 'imprimimos o autor'
| |
| − | }
| |
| − | else {
| |
| − | $autor=$row["nome_autor"]; // En caso contrario quere dicir que o autor actual é diferente ao anterior e polo tanto debemos sacalo por pantalla.
| |
| − | }
| |
| | | | |
| − | printf(AMOSAMOS OS DATOS DO LIBRO E DA VARIABLE $autor);
| + | * Sempre debemos ter en mente a seguridade de acceso aos datos gardados nunha base de datos. |
| | | | |
| − | $last_autor=$row["nome_autor"]; // Gardamos o nome do autor para comparar có autor que veña na seguinte fila de datos.
| + | : Ata o de agora, o usuario que usamos para acceder á base de datos tiña asinado todos os permisos sobre a mesma, pero esta práctica non é aconsellable. |
| − | }
| + | : O ideal é que teña asinado os permisos mínimos necesarios para que poida utilizar a aplicación. |
| | | | |
| − | </syntaxhighlight>
| |
| | | | |
| | + | * Cando creamos unha aplicación WEB podemos ter varios supostos no que se refire ao acceso á base de datos. |
| | + | :* Temos creado en MYSQL un usuario diferente por cada usuario, de tal forma que a cadea de conexión á base de datos a crearemos en base ao usuario/password introducido dende unha páxina PHP. |
| | + | :* Utilizamos o mesmo usuario MYSQL para acceder á base de datos e temos unha táboa de usuario/password. En función do usuario validado, este terá acceso a diferentes páxinas PHP con diferentes funcionalidades. |
| | + | :* Utilizamos o mesmo usuario MYSQL para acceder á base de datos e todos os usuarios do sitio web poden ter acceso ás mesmas páxinas. |
| | | | |
| | + | * En calquera destes supostos, o usuario que empreguemos para acceder a MYSQL debe ter os permisos mínimos para poder funcionar no noso sitio web. |
| | | | |
| − | * Imos facer que cada libro sexa un enlace para abrir unha páxina nova de nome '''detalle_libro.php'''. No resultado da busca soamente imos amosar o nome do libro, de tal forma que cando o usuario preme sobre o enlace, apareza unha nova xanela cos datos completos dese libro, incluíndo os exemplares).
| |
| | | | |
| − | :: Para facelo imos facer uso de javascript e da [http://www.w3schools.com/jsref/met_win_open.asp función window.open].
| |
| − | :: Crearei unha función en javascript que espere recibir un parámetro, que sexa a url a abrir e chamarei a window.open cunha serie de modificacións, como o tamaño da xanela, que non poida modificar a url,...
| |
| − | :: O código será o seguinte:
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="4-8" >
| |
| − | <head>
| |
| − | <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
| |
| − | <title>Operacións BD</title>
| |
| − | <script type="text/javascript">
| |
| − | function abrirVentana($url){
| |
| − | window.open($url,'_blank','channelmode=0,status=0;directories=0,location=0,menubar=0,titlebar=0,toolbar=0,width=600,height=400');
| |
| − | }
| |
| − | </script>
| |
| − |
| |
| − | </head>
| |
| − | </syntaxhighlight>
| |
| | | | |
| | | | |
| − | :: Para chamar a esta función usaremos a etiqueta <a> e o evento onclick, coma o seguinte exemplo:
| + | * Partimos de que temos creado un usuario en Mysql, que vai ser o usuario que imos utilizar no noso sitio web para conectarnos a MYSQL e queremos darlle os permisos mínimos. |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="4-8" >
| |
| − | <a href='#' onclick="javascript:abrirVentana('detalle_libro.php?libro=1');">Título Libro</a>
| |
| − | </syntaxhighlight>
| |
| | | | |
| − | :: <u>Hai que ter moito coidado cando estamos a traballar con PHP-Javascript polo tema das comillas xa que teremos que 'xogar' coas simples e dobres para que se pechen correctamente</u>.
| |
| | | | |
| | + | * '''CASO 1:''' A nosa aplicación fai as operacións contra a base de datos empregando sentenzas SQL (SELECT/INSERT/UPDATE/DELETE) deberemos outorgar os permisos e so os permisos necesarios para facer as operacións sobre as táboas da nosa base de datos. |
| | | | |
| | + | :* Para facelo deberemos facer uso da [https://dev.mysql.com/doc/refman/5.5/en/grant.html orde grant] e [https://dev.mysql.com/doc/refman/5.5/en/revoke.html orde revoke]. |
| | + | :: A primeira (grant) serve para outorgar un permiso a un usuario. |
| | + | :: A segunda (revoke) serve para quitar un permiso a un usuario. |
| | | | |
| | + | :* No caso de SELECT e UPDATE, podemos outorgar permisos a nivel de columnas, de tal forma que podemos especificar que columnas dunha táboa vai ter permiso para poder ser seleccionadas ou actualizadas. |
| | | | |
| | | | |
| − | <br />
| + | : Se estamos a utilizar o WorkBench, podemos asociar os permisos graficamente. |
| − | * '''SOLUCIÓN'''
| |
| | | | |
| − | '''Arquivo: bd.inc.php'''
| + | [[Imagen:Php_Perm_Minimos_1.jpg|600px|center]] |
| | | | |
| − | <u>Nota:</u> Definimos a consulta a realizar á base de datos ('''lembrar que é mellor facer dita consulta directamente no Mysql e despois copiala ao código PHP pasando os parámetros correspondentes''')
| + | ::* Prememos na sección 'User and privileges' (1). |
| − | ::<syntaxhighlight lang="java" enclose="div" highlight="15-19" > | + | ::* Prememos o botón 'Add entry' e escollemos o schema (base de datos) sobre a que queremos outorgar os permisos (2). |
| − | <?php
| + | ::* Aparecerá o schema na lista (3)^. |
| | + | ::* Agora na parte baixa aparecerán os permisos que podemos outorgar. Fixarse que os marcados con (4) son os permisos de SELECT/INSERT/UPDATE/DELETE |
| | | | |
| − | define ('CONSULTAR_LIBROS_POR_AUTOR',"select CONCAT(apelidos,',',nome) as nome_autor,libros.id ,titulo from libros " .
| + | :* Se eliximos este 'tipo de permisos' estaremos dando o permiso sobre todas as táboas creadas na base de datos. |
| − | ' INNER JOIN libro_autor ON (libro_autor.libro_id=libros.id) ' .
| |
| − | ' INNER JOIN autores ON (libro_autor.autor_id=autores.id)' .
| |
| − | " where CONCAT(autores.nome,' ',autores.apelidos) like '%%%s%%' " .
| |
| − | 'order by nome_autor,titulo');
| |
| | | | |
| − | : <u>Nota:</u> Fixarse como o parámetro que recibe 'like' é de tipo cadea e polo tanto vai entre comillas e leva dobre % para que escriba o % non o interprete como un parámetro do printf.
| |
| − | : É dicir, nos o que queremos é que execute a sentenza: like '%palabraabuscar%' pero esa 'palabra a buscar' vai ser %s (un parámetro cadea a empregar en printf). Entón non podemos poñer %%s% xa que printf interpretará eses % como parámetros a enviar. Temos que decirlle que o % inicial e final non debe interpretalos como parámetro e iso se fai poñenedo %%.
| |
| | | | |
| | + | : Se estamos a utilizar a '''ferramenta Phpmyadmin''' poderemos facer o mesmo <u>seleccionado a base de datos</u> e indo a opción '''Privilegios'''. Aparecerá o usuario creado e poderemos editar os seus permisos. |
| | + | [[Imagen:Drupal_instalacion_mysql_7.jpg|600px|center]] |
| | | | |
| − | '''Arquivo buscar_libros_por_autor.php'''
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | <?php
| |
| − | /* Lembrar quitar isto ao probar o scripts sen erros */
| |
| − | ini_set('display_errors', 1);
| |
| − | ini_set('display_startup_errors', 1);
| |
| − | error_reporting(E_ALL);
| |
| − |
| |
| − | require ('/var/www/confBD/bd.inc.php');
| |
| | | | |
| − | try {
| |
| − | $conex = new mysqli(SERVIDOR, USUARIO, PASSWORD,BD);
| |
| − | $conex->set_charset('utf8mb4');
| |
| | | | |
| − | $erro=""; // Variable que a usaremos para amosar os posibles erros controlados por nos que poidan darse.
| |
| − | ?>
| |
| | | | |
| − | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
| + | :* Se queremos dar un permiso individual a unha táboa usaremos a orde '''grant'''. <u>Esta será a opción a escoller se queremos otorgar un <b>permisos mínimos</b> a nosa base de datos</u>: |
| − | "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
| + | ::<u>Nota:</u> A orde grant, cando fai referencia a un obxecto (coma unha táboa) pode indicarse o nome da base de datos e o nome do obxecto desta forma: bd.obxecto |
| − | <html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es"> | + | ::: Nos exemplos seguintes omitimos o nome da base de datos e supoñemos que xa está seleccionada (activa) a base de datos sobre a que imos a executar as ordes. |
| − | <head>
| |
| − | <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
| |
| − | <title>Operacións BD</title>
| |
| − | <script type="text/javascript">
| |
| − | function abrirVentana($url){
| |
| − | window.open($url,'_blank','channelmode=0,status=0;directories=0,location=0,menubar=0,titlebar=0,toolbar=0,width=600,height=400');
| |
| − | }
| |
| − | </script>
| |
| − |
| |
| − | </head>
| |
| | | | |
| − | <body>
| + | :::Permiso 'select' sobre unha táboa. |
| − | <form action="<?php echo htmlentities($_SERVER['PHP_SELF']) ?>" method='post'>
| + | :::<syntaxhighlight lang="java" line enclose="div" highlight="" > |
| − | Introduce o nome do autor a buscar:
| + | grant select on TABOA to 'novo_usuario'; |
| − | <input type='text' size='50' maxlength="100" required name="txtAutor" value='<?php echo (isset($_POST['txtAutor']) ? htmlentities($_POST["txtAutor"]):""); ?>'/>
| + | </syntaxhighlight> |
| − | <input type="submit" value="BUSCAR" />
| |
| − | <input type='button' onclick="javascript:this.form['txtAutor'].value='';" value="LIMPAR" />
| |
| − | <?php
| |
| − | if (!empty($_POST['txtAutor'])){ // Premimos o botón de busca
| |
| − | $textoBuscar = filter_var($_POST['txtAutor'],FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_FLAG_NO_ENCODE_QUOTES); // Eliminamos da cadea de busca todos os caracteres non permitidos
| |
| − | if (strlen($textoBuscar)<3){ // É un exemplo de control de erros. Indico que o texto a buscar ten que ter a lo menos 3 caracteres
| |
| − | $erro="<h2>A cadea a buscar non pode ser menor de 3 caracteres</h2>";
| |
| − | }
| |
| − | else {
| |
| − | $query=sprintf(CONSULTAR_LIBROS_POR_AUTOR,$conex->real_escape_string($textoBuscar));
| |
| − | $result=$conex->query($query);
| |
| − | if ($result->num_rows>0){
| |
| − | $autor=''; // A utilizamos para que non se repita o autor na táboa cando hai varios libros do mesmo autor
| |
| − | $last_autor=''; // A utilizamos para que non se repita o autor na táboa cando hai varios libros do mesmo autor
| |
| − | echo "<table border='1'>";
| |
| − | echo "<th>Autor</th><th>Libro</th>";
| |
| − | while($row = $result->fetch_assoc()) {
| |
| − | if ($last_autor==$row["nome_autor"]){
| |
| − | $autor=''; // O autor é o mesmo e non queremos que se repita na táboa
| |
| − | }
| |
| − | else {
| |
| − | $autor=htmlentities($row["nome_autor"]);
| |
| − | }
| |
| − | echo '<tr>';
| |
| − | $onclick=sprintf("javascript:abrirVentana(\"detalle_libro.php?libro=%d\");",htmlentities($row["id"]));
| |
| − | printf("<td>%s</td><td><a href='#' onclick='%s'>%s</a></td>",$autor,$onclick,htmlentities($row['titulo']));
| |
| − | echo '</tr>';
| |
| − | $last_autor=$row["nome_autor"];
| |
| − | }
| |
| − | $result->free();
| |
| − | echo "</table>";
| |
| − | }
| |
| − | else {
| |
| − | $erro = "<br />Non hai resultados coa cadea $textoBuscar";
| |
| − | }
| |
| − |
| |
| − |
| |
| − | }
| |
| − | }
| |
| − |
| |
| − | ?>
| |
| − | </form>
| |
| − | <?php
| |
| − | if ($erro!='') {
| |
| − | printf("<h3>%s</h3>",$erro);
| |
| − | }
| |
| − | ?>
| |
| − |
| |
| − |
| |
| − | </body>
| |
| − | </html> | |
| | | | |
| − | <?php | + | :::Permiso 'select' sobre unha columna dunha táboa. |
| − | } // Pecha o try da parte superior da páxina
| + | :::<syntaxhighlight lang="java" line enclose="div" highlight="" > |
| − | catch (mysqli_sql_exception $e) {
| + | grant select (columna) on TABOA to 'novo_usuario'; |
| − | die("Erro mysql: " . $e->getMessage());
| + | </syntaxhighlight> |
| − | }
| |
| − | catch (Expcetion $e) {
| |
| − | die("Erro xeral: " . $e->getMessage());
| |
| − | }
| |
| − | finally{
| |
| − | if(!empty($conex)){
| |
| − | $conex->close();
| |
| − | }
| |
| − | }
| |
| − | ?>
| |
| − | </syntaxhighlight> | |
| | | | |
| − | * Fixarse que nesta páxina non necesitamos conectar ao principio, xa que soamente se carga a caixa de texto para que o usuario introduza o nome do autor a buscar.
| + | :::Permiso 'update' sobre unha táboa. |
| − | :: Polo tanto a conexión e o peche da base de datos se podería poñer dentro do else da liña 43. | + | :::<syntaxhighlight lang="java" line enclose="div" highlight="" > |
| − | * Fixarse que o botón de limpar non é de tipo 'reset' xa que ao cargar o que veña en $_POST o botón tipo reset volvería a amosar dito valor.
| + | grant update on TABOA to 'novo_usuario'; |
| | + | </syntaxhighlight> |
| | | | |
| | + | :::Permiso 'update' sobre unha columna dunha táboa. |
| | + | :::<syntaxhighlight lang="java" line enclose="div" highlight="" > |
| | + | grant update (columna) on TABOA to 'novo_usuario'; |
| | + | </syntaxhighlight> |
| | | | |
| − | * '''Resultado:'''
| + | :: Para os permisos DELETE e INSERT soamente é necesario poñer na orde grant a operación igual que nos casos anteriores. |
| − | [[Imagen:Php_BD_11.jpg|500px|center]]
| |
| | | | |
| | | | |
| Línea 1879: |
Línea 1515: |
| | | | |
| | | | |
| | + | * '''CASO 2:''' A nosa aplicación fai as operacións contra a base de datos empregando <u>procedementos almacenados</u>. |
| | | | |
| − | <br />
| + | :* No caso anterior, aínda que melloramos a seguridade, o usuario aínda ten acceso ás táboas directamente e pode ver a estrutura das mesmas. |
| − | === Consulta chamando a procedementos almacenados ===
| + | :: Se queremos aumentar a seguridade podemos utilizar procedementos almacenados. |
| | | | |
| − | * Un procedemento almacenado ven a ser o equivalente a unha función en PHP, coa diferenza que as ordes que conforman o procedemento van permitir realizar accións (inserción, borrado, modificación e selección) sobre a base de datos.
| |
| | | | |
| − | * Como comentamos [http://wiki.cifprodolfoucha.es/index.php?title=PHP_Paso_de_datos_con_formularios#Soluci.C3.B3n_a_inxecci.C3.B3n_de_CSS anteriormente nesta wiki], unha das mellores formas de evitar ataques de tipo 'inxección de CSS' é utilizar procedementos almacenados. | + | :* Xa vimos anteriormente que empregar ditos procedementos leva consigo: |
| | + | ::* Mellor rendemento xa que as sentenzas xa están compiladas e analizadas polo xestor. |
| | + | ::* Mellor seguridade xa que é imposible facer ataques de inxección de código. |
| | + | ::* Mellor seguridade xa que o usuario non pode ver a estrutura de táboas. |
| | + | ::* Mantemento máis doado, xa que podo cambiar a estrutura de táboas sempre que o procedemento continúe devolvendo o mesmo ao usuario. |
| | | | |
| − | : Tamén é unha forma de aumentar a seguridade de acceso á base de datos Mysql, xa que teremos que dar permiso de execución sobre o procedemento almacenado e o usuario non vai poder acceder ás táboas da base de datos nin examinar a estrutura das mesmas.
| |
| − | : Desta forma podemos cambiar a estrutura da táboa (como por exemplo cambiar o nome dunha columna) e simplemente cambiando a sentenza SQL que está dentro do procedemento almacenado e poñendo un alias có nome antigo, a aplicación no cliente segue funcionando.
| |
| − | : Outra vantaxe é que as sentencias SQL non están definidas nas páxinas PHP e polo tanto se realizamos un cambio o faremos no Mysql sendo 'transparente' cara ao usuario.
| |
| | | | |
| | + | * <u>IMPORTANTE</u>: Se utilizamos este tipo de 'seguridade' xa non necesitamos outorgar ningún tipo de permiso sobre as táboas. |
| | + | |
| | + | :* O permiso que debemos outorgar para que poida executar un procedemento almacenado é 'EXECUTE'. |
| | + | ::* Podemos facelo como no punto anterior facendo uso do WORKBENCH (neste caso daremos permiso de execución sobre todos os procedementos almacenados). |
| | + | ::* Usar a orde '''grant''': |
| | | | |
| − | * Máis información [http://php.net/manual/es/mysqli.quickstart.stored-procedures.php neste enlace]. | + | ::: Dando permiso de execución a todos os procedementos almacenados. |
| | + | :::<syntaxhighlight lang="java" line enclose="div" highlight="" > |
| | + | grant execute on *.* to 'novo_usuario' |
| | + | </syntaxhighlight> |
| | | | |
| − | * O primeiro que temos que facer é crear o procedemento almacenado.
| + | ::: Dando permiso de execución a un procedemento almacenado. |
| − | * A orde SQL é [http://dev.mysql.com/doc/refman/5.7/en/create-procedure.html '''CREATE PROCEDURE'''].
| + | :::<syntaxhighlight lang="java" line enclose="div" highlight="" > |
| | + | grant execute on procedure NOME_PROCEDEMENTO to 'novo_usuario' |
| | + | </syntaxhighlight> |
| | | | |
| − | * Creamos o procedemento almacenado:
| + | == Gardando contrasinais de forma segura == |
| − | :* Cando definimos un procedemento almacenado debemos facer uso da palabra DELIMITER para indicarlle ao Mysql onde acaba a definición do procedemento almacenado. Máis información [http://dev.mysql.com/doc/refman/5.7/en/stored-programs-defining.html neste enlace].
| |
| | | | |
| − | '''Creamos un procedemento almacenado dentro do MysqlWorkBench''' | + | * En moitas aplicacións necesitamos gardar na base de datos información relacionada coa autenticación dos usuarios, como por exemplo os seus '''contrasinais'''. |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | DELIMITER $$
| |
| | | | |
| − | CREATE PROCEDURE `NOME_PROCEDEMENTO` ()
| + | * Os contrasinais '''nunca deben gardarse directamente na base de datos en texto plano'''. |
| − | BEGIN
| |
| | | | |
| − | select col1,col2
| + | : Se un atacante consegue acceder á base de datos podería coñecer inmediatamente os contrasinais de todos os usuarios. |
| − | from TABOA_1
| |
| − | order by col1;
| |
| | | | |
| | + | * Tampouco debemos empregar algoritmos de hash xenéricos como `MD5`, `SHA1` ou `SHA256` directamente para almacenar contrasinais. |
| | | | |
| − | END
| + | : Estes algoritmos están deseñados para calcular hashes de forma moi rápida, característica que facilita que un atacante poida realizar grandes cantidades de intentos mediante ataques de forza bruta. |
| − | $$
| |
| − | </syntaxhighlight>
| |
| | | | |
| − | : Como vemos o procedemento está formado por unha consulta SELECT (pode poñerse calquera) | + | * Para gardar contrasinais PHP proporciona funcións específicas que empregan algoritmos deseñados para este propósito: |
| | | | |
| | + | :* [https://www.php.net/manual/es/function.password-hash.php password_hash()] |
| | + | :* [https://www.php.net/manual/es/function.password-verify.php password_verify()] |
| | + | :* [https://www.php.net/manual/es/function.password-needs-rehash.php password_needs_rehash()] |
| | | | |
| − | * Agora dende PHP temos que facer uso do comando CALL 'NOME_PROCEDEMENTO'.
| + | === Recomendacións para os contrasinais === |
| − | : Isto é o que teriamos que poñer se dende Mysql queredes chamar ao procedemento....
| |
| | | | |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="12" >
| + | * Actualmente considérase que a '''lonxitude''' do contrasinal é un dos factores máis importantes para aumentar a súa seguridade. |
| − | <?php
| |
| − | /* Lembrar quitar isto ao probar o script sen erros */
| |
| − | ini_set('display_errors', 1);
| |
| − | ini_set('display_startup_errors', 1);
| |
| − | error_reporting(E_ALL);
| |
| | | | |
| − | require ('/var/www/confBD/bd.inc.php'); // Onde están definidas as constantes que se empregan na conexión (feito nas seccións anteriores)
| + | * Como recomendación xeral: |
| | | | |
| − | try {
| + | :* Se o contrasinal é o único factor de autenticación, debería ter como mínimo '''15 caracteres'''. |
| − | $conex = new mysqli(SERVIDOR, USUARIO, PASSWORD,BD);
| + | :* Se se emprega como parte dun sistema de autenticación multifactor (MFA), poderá admitirse unha lonxitude mínima de '''8 caracteres'''. |
| − | $conex->set_charset('utf8mb4');
| + | :* Débense permitir contrasinais longos ou '''frases de paso (passphrases)'''. |
| − | $proc = sprintf("CALL %s",NOME_PROCEDEMENTO);
| + | :* Recoméndase permitir unha lonxitude máxima de polo menos '''64 caracteres'''. |
| | + | :* Non é recomendable obrigar ao usuario a incluír necesariamente maiúsculas, minúsculas, números e caracteres especiais. |
| | + | :* Débese evitar o emprego de contrasinais moi coñecidos, previsibles ou que apareceran en filtracións de datos. |
| | | | |
| − | }
| + | : Por exemplo, unha frase de paso longa pode ser máis fácil de lembrar e proporcionar maior seguridade que un contrasinal curto construído unicamente para cumprir regras de complexidade. |
| − | catch (mysqli_sql_exception $e) {
| + | |
| − | die("Erro mysql: " . $e->getMessage());
| + | : Máis información: |
| − | }
| + | :* [https://pages.nist.gov/800-63-4/sp800-63b.html NIST SP 800-63B] |
| − | catch (Expcetion $e) {
| + | :* [https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html OWASP - Authentication Cheat Sheet] |
| − | die("Erro xeral: " . $e->getMessage());
| + | |
| − | }
| + | === Función password_hash() === |
| − | finally{
| |
| − | if(!empty($conex)){
| |
| − | $conex->close();
| |
| − | }
| |
| − | }
| |
| | | | |
| | + | * Para gardar un contrasinal na base de datos empregaremos a función: |
| | | | |
| − | </syntaxhighlight> | + | ::<syntaxhighlight lang="php" enclose="div"> |
| | + | password_hash() </syntaxhighlight> |
| | | | |
| | + | * Por exemplo: |
| | | | |
| | + | ::<syntaxhighlight lang="php" line enclose="div"> |
| | | | |
| − | * A diferenza do caso anterior, cando chamemos a un procedemento almacenado teremos que facer uso dunha das seguintes funcións:
| + | <?php |
| − | :: [http://php.net/manual/es/mysqli.real-query.php mysqli::real_query] ou [http://php.net/manual/es/mysqli.multi-query.php mysqli::multi_query]
| |
| | | | |
| − | : Nos imos facer uso da chamada a '''multi_query''' xa que tamén a imos poder utilizar cando dentro dun mesmo procedemento almacenado teñamos varios select´s e queiramos recuperar o contido de todos eles (cada un será un conxunto de resultados).
| + | $password = 'contrasinal_introducido_polo_usuario'; |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="3,4" >
| |
| − | ........................
| |
| − | $proc = sprintf("CALL %s",NOME_PROCEDEMENTO);
| |
| − | if ($conex->multi_query($proc)){
| |
| − | $result = $conex->store_result();
| |
| − | if ($result && ($result->num_rows>0)){
| |
| − | ................
| |
| − | }
| |
| | | | |
| − | }
| + | $hash = password_hash( |
| | + | $password, |
| | + | PASSWORD_DEFAULT |
| | + | ); |
| | | | |
| − | </syntaxhighlight>
| + | echo $hash; |
| | | | |
| | + | ?> |
| | | | |
| − | : Unha vez temos o conxunto de resultados ($result) xa operamos como nos casos anteriores.
| + | </syntaxhighlight> |
| | | | |
| | + | * A constante: |
| | | | |
| − | *<u>IMPORTANTE</u> Se estamos a utilizar procedementos almacenados, pode darse o caso de que despois de chamar a un procedemento que devolva <u>un conxunto de resultados</u>, ao intentar chamar a outro procedemento vos dea un erro '2014 - Commands out of sync; you can't run this command now' ou simplemente a chamada ao seguinte procedemento non vos devolva resultados.
| + | ::<syntaxhighlight lang="php" enclose="div"> |
| − | : A resposta a ese erro a tedes [https://dev.mysql.com/doc/refman/8.0/en/commands-out-of-sync.html neste enlace].
| + | PASSWORD_DEFAULT </syntaxhighlight> |
| | | | |
| | + | : indica que PHP empregará o algoritmo considerado predeterminado para almacenar contrasinais. |
| | | | |
| | + | : En PHP 8.5, `PASSWORD_DEFAULT` emprega actualmente '''bcrypt'''. Non obstante, esta constante está deseñada para que PHP poida cambiar o algoritmo empregado en futuras versións se aparece unha alternativa máis adecuada. |
| | | | |
| − | * Ao parecer, existen aínda conxuntos de resultados sen 'recoller' (aínda que o procedemento soamente devolva un) e non vos deixará realizar outra chamada ata que leades todos os conxuntos de resultados.
| + | : Por este motivo, a lonxitude do hash podería cambiar no futuro e recoméndase gardalo nun campo suficientemente grande da base de datos, por exemplo: |
| − | : Para facelo, podedes engadir o seguinte código:
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="9" > | |
| − | ........................
| |
| − | $proc = sprintf("CALL %s",NOME_PROCEDEMENTO);
| |
| − | if ($conex->multi_query($proc)){
| |
| − | $result = $conex->store_result();
| |
| − | if ($result && ($result->num_rows>0)){
| |
| − | ................
| |
| − | // Procesamos cada fila
| |
| − | }
| |
| − | while($conex->next_result() && $conex->store_result());
| |
| | | | |
| − | }
| + | ::<syntaxhighlight lang="sql" enclose="div"> |
| | + | password VARCHAR(255) </syntaxhighlight> |
| | | | |
| − | </syntaxhighlight>
| + | === O salt === |
| | | | |
| | + | * Os algoritmos de almacenamento de contrasinais empregan un valor aleatorio denominado '''salt'''. |
| | | | |
| | + | : O salt fai, entre outras cousas, que dous usuarios que teñan o mesmo contrasinal obteñan normalmente hashes diferentes. |
| | | | |
| | + | : Tamén dificulta o emprego de táboas precalculadas de hashes, como as chamadas '''Rainbow Tables'''. |
| | | | |
| − | * Como comentei anteriormente, un procedemento pode estar conformado por múltiples 'select'. | + | * '''Non debemos crear nin proporcionar manualmente o salt'''. |
| − | : Se queremos 'pasar' ao seguinte 'conxunto de resultados' temos que chamar á [http://php.net/manual/es/mysqli.next-result.php función mysqli::next_result] desta forma:
| |
| | | | |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="8-10" > | + | : A función `password_hash()` xera automaticamente un salt seguro e diferente para cada contrasinal. |
| − | ........................
| |
| − | $proc = sprintf("CALL %s",NOME_PROCEDEMENTO);
| |
| − | if ($conex->multi_query($proc)){
| |
| − | $result = $conex->store_result();
| |
| − | if ($result && ($result->num_rows>0)){
| |
| − | ................
| |
| − | }
| |
| − | if ($conex->next_result()){
| |
| − | $result=$conex->store_result();
| |
| − | }
| |
| | | | |
| − | }
| + | ::<syntaxhighlight lang="php" enclose="div"> |
| | + | $hash = password_hash( |
| | + | $password, |
| | + | PASSWORD_DEFAULT |
| | + | ); </syntaxhighlight> |
| | | | |
| − | </syntaxhighlight>
| + | : O algoritmo empregado, os seus parámetros e o salt necesario para comprobar posteriormente o contrasinal están incluídos dentro do propio hash devolto por `password_hash()`. |
| | | | |
| − | : Se imos procesar todos os conxuntos de resultados da mesma forma poderíamos utilizar un while{}... | + | : Polo tanto, '''non é necesario gardar o salt nun campo separado da base de datos'''. |
| | | | |
| | + | : '''IMPORTANTE:''' En versións antigas de PHP era posible proporcionar manualmente un salt a `password_hash()`. Esta opción está obsoleta desde PHP 7 e foi eliminada en PHP 8.0. Non debe empregarse. |
| | | | |
| | + | === Gardar o contrasinal na base de datos === |
| | | | |
| | + | * Supoñamos que recibimos o contrasinal desde un formulario: |
| | | | |
| − | <br /> | + | ::<syntaxhighlight lang="php" enclose="div"> |
| − | ==== Caso Práctico: Consulta chamando a procedementos almacenados ==== | + | $password = $_POST['password']; </syntaxhighlight> |
| | | | |
| − | * Vexamos un exemplo. Imos copiar o código da páxina '''Consultar_Libros.php''' e imos crear unha nova de nome '''Consultar_Libros_Proc''' co mesmo código, facendo as modificacións que se indican a continuación para que faga uso da chamada a un procedemento almacenado. | + | * Primeiro calcularemos o hash: |
| | | | |
| | + | ::<syntaxhighlight lang="php" enclose="div"> |
| | + | $hash = password_hash( |
| | + | $password, |
| | + | PASSWORD_DEFAULT |
| | + | ); </syntaxhighlight> |
| | | | |
| − | * Creamos o procedemento almacenado: | + | * O valor que gardaremos na base de datos será '''$hash''' e nunca o contrasinal orixinal. |
| − | '''Creamos un procedemento almacenado dentro do MysqlWorkBench''' | |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | DELIMITER $$
| |
| | | | |
| − | CREATE PROCEDURE `CONSULTAR_LIBROS` ()
| + | * Por exemplo, empregando PDO e unha sentenza preparada: |
| − | BEGIN
| |
| | | | |
| − | select ìd,titulo
| + | ::<syntaxhighlight lang="php" line enclose="div"> |
| − | from libros
| + | $sql = 'INSERT INTO usuarios (nome, password) |
| − | order by titulo;
| + | VALUES (:nome, :password)'; |
| | | | |
| | + | $stmt = $conex->prepare($sql); |
| | | | |
| − | END
| + | $hash = password_hash( |
| − | $$ | + | $_POST['password'], |
| − | </syntaxhighlight>
| + | PASSWORD_DEFAULT |
| | + | ); |
| | | | |
| | + | $stmt->execute([ |
| | + | 'nome' => $_POST['nome'], |
| | + | 'password' => $hash |
| | + | ]); </syntaxhighlight> |
| | | | |
| | + | === Comprobar un contrasinal === |
| | | | |
| − | * Definimos nunha constante o nome do procedemento almacenado dentro do arquivo bd.inc.php. | + | * Cando un usuario inicia sesión non se calcula manualmente o hash do contrasinal para comparalo co gardado. |
| | | | |
| − | :'''Arquivo: bd.inc.php''' | + | * Para comprobar se o contrasinal introducido corresponde co hash almacenado na base de datos empregaremos: |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | define('PROCEDEMENTO_CONSULTAR_LIBROS','CONSULTAR_LIBROS()'); // Se levara parámetros faríamos o mesmo que nos casos anteriores coas sentencias select, empregando %s, %d...
| |
| − | </syntaxhighlight>
| |
| | | | |
| | + | ::<syntaxhighlight lang="php" enclose="div"> |
| | + | password_verify() </syntaxhighlight> |
| | | | |
| | + | * Por exemplo: |
| | | | |
| − | * Debemos cambiar a chamada a 'query' pola chamada [http://php.net/manual/es/mysqli.real-query.php mysqli::real_query] ou [http://php.net/manual/es/mysqli.multi-query.php mysqli::multi_query]
| + | ::<syntaxhighlight lang="php" line enclose="div"> |
| − | :: Nos imos facer uso da chamada a '''multi_query''' xa que tamén a imos poder utilizar cando dentro dun mesmo procedemento almacenado teñamos varios select´s e queiramos recuperar o contido de todos eles (cada un será un conxunto de resultados).
| |
| | | | |
| − | :'''Arquivo: consultar_libros_proc.php'''
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="24-26" >
| |
| | <?php | | <?php |
| − | /* Lembrar quitar isto ao probar o scripts sen erros */
| |
| − | ini_set('display_errors', 1);
| |
| − | ini_set('display_startup_errors', 1);
| |
| − | error_reporting(E_ALL);
| |
| | | | |
| − | require ('/var/www/confBD/bd.inc.php');
| + | $passwordIntroducido = $_POST['password']; |
| | | | |
| − | try {
| + | // Este valor obteríase previamente da base de datos |
| − | $conex = new mysqli(SERVIDOR, USUARIO, PASSWORD,BD);
| + | $hashGardado = $usuario['password']; |
| − | $conex->set_charset('utf8mb4');
| |
| | | | |
| − | ?>
| + | if (password_verify($passwordIntroducido, $hashGardado)) { |
| − | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
| |
| − | "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
| |
| − | <html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es">
| |
| − | <head>
| |
| − | <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
| |
| − | <title>Operacións BD</title>
| |
| − | </head>
| |
| − | <body>
| |
| − | <form action="<?php echo htmlentities($_SERVER['PHP_SELF']) ?>" method='post'>
| |
| − | <?php
| |
| − | $proc = sprintf("CALL %s",PROCEDEMENTO_CONSULTAR_LIBROS);
| |
| − | if ($conex->multi_query($proc)){
| |
| − | $result = $conex->store_result();
| |
| − | if ($result && ($result->num_rows>0)){
| |
| − | echo "<select name='lstLibros' onchange='this.form.submit()'>";
| |
| − | echo "<option value='-1'></option>";
| |
| − | while($row = $result->fetch_assoc()) {
| |
| − | if(isset($_POST['lstLibros']) && $row['id']==$_POST['lstLibros']){
| |
| − | $cadea="selected='selected'";
| |
| − | }
| |
| − | else{
| |
| − | $cadea='';
| |
| − | }
| |
| − | printf("<option value='%d' %s>%s</option>",$row["id"],$cadea,$row["titulo"]);
| |
| − | }
| |
| − | echo '</select>';
| |
| − | $result->free(); // Liberamos da memoria os recursos
| |
| − | }
| |
| − | else {
| |
| − | echo "Non hai datos que amosar...";
| |
| − | }
| |
| − |
| |
| − | }
| |
| − | else {
| |
| − | echo "Houbo un erro na chamado ao procedemento:" . $conex->error; // A mensaxe do erro ($conex->error) non a teremos que amosar coa aplicación funcionando
| |
| − | }
| |
| − | ?>
| |
| − |
| |
| − |
| |
| − | </body>
| |
| − | </html>
| |
| − | <?php
| |
| − | } // Pecha o try da parte superior da páxina
| |
| − | catch (mysqli_sql_exception $e) {
| |
| − | die("Erro mysql: " . $e->getMessage());
| |
| − | }
| |
| − | catch (Expcetion $e) {
| |
| − | die("Erro xeral: " . $e->getMessage());
| |
| − | }
| |
| − | finally{
| |
| − | if(!empty($conex)){
| |
| − | $conex->close();
| |
| − | }
| |
| − | }
| |
| − | ?>
| |
| − | </syntaxhighlight>
| |
| | | | |
| − | :* Liña 24: Creamos a cadea que vai chamar ao procedemento almacenado: CALL CONSULTAR_LIBROS()
| + | echo 'Contrasinal correcto'; |
| − | :* Liña 25: Modificamos a liña e agora chamamos ao procedemento mysqli::multi_query(nome_proc). Dita chamada devolve false en caso de erro.
| |
| − | :* Liña 26: Gardamos o resultado (o primeiro select do procedemento almacenado) no obxecto $result e xa operamos igual que no caso anterior cando usamos as sentencias 'select' directamente.
| |
| | | | |
| | + | } else { |
| | | | |
| | + | echo 'Contrasinal incorrecto'; |
| | | | |
| − | <br />
| + | } |
| − | === Consulta chamando a procedementos almacenados con parámetros de entrada ===
| |
| | | | |
| − | * Podemos chamar a un procedemento almacenado enviando datos ao mesmo en forma de parámetros.
| + | ?> |
| − | : Indicar que en case todos os xestores os parámetros poden ser de 3 tipos: '''de entrada''', '''de saída''' e de '''entrada/saída'''.
| |
| − | ::* Os <u>parámetros de saída</u> (OUT) son variables que van poder ser modificadas no interior do procedemento e cuxo valor vai poder ser lido dentro do código da páxina php.
| |
| − | ::* Os <u>parámetros de entrada</u> (IN) son variables que van poder ser utilizadas dentro do procedemento (chegan en forma de parámetros) pero as modificacións que se fagan no seu valor non chegan á paxina PHP.
| |
| − | ::* Os <u>parámetros de entrada/entrada</u> (INOUT) son unha combinación dos dous anteriores.
| |
| | | | |
| − | : Máis información [http://www.mysqltutorial.org/stored-procedures-parameters.aspx neste enlace].
| + | </syntaxhighlight> |
| | | | |
| − | * Por defecto, se non especificamos nada, todos os parámetros dun procedemento almacenado son considerados 'de entrada' | + | * `password_verify()` recibe: |
| | | | |
| − | * Primeiro teremos que definir o procedemento en Mysql:
| + | :# O contrasinal introducido polo usuario. |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" > | + | :# O hash que temos gardado na base de datos. |
| − | DELIMITER $$
| |
| | | | |
| − | CREATE PROCEDURE `PROCEDEMENTO_CON_PARAMETROS`(IN _param1 varchar(100))
| + | : A función devolve `true` se o contrasinal se corresponde co hash almacenado ou `false` en caso contrario. |
| − | BEGIN
| |
| | | | |
| − | SELECT col1,col2
| + | : Toda a información necesaria para realizar a comprobación, incluído o algoritmo, os seus parámetros e o salt, está incluída no propio hash. |
| − | FROM TABOA_1
| |
| − | WHERE col2=_param1;
| |
| | | | |
| − | END
| + | === Actualización do hash === |
| − | </syntaxhighlight>
| |
| | | | |
| − | : Como vemos, xunto ao nome do procedemento hai que especificar o tipo (dentro dos tipos de mysql).
| + | * Os algoritmos ou os seus parámetros poden cambiar co paso do tempo. |
| − | : Unha vez definidos podemos utilizalos dentro do procedemento almacenado.
| |
| − | : Calquera modificación do valor do parámetro non se verá reflectidos no código PHP. Se no código PHP 'recuperamos' o valor do parámetro, este terá o mesmo valor que cando enviamos o dato ao procedemento.
| |
| | | | |
| | + | : PHP proporciona a función: |
| | | | |
| − | : Podemos definir máis parámetros, separando por comas cada un deles, por exemplo:
| + | ::<syntaxhighlight lang="php" enclose="div"> |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="3,8,9" > | + | password_needs_rehash() </syntaxhighlight> |
| − | DELIMITER $$
| |
| | | | |
| − | CREATE PROCEDURE `PROCEDEMENTO_CON_PARAMETROS`(IN _param1 varchar(100),_param2 int)
| + | : que permite comprobar se un hash almacenado debería volver calcularse utilizando a configuración actual. |
| − | BEGIN
| |
| | | | |
| − | SELECT col1,col2
| + | * Podemos facer esta comprobación despois de validar correctamente o contrasinal: |
| − | FROM TABOA_1
| |
| − | WHERE col2=_param1 and
| |
| − | col1=_param2
| |
| | | | |
| − | END
| + | ::<syntaxhighlight lang="php" line enclose="div"> |
| − | </syntaxhighlight> | + | if (password_verify($passwordIntroducido, $hashGardado)) { |
| | | | |
| | + | ``` |
| | + | // O contrasinal é correcto |
| | | | |
| − | * Unha vez definido teremos que enviar os datos que queiramos dende PHP desta forma:
| + | if (password_needs_rehash( |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| + | $hashGardado, |
| − | define('CHAMAR_PROCEDEMENTO_CON_PARAMETROS','PROCEDEMENTO_CON_PARAMETROS(%d,"%s")');
| + | PASSWORD_DEFAULT |
| − | </syntaxhighlight>
| + | )) { |
| − | : Fixarse como no caso de datos de tipo numérico non enviamos o dato con comiñas.
| |
| − | : Fixarse como 'xogamos' coas comiñas simples de dobres para pechar correctamente a cadea.
| |
| | | | |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="5,6" >
| + | $novoHash = password_hash( |
| − | define('CHAMAR_PROCEDEMENTO_CON_PARAMETROS','PROCEDEMENTO_CON_PARAMETROS(%d,"%s")');
| + | $passwordIntroducido, |
| | + | PASSWORD_DEFAULT |
| | + | ); |
| | | | |
| − | ........................
| + | // Actualizar $novoHash na base de datos |
| | + | } |
| | | | |
| − | $proc = sprintf(CHAMAR_PROCEDEMENTO_CON_PARAMETROS,valor_numérico,'valor_cadea');
| + | // Continuar co proceso de autenticación |
| − | $proc = sprintf("CALL %s",$proc);
| + | ``` |
| − | if ($conex->multi_query($proc)){
| |
| − | $result = $conex->store_result();
| |
| − | if ($result && ($result->num_rows>0)){
| |
| − | ................
| |
| − | }
| |
| − | }
| |
| | | | |
| − | </syntaxhighlight> | + | } </syntaxhighlight> |
| | | | |
| − | : Fixarse que ao final o que temos que conseguir é esta cadea: CALL PROCEDEMENTO_CON_PARAMETROS(5,'valor') // 5 e valor son datos de exemplo | + | : Desta forma podemos actualizar progresivamente os hashes almacenados cando cambie o algoritmo predeterminado de PHP ou os seus parámetros. |
| − | : Isto o podemos conseguir de diferentes formas. Eu vos amoso unha delas.
| |
| | | | |
| | + | === Hash e cifrado non son o mesmo === |
| | | | |
| | + | * Un '''hash''' é unha transformación deseñada para non poder obter novamente o valor orixinal. |
| | | | |
| | + | : Por este motivo é adecuada para almacenar contrasinais: non necesitamos coñecer o contrasinal orixinal, unicamente comprobar se o introducido polo usuario é correcto. |
| | | | |
| − | <br />
| + | * Se necesitamos gardar un dato confidencial pero posteriormente temos que recuperar o seu valor orixinal, '''non debemos empregar `password_hash()`'''. |
| − | ==== Caso Práctico: Consulta chamando a procedementos almacenados con parámetros de entrada ====
| |
| | | | |
| − | * Seguindo có noso exemplo...imos crear unha nova páxina de nome '''buscar_libros_por_autor_proc.php''' que faga uso de procedementos almacenados en vez de sentencias SQL directamente no código.
| + | : Nese caso necesitaríamos empregar técnicas de '''cifrado''' adecuadas para protexer os datos. |
| | | | |
| − | '''Solución:''' | + | '''IMPORTANTE:''' |
| − | * '''Procedemento almacenado CONSULTAR_LIBROS_POR_AUTOR en Mysql'''
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | DELIMITER $$
| |
| − | | |
| − | CREATE PROCEDURE `CONSULTAR_LIBROS_POR_AUTOR`(IN p_autor_buscar varchar(100))
| |
| − | BEGIN
| |
| | | | |
| − | select CONCAT(apelidos,',',nome) as nome_autor,libros.id as id_libro,titulo from libros
| + | :* Para almacenar contrasinais empregaremos `password_hash()`. |
| − | INNER JOIN libro_autor ON (libro_autor.libro_id=libros.id)
| + | :* Para comprobar contrasinais empregaremos `password_verify()`. |
| − | INNER JOIN autores ON (libro_autor.autor_id=autores.id)
| + | :* Non empregaremos `MD5`, `SHA1` nin hashes xenéricos para almacenar contrasinais. |
| − | where CONCAT(autores.nome,' ',autores.apelidos) like CONCAT('%', p_autor_buscar , '%')
| + | :* Non xeraremos nin gardaremos manualmente o salt. |
| − | order by nome_autor,titulo;
| + | :* Na base de datos gardaremos unicamente o hash xerado por `password_hash()`. |
| | + | :* Recoméndase utilizar un campo como `VARCHAR(255)` para almacenar o hash. |
| | | | |
| − | END$$
| + | <br /> |
| − | </syntaxhighlight> | |
| − | | |
| − | : <u>Nota:</u> Para comprobar se funciona facede primeiro unha chamada dende o propio Mysql coa orde: call CONSULTAR_LIBROS_POR_AUTOR('dru');
| |
| − | : Fixarse como o parámetro é de tipo IN (valor por defecto, podería non poñerse nada).
| |
| − | | |
| − | | |
| − | | |
| − | * Definimos a constante en PHP no que está definido o nome do procedemento e o parámetro que vai levar.
| |
| − | :'''Arquivo: bd.inc.php'''
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | define('PROCEDEMENTO_CONSULTAR_LIBROS_POR_AUTOR','CONSULTAR_LIBROS_POR_AUTOR("%s")');
| |
| − | </syntaxhighlight>
| |
| − | | |
| − | :<u>Nota:</u> Fixarse como o parámetro ao ser de tipo 'cadea' debe ir entre comillas.
| |
| − | | |
| − | | |
| − | | |
| − | * '''Arquivo buscar_libros_por_autor_proc.php'''
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="44-47" >
| |
| − | <?php
| |
| − | /* Lembrar quitar isto ao probar o scripts sen erros */
| |
| − | ini_set('display_errors', 1);
| |
| − | ini_set('display_startup_errors', 1);
| |
| − | error_reporting(E_ALL);
| |
| − | | |
| − | require ('/var/www/confBD/bd.inc.php');
| |
| − | | |
| − | try {
| |
| − | $conex = new mysqli(SERVIDOR,USUARIO,PASSWORD,BD);
| |
| − | $conex->set_charset('utf8mb4');
| |
| − | | |
| − | $erro=""; // Variable que a usaremos para amosar os posibles erros que poidan darse
| |
| | | | |
| − | ?>
| |
| − |
| |
| − | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
| |
| − | "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
| |
| − | <html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es">
| |
| − | <head>
| |
| − | <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
| |
| − | <title>Operacións BD</title>
| |
| − | <script type="text/javascript">
| |
| − | function abrirVentana($url){
| |
| − | window.open($url,'_blank','channelmode=0,status=0;directories=0,location=0,menubar=0,titlebar=0,toolbar=0,width=600,height=400');
| |
| − | }
| |
| − | </script>
| |
| − |
| |
| − | </head>
| |
| − |
| |
| − | <body>
| |
| − | <form action="<?php echo htmlentities($_SERVER['PHP_SELF']) ?>" method='post'>
| |
| − | Introduce o nome do autor a buscar:
| |
| − | <input type='text' size='50' maxlength="100" required name="txtAutor" value='<?php echo (isset($_POST['txtAutor']) ? $_POST["txtAutor"]:""); ?>'/>
| |
| − | <input type="submit" value="BUSCAR" />
| |
| − | <input type='button' onclick="javascript:this.form['txtAutor'].value='';" value="LIMPAR" />
| |
| − | <?php
| |
| − | if (!empty($_POST['txtAutor'])){ // Premimos o botón de busca
| |
| − | $textoBuscar = filter_var($_POST['txtAutor'],FILTER_SANITIZE_FULL_SPECIAL_CHARS, FILTER_FLAG_NO_ENCODE_QUOTES); // Eliminamos da cadea de busca todos os caracteres non permitidos
| |
| − | if (strlen($textoBuscar)<3){ // É un exemplo de control de erros. Indico que o texto a buscar ten que ter a lo menos 3 caracteres
| |
| − | $erro="<h2>A cadea a buscar non pode ser menor de 3 caracteres</h2>";
| |
| − | }
| |
| − | else {
| |
| − | $proc = sprintf(PROCEDEMENTO_CONSULTAR_LIBROS_POR_AUTOR,$conex->real_escape_string($textoBuscar)); // Cambiamos o texto a buscar polo parámetro definido na constante
| |
| − | $proc = sprintf("CALL %s",$proc);
| |
| − | if ($conex->multi_query($proc)){
| |
| − | $result = $conex->store_result();
| |
| − | if ($result->num_rows>0){
| |
| − | $autor=''; // A utilizamos para que non se repita o autor na táboa cando hai varios libros do mesmo autor
| |
| − | $last_autor=''; // A utilizamos para que non se repita o autor na táboa cando hai varios libros do mesmo autor
| |
| − | echo "<table border='1'>";
| |
| − | echo "<th>Autor</th><th>Libro</th>";
| |
| − | while($row = $result->fetch_assoc()) {
| |
| − | if ($last_autor==$row["nome_autor"]){
| |
| − | $autor='';
| |
| − | }
| |
| − | else {
| |
| − | $autor=$row["nome_autor"];
| |
| − | }
| |
| − | echo '<tr>';
| |
| − | $onclick=sprintf("javascript:abrirVentana(\"detalle_libro.php?libro=%d\");",htmlentities($row["id_libro"]));
| |
| − | printf("<td>%s</td><td><a href='#' onclick='%s'>%s</a></td>",$autor,$onclick,htmlentities($row['titulo']));
| |
| − | echo '</tr>';
| |
| − | $last_autor=$row["nome_autor"];
| |
| − | }
| |
| − | $result->free();
| |
| − | echo "</table>";
| |
| − | }
| |
| − | else {
| |
| − | $erro = "Non hai resultados,";
| |
| − | }
| |
| − | }
| |
| − | }
| |
| − | }
| |
| − |
| |
| − | ?>
| |
| − | </form>
| |
| − | <?php
| |
| − | if ($erro!='') {
| |
| − | printf("<h3>%s</h3>",$erro);
| |
| − | }
| |
| − | ?>
| |
| − |
| |
| − |
| |
| − | </body>
| |
| − | </html>
| |
| − |
| |
| − | <?php
| |
| − | } // Pecha o try do principio da páxina
| |
| − | catch (mysqli_sql_exception $e) {
| |
| − | die("Erro mysql: " . $e->getMessage());
| |
| − | }
| |
| − | catch (Expcetion $e) {
| |
| − | die("Erro xeral: " . $e->getMessage());
| |
| − | }
| |
| − | finally{
| |
| − | if(!empty($conex)){
| |
| − | $conex->close();
| |
| − | }
| |
| − | }
| |
| − | ?>
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − | :* Liñas 44 e 45: Creamos a cadea que vai chamar ao procedemento almacenado de Mysql: CALL CONSULTAR_LIBROS_POR_AUTOR('dru'); (é un exemplo de busca pola palabra dru)
| |
| − |
| |
| − |
| |
| − |
| |
| − |
| |
| − |
| |
| − | <br />
| |
| − | === Consulta chamando a procedementos almacenados con parámetros de saída ===
| |
| − |
| |
| − | * Agora imos aplicar o concepto de parámetros de saída, que como indicamos no punto anterior, son parámetros que van poder ser modificados o seu valor dentro do procedemento almacenado e recoller o seu valor modificado dende a parte de PHP.
| |
| − |
| |
| − | * Primeiro teremos que definir o procedemento en Mysql:
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="3,7" >
| |
| − | DELIMITER $$
| |
| − |
| |
| − | CREATE PROCEDURE `PROCEDEMENTO_CON_PARAMETROS`(OUT _param1 varchar(100))
| |
| − | BEGIN
| |
| − |
| |
| − | SELECT col2
| |
| − | INTO _param1
| |
| − | FROM TABOA_1
| |
| − | WHERE col1=1;
| |
| − |
| |
| − | END
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − | : Agora especificamos que o parámetro é de saída antepoñendo a palabra OUT ao seu nomo.
| |
| − | : Neste exemplo estamos a supoñer que a consulta devolve unha única fila e estamos gardando o resultado da consulta no parámetro de saída.
| |
| − | : Podemos modificar o seu valor dentro do procedemento coa orde: SET _param1=valor;
| |
| − |
| |
| − |
| |
| − |
| |
| − | * Unha vez definido teremos que construír a chamada ao procedemento enviando o nome dunha variable (para recoller o valor modificado).
| |
| − | : O nome da variable terá este formato: '''@'''nomeVariable:
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | define('CHAMAR_PROCEDEMENTO_CON_PARAMETROS','PROCEDEMENTO_CON_PARAMETROS("@nomeVariable")');
| |
| − | </syntaxhighlight>
| |
| − | : Fixarse como a variable é de tipo cadea e vai entre comiñas.
| |
| − |
| |
| − |
| |
| − | * Agora a chamada se fai igual que no caso anterior, pero fixarse que neste caso O PROCEDEMENTO NON DEVOLVE UN CONXUNTO DE RESULTADOS. Podería ser e nese caso obraríamos igual que no caso anterior, a maiores do que imos ver agora.
| |
| − | : Para recoller o dato da variable teremos que facer unha consulta SELECT da forma: SELECT @nomeVariable as valorDevolto
| |
| − |
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="10,14" >
| |
| − | <?php
| |
| − | define('CHAMAR_PROCEDEMENTO_CON_PARAMETROS','PROCEDEMENTO_CON_PARAMETROS("@nomeVariable")');
| |
| − |
| |
| − | ........................
| |
| − |
| |
| − | $proc = sprintf("CALL %s",CHAMAR_PROCEDEMENTO_CON_PARAMETROS);
| |
| − | if ($conex->multi_query($proc)){
| |
| − | $result = $conex->store_result();
| |
| − |
| |
| − | if (!($resultado = $conex->query("SELECT @nomeVariable as valorDevolto"))) {
| |
| − | echo "Fallou a obtención: (" . $conex->errno . ") " . $conex->error;
| |
| − | }
| |
| − | else {
| |
| − | $fila = $resultado->fetch_assoc();
| |
| − | echo $fila['valorDevolto']);
| |
| − | }
| |
| − |
| |
| − | }
| |
| − | ........................
| |
| − |
| |
| − |
| |
| − | ?>
| |
| − |
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − | : Fixarse que ao final o que temos que conseguir é esta cadea: CALL PROCEDEMENTO_CON_PARAMETROS(5,'valor') // 5 e valor son datos de exemplo
| |
| − | : Isto o podemos conseguir de diferentes formas. Eu vos amoso unha delas.
| |
| − |
| |
| − |
| |
| − |
| |
| − | : <u>Nota:</u> Non fai falla facer un alias do nome da variable. Podemos empregar o propio nome no fetch_assoc().
| |
| − | :: Podemos poñer:
| |
| − |
| |
| − | :::<syntaxhighlight lang="java" line enclose="div" highlight="5,10" >
| |
| − | $proc = sprintf("CALL %s",CHAMAR_PROCEDEMENTO_CON_PARAMETROS);
| |
| − | if ($conex->multi_query($proc)){
| |
| − | $result = $conex->store_result();
| |
| − |
| |
| − | if (!($resultado = $conex->query("SELECT @nomeVariable"))) {
| |
| − | echo "Fallou a obtención: (" . $conex->errno . ") " . $conex->error;
| |
| − | }
| |
| − | else {
| |
| − | $fila = $resultado->fetch_assoc();
| |
| − | echo $fila['@nomVariable']);
| |
| − | }
| |
| − |
| |
| − | }
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − |
| |
| − |
| |
| − |
| |
| − | <br />
| |
| − | ==== Caso práctico: Consulta chamando a procedementos almacenados con parámetros de saída ====
| |
| − |
| |
| − | * Segundo o noso exemplo, imos crear un procedemento almacenado que devolva nun parámetro de saída o número de libros que temos na nosa tenda.
| |
| − |
| |
| − |
| |
| − | * '''Procedemento almacenado CONSULTAR_NUM_LIBROS en Mysql'''
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | DELIMITER $$
| |
| − |
| |
| − | CREATE DEFINER=`root`@`localhost` PROCEDURE `CONSULTAR_NUM_LIBROS`(OUT p_num_libros int)
| |
| − | BEGIN
| |
| − | SELECT COUNT(*)
| |
| − | INTO p_num_libros
| |
| − | FROM libros;
| |
| − |
| |
| − | END
| |
| − | $$
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − |
| |
| − | : Como se pode ver leva un parámetro de saída (OUT). Indicar que podemos poñer calquera combinación de parámetros na definición dun procedemento almacenado.
| |
| − |
| |
| − |
| |
| − | * '''Arquivo bd.inc.php''':
| |
| − | * Definimos a chamada ao procedemento cunha variable no seu interior que vai ser a que garde o valor modificado de dentro do procedemento.
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | define('PROCEDEMENTO_CONSULTAR_NUM_LIBROS','CONSULTAR_NUM_LIBROS(@numero_libros)');
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − |
| |
| − |
| |
| − | * '''Arquivo consultar_num_libros.php'''
| |
| − | : Esta páxina está a modo de exemplo. Loxicamente isto podería ser utilizado dentro dunha páxina para obter o número de libros sen necesidade de crear unha páxina específica.
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | <?php
| |
| − | /* Lembrar quitar isto ao probar o scripts sen erros */
| |
| − | ini_set('display_errors', 1);
| |
| − | ini_set('display_startup_errors', 1);
| |
| − | error_reporting(E_ALL);
| |
| − |
| |
| − | require ('/var/www/confBD/bd.inc.php');
| |
| − |
| |
| − | try {
| |
| − | $conex = new mysqli(SERVIDOR, USUARIO, PASSWORD,BD);
| |
| − | $conex->set_charset('utf8mb4');
| |
| − |
| |
| − | $erro=""; // Variable que a usaremos para amosar os posibles erros controlados por nos que poidan darse.
| |
| − | ?>
| |
| − | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
| |
| − | "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
| |
| − | <html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es">
| |
| − | <head>
| |
| − | <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
| |
| − | <title>Operacións BD</title>
| |
| − |
| |
| − | </head>
| |
| − |
| |
| − | <body>
| |
| − |
| |
| − | <?php
| |
| − | $proc= sprintf("CALL %s",PROCEDEMENTO_CONSULTAR_NUM_LIBROS);
| |
| − | if ($conex->multi_query($proc)){
| |
| − | $result = $conex->store_result();
| |
| − | if (!($resultado = $conex->query("SELECT @numero_libros as _p_out"))) {
| |
| − | echo "Fallou a obtención: (" . $conex->errno . ") " . $conex->error;
| |
| − | }
| |
| − | else {
| |
| − | $fila = $resultado->fetch_assoc();
| |
| − | printf("<h1>NUMERO DE LIBROS A VENDA:%d",$fila['_p_out']);
| |
| − | }
| |
| − | }
| |
| − | ?>
| |
| − |
| |
| − |
| |
| − | </body>
| |
| − | </html>
| |
| − | <?php
| |
| − | } // Pecha o try da parte superior da páxina
| |
| − | catch (mysqli_sql_exception $e) {
| |
| − | die("Erro mysql: " . $e->getMessage());
| |
| − | }
| |
| − | catch (Expcetion $e) {
| |
| − | die("Erro xeral: " . $e->getMessage());
| |
| − | }
| |
| − | finally{
| |
| − | if(!empty($conex)){
| |
| − | $conex->close();
| |
| − | }
| |
| − | }
| |
| − | ?>
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − |
| |
| − |
| |
| − |
| |
| − | <br />
| |
| − | === Consulta chamando a funcións ===
| |
| − |
| |
| − | * Dende Mysql pódense definir funcións, que son procedementos almacenados que devolven un valor dun tipo de datos.
| |
| − |
| |
| − | : [http://dev.mysql.com/doc/refman/5.7/en/create-procedure.html Definicións de funcións dende Mysql].
| |
| − |
| |
| − |
| |
| − | * Vexamos un exemplo:
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | DELIMITER $$
| |
| − |
| |
| − | CREATE FUNCTION `EXEMPLO_FUNCION`() RETURNS int
| |
| − | BEGIN
| |
| − | declare variable int;
| |
| − |
| |
| − | SELECT col2
| |
| − | INTO variable
| |
| − | FROM TABLA_1
| |
| − | WHERE col1=1;
| |
| − |
| |
| − | return variable;
| |
| − |
| |
| − |
| |
| − | END
| |
| − | $$
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − | : Como vemos, esta función vai devolver un número de tipo integer.
| |
| − |
| |
| − |
| |
| − | * Para chamar á función dende Mysql teremos que 'engadila' dentro dunha cláusula SELECT.
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | define('FUNCION_EXEMPLO_MYSQL','select EXEMPLO_FUNCION() as valor_devolto');
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − | : Como vemos, a tratamos coma dunha cláusula SELECT calquera, dándolle un nome (alias) ao resultado da execución da función.
| |
| − |
| |
| − |
| |
| − | * Agora soamente queda obter o resultado como fixemos [http://wiki.cifprodolfoucha.es/index.php?title=PHP_Operaci%C3%B3ns_BD_Mysql#Consulta no exemplo de consulta], anteriormente visto.
| |
| − |
| |
| − |
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="19" >
| |
| − |
| |
| − | <?php
| |
| − |
| |
| − | define('FUNCION_EXEMPLO_MYSQL','select EXEMPLO_FUNCION() as valor_devolto');
| |
| − |
| |
| − | $conex = new mysqli('SERVIDOR','USUARIO','PASSWORD','BD');
| |
| − | $conex->set_charset('utf8');
| |
| − |
| |
| − | if ($conex->connect_error){
| |
| − | die('Erro de Conexión (' . mysqli_connect_errno() . ') ' . mysqli_connect_error());
| |
| − | }
| |
| − |
| |
| − | ?>
| |
| − |
| |
| − |
| |
| − | <?php
| |
| − | $result=$conex->query(FUNCION_EXEMPLO_MYSQL);
| |
| − | if ($result || ($result->num_rows>0)){
| |
| − | $row = $result->fetch_assoc();
| |
| − | printf("<h1>Dato devolto:%d</h1>",$row['valor_devolto']);
| |
| − | $result->free();
| |
| − | }
| |
| − | else {
| |
| − | echo "Fallou a chamada á función: (" . $conex->errno . ") " . $conex->error;
| |
| − | }
| |
| − |
| |
| − |
| |
| − | $conex->close(); // Pechamos a conexión
| |
| − |
| |
| − | ?>
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − | :<u>Nota:</u> Fixarse como na liña 19 a referencia que utilizamos é o alias posto no DEFINE.
| |
| − |
| |
| − |
| |
| − |
| |
| − |
| |
| − | <br />
| |
| − | ==== Caso Práctico: Consulta chamando a funcións ====
| |
| − |
| |
| − | * Imos facer un exemplo aplicado ao noso proxecto.
| |
| − | : Imos facer que o número de libros da nosa tenda sexa obtido utilizando unha función e non o procedemento almacenado con parámetro de saída da sección anterior.
| |
| − |
| |
| − | * '''Creamos a función en Mysql'''
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | DELIMITER $$
| |
| − |
| |
| − | CREATE FUNCTION `CONSULTAR_NUM_LIBROS`() RETURNS int READS SQL DATA
| |
| − | BEGIN
| |
| − | declare num_libros int;
| |
| − |
| |
| − | SELECT COUNT(*)
| |
| − | INTO num_libros
| |
| − | FROM libros;
| |
| − |
| |
| − | return num_libros;
| |
| − | END$$
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − |
| |
| − |
| |
| − | * '''Aqruivo bd.inc.php'''
| |
| − | : Definimos a constante que vai chamar á función.
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | define('FUNCION_CONSULTAR_NUM_LIBROS','select CONSULTAR_NUM_LIBROS() as num_libros');
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − | : Fixarse no alias da chamada á función.
| |
| − |
| |
| − |
| |
| − | * '''Creamos unha páxina consultar_num_libros_con_funcionMYSQL.php'''
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | <?php
| |
| − | /* Lembrar quitar isto ao probar o scripts sen erros */
| |
| − | ini_set('display_errors', 1);
| |
| − | ini_set('display_startup_errors', 1);
| |
| − | error_reporting(E_ALL);
| |
| − |
| |
| − | require ('/var/www/confBD/bd.inc.php');
| |
| − |
| |
| − | try {
| |
| − | $conex = new mysqli(SERVIDOR, USUARIO, PASSWORD,BD);
| |
| − | $conex->set_charset('utf8mb4');
| |
| − |
| |
| − | $erro=""; // Variable que a usaremos para amosar os posibles erros controlados por nos que poidan darse.
| |
| − | ?>
| |
| − | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
| |
| − | "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
| |
| − | <html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es">
| |
| − | <head>
| |
| − | <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
| |
| − | <title>Operacións BD</title>
| |
| − |
| |
| − | </head>
| |
| − |
| |
| − | <body>
| |
| − |
| |
| − | <?php
| |
| − | $result=$conex->query(FUNCION_CONSULTAR_NUM_LIBROS);
| |
| − | if ($result && ($result->num_rows>0)){
| |
| − | $row = $result->fetch_assoc();
| |
| − | printf("<h1>Número de libros:%d</h1>",$row['num_libros']);
| |
| − | $result->free();
| |
| − | }
| |
| − | else {
| |
| − | echo "Fallou a chamada á función.: (" . $conex->errno . ") " . $conex->error;
| |
| − | }
| |
| − | ?>
| |
| − |
| |
| − |
| |
| − | </body>
| |
| − | </html>
| |
| − | <?php
| |
| − | } // Pecha o try da parte superior da páxina
| |
| − | catch (mysqli_sql_exception $e) {
| |
| − | die("Erro mysql: " . $e->getMessage());
| |
| − | }
| |
| − | catch (Expcetion $e) {
| |
| − | die("Erro xeral: " . $e->getMessage());
| |
| − | }
| |
| − | finally{
| |
| − | if(!empty($conex)){
| |
| − | $conex->close();
| |
| − | }
| |
| − | }
| |
| − | ?>
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − |
| |
| − |
| |
| − |
| |
| − |
| |
| − | <br />
| |
| − | === Paxinación de resultados ===
| |
| − |
| |
| − | * En moitas ocasións imos necesitar paxinar os resultados dunha consulta.
| |
| − | : Isto é necesario cando o resultado pode devolver unha cantidade moi grande de rexistros que obrigaría ao usuario a facer demasiado scroll.
| |
| − |
| |
| − |
| |
| − | * A idea é moi simple. Utilizando a cláusula [https://dev.mysql.com/doc/refman/5.5/en/limit-optimization.html '''LIMIT''' da orde SQL Select].
| |
| − | : Dita cláusula pode levar dous parámetros separados por coma: LIMIT numero_fila,tamaño e vai ao final da orde SELECT.
| |
| − |
| |
| − | : Por exemplo:
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="4" >
| |
| − | SELECT id,titulo
| |
| − | FROM libros
| |
| − | ORDER by titulo
| |
| − | LIMIT 0,2
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − | :: Este exemplo amosaría os dous primeiros rexistros empezando polo primeiro do conxunto de resultados (rexistros 0 e 1)
| |
| − |
| |
| − | : Se modifico a orde e poño:
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="4" >
| |
| − | SELECT id,titulo
| |
| − | FROM libros
| |
| − | ORDER by titulo
| |
| − | LIMIT 2,2
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − | :: Devolverá os rexistros 2 e 3 do conxunto de resultados. Estamos paxinando os resultados de dous en dous !!!!
| |
| − |
| |
| − |
| |
| − | * Polo tanto para paxinar os datos dunha consulta terei que:
| |
| − | :* Establecer o número de rexistros por páxina. Isto será o que leve a cláusula LIMIT como segundo dato.
| |
| − | :* Gardar a páxina actual e mandar dito dato entre as diferentes peticións da páxina PHP (normalmente enlaces có número de páxina).
| |
| − | :* Para saber o número de páxinas terei que obter o número de rexistros en total, xa que o número de páxinas será igual ao número de rexistros dividido (parte enteira) entre o número de rexistros por páxina.
| |
| − | :: Para isto faremos uso da [http://php.net/manual/es/function.ceil.php función ceil] que devolve o número enteiro redondeado cara arriba.
| |
| − | :: Así, se teño 10 elementos e quero paxinar 3 elementos por páxina: ceil(10/3) = '''4''' que serán o número de páxinas.
| |
| − |
| |
| − |
| |
| − |
| |
| − |
| |
| − | <br />
| |
| − | ==== Caso Práctico: Paxinación de resultados ====
| |
| − |
| |
| − |
| |
| − | * Imos crear unha nova páxina de nome '''consultar_libros_con_paxinacion.php''' que partindo do código da páxina 'consultar_libros.php' feita previamente, amose os libros con paxinación.
| |
| − |
| |
| − | : O faremos utilizando un procedemento almacenado (de nome CONSULTAR_LIBROS_PAXINACION), que vai levar dous parámetros, o número de páxina actual e o número de libros por páxina (tamén se podería facer directamente cun select dende PHP sen utilizar procedementos almacenados, pero xa vimos que é máis seguro).
| |
| − |
| |
| − |
| |
| − | * Aplicado ao noso exemplo, imos engadir máis datos para poder facer unha paxinación.
| |
| − |
| |
| − | : '''Táboa autores'''
| |
| − |
| |
| − | ::<syntaxhighlight lang="java" enclose="div" highlight="" >
| |
| − | INSERT INTO `php`.`autores` (`apelidos`, `nome`) VALUES ('ABBOTT', 'RACHEL');
| |
| − | INSERT INTO `php`.`autores` (`apelidos`, `nome`) VALUES ('HJORTH', 'MICHAEL');
| |
| − | INSERT INTO `php`.`autores` (`apelidos`, `nome`) VALUES ('LAURENS', 'STEPHANIE');
| |
| − | INSERT INTO `php`.`autores` (`apelidos`, `nome`) VALUES ('ESTRINGANA', 'MORUENA');
| |
| − | INSERT INTO `php`.`autores` (`apelidos`, `nome`) VALUES ('NIXON', 'RITA');
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − |
| |
| − | : '''Táboa libros'''
| |
| − |
| |
| − | ::<syntaxhighlight lang="java" enclose="div" highlight="" >
| |
| − | INSERT INTO `php`.`libros` (`titulo`) VALUES ('COMO UNA EXTRAÑA');
| |
| − | INSERT INTO `php`.`libros` (`titulo`) VALUES ('CRÍMENES DUPLICADOS');
| |
| − | INSERT INTO `php`.`libros` (`titulo`) VALUES ('A SALVO CON SU CAPTOR');
| |
| − | INSERT INTO `php`.`libros` (`titulo`) VALUES ('MI ERROR FUE NO SER YO MISMA. PARTE II');
| |
| − | INSERT INTO `php`.`libros` (`titulo`) VALUES ('EL AMOR NO ES UN INVENTO DE LOS POETAS');
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − |
| |
| − | : '''Táboa libro_autor'''
| |
| − | :: Teredes que ter en conta os identificadores autonuméricos xerados nos insert´s anteriores.
| |
| − | ::<syntaxhighlight lang="java" enclose="div" highlight="" >
| |
| − | INSERT INTO `php`.`libro_autor` (`libro_id`, `autor_id`) VALUES ('3', '2');
| |
| − | INSERT INTO `php`.`libro_autor` (`libro_id`, `autor_id`) VALUES ('4', '3');
| |
| − | INSERT INTO `php`.`libro_autor` (`libro_id`, `autor_id`) VALUES ('5', '4');
| |
| − | INSERT INTO `php`.`libro_autor` (`libro_id`, `autor_id`) VALUES ('6', '5');
| |
| − | INSERT INTO `php`.`libro_autor` (`libro_id`, `autor_id`) VALUES ('7', '6');
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − |
| |
| − | : '''Táboa exemplares'''
| |
| − | :: Teredes que ter en conta os identificadores autonuméricos xerados nos insert´s da táboa libros.
| |
| − | ::<syntaxhighlight lang="java" enclose="div" highlight="" >
| |
| − | INSERT INTO `php`.`exemplares` (`libro_id`, `id_exemplar`, `prezo`, `estado_id`) VALUES ('3', '1', '9.99', '1');
| |
| − | INSERT INTO `php`.`exemplares` (`libro_id`, `id_exemplar`, `prezo`, `estado_id`) VALUES ('3', '2', '9.99', '1');
| |
| − | INSERT INTO `php`.`exemplares` (`libro_id`, `id_exemplar`, `imaxe`, `prezo`, `estado_id`) VALUES ('4', '1', '', '29.99', '1');
| |
| − | INSERT INTO `php`.`exemplares` (`libro_id`, `id_exemplar`, `prezo`, `estado_id`) VALUES ('5', '1', '23.35', '1');
| |
| − | INSERT INTO `php`.`exemplares` (`libro_id`, `id_exemplar`, `imaxe`, `prezo`, `estado_id`) VALUES ('6', '1', '', '34.76', '1');
| |
| − | INSERT INTO `php`.`exemplares` (`libro_id`, `id_exemplar`, `imaxe`, `prezo`, `estado_id`) VALUES ('7', '1', '', '22.54', '1');
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − |
| |
| − | * Creamos o procedemento almacenado en Mysql para que devolva os libros con paxinación.
| |
| − | : Poderíamos modificar o que xa tiñamos, comprobando se enviamos valores aos parámetros ou non (valor null) e facendo un select ou outro en función dos valores...
| |
| − |
| |
| − | '''Procedemento almacenado CONSULTAR_LIBROS_PAXINACION en Mysql'''
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | DELIMITER $$
| |
| − |
| |
| − | CREATE DEFINER=`root`@`localhost` PROCEDURE `CONSULTAR_LIBROS_PAXINACION`(IN p_num_rexistro int,IN p_tam_paxina INT)
| |
| − | BEGIN
| |
| − | SELECT id,titulo
| |
| − | FROM libros
| |
| − | ORDER BY titulo
| |
| − | LIMIT p_num_rexistro,p_tam_paxina;
| |
| − |
| |
| − | END$$
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − |
| |
| − |
| |
| − | * Creamos a constante que vai facer uso deste procedemento.
| |
| − | '''Arquivo: bd.inc.php'''
| |
| − | ::<syntaxhighlight lang="java" enclose="div" highlight="" >
| |
| − | define('PROCEDEMENTO_CONSULTAR_LIBROS_PAXINACION','CONSULTAR_LIBROS_PAXINACION(%d,%d)');
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − | :: Como vemos imos enviar dous parámetros.
| |
| − |
| |
| − |
| |
| − |
| |
| − |
| |
| − | * Partimos do código da páxina consultar_libros.php e <u>creamos unha nova páxina</u> de nome '''consultar_libros_con_paxinacion.php'''
| |
| − | : Nesta páxina voi definir nunha constante o número de libros por páxina. Se este dato fora compartido por outras páxinas, debería no arquivo bc.inc.php
| |
| − |
| |
| − |
| |
| − | : '''Arquivo: consultar_libros_con_paxinacion.php'''
| |
| − |
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="8,31-42,48-68,71-72" >
| |
| − | <?php
| |
| − | /* Lembrar quitar isto ao probar o scripts sen erros */
| |
| − | ini_set('display_errors', 1);
| |
| − | ini_set('display_startup_errors', 1);
| |
| − | error_reporting(E_ALL);
| |
| − |
| |
| − | require ('/var/www/confBD/bd.inc.php');
| |
| − | define('CONST_NUM_LIBROS_PAXINA',2); // Podería ir en bd.inc.php ou noutro arquivo inc.php se vai ser empregada por outras páxinas
| |
| − |
| |
| − |
| |
| − | try {
| |
| − | $conex = new mysqli(SERVIDOR,USUARIO,PASSWORD); // Non indicamos o nome da bd
| |
| − | $conex->set_charset('utf8mb4');
| |
| − | $conex->select_db(BD); // O parámetro indica o nome da base de datos
| |
| − |
| |
| − | ?>
| |
| − |
| |
| − | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
| |
| − | "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
| |
| − | <html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es">
| |
| − | <head>
| |
| − | <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
| |
| − | <title>Operacións BD</title>
| |
| − | </head>
| |
| − |
| |
| − | <body>
| |
| − | <?php
| |
| − | // OBTEMOS O NUMERO DE LIBROS
| |
| − | // Este código ven dun caso práctico anterior que devolve o número de libros
| |
| − | // facendo uso dun procedemento almacenado e dun parámetro de saída.
| |
| − | $numLibros=0;
| |
| − | $proc= sprintf("CALL %s",PROCEDEMENTO_CONSULTAR_NUM_LIBROS);
| |
| − | if ($conex->multi_query($proc)){
| |
| − | $result = $conex->store_result();
| |
| − | if (!($resultado = $conex->query("SELECT @numero_libros as _p_out"))) {
| |
| − | die ("Fallou a obtención: (" . $conex->errno . ") " . $conex->error);
| |
| − | }
| |
| − | else {
| |
| − | $fila = $resultado->fetch_assoc();
| |
| − | $numLibros = $fila['_p_out'];
| |
| − | }
| |
| − | }
| |
| − | ?>
| |
| − |
| |
| − |
| |
| − | <?php // $numLibros ten o número de libros da nosa táboa
| |
| − | // AMOSAMOS AS PAXINAS EN BASE AO NUMERO TOTAL E O NUMERO POR PAXINA
| |
| − | $total_paxinas = ceil($numLibros/CONST_NUM_LIBROS_PAXINA);
| |
| − |
| |
| − | // Establecemos cal é a páxina actual. Se o usuario elixiu algunha virá en forma de URL $_GET
| |
| − | if(!empty($_GET['paxina'])){
| |
| − | $paxina_actual=htmlentities($_GET['paxina']);
| |
| − | }
| |
| − | else{
| |
| − | $paxina_actual=1; // A primeira vez o usuario non pulsou ningunha páxina e polo tanto a páxina inicial é a 1
| |
| − | }
| |
| − |
| |
| − | // 'Imprimimos as páxinas...
| |
| − | echo "<div>";
| |
| − | for($cont=1;$cont<=$total_paxinas;$cont++){
| |
| − | if ($cont==$paxina_actual){ // A páxina actual non leva link
| |
| − | echo $cont . ' ';
| |
| − | }
| |
| − | else{
| |
| − | printf("<a href='%s?paxina=%d'>%d</a> ",htmlentities($_SERVER['PHP_SELF']),$cont,$cont);
| |
| − | }
| |
| − | }
| |
| − | echo "</div>";
| |
| − | ?>
| |
| − | <?php
| |
| − | $rexistro_actual=($paxina_actual-1)*CONST_NUM_LIBROS_PAXINA; // Restamos 1 á páxina actual xa que a páxina 1 empeza no rexistro 0
| |
| − | $proc=sprintf(PROCEDEMENTO_CONSULTAR_LIBROS_PAXINACION,$conex->real_escape_string($rexistro_actual),CONST_NUM_LIBROS_PAXINA);
| |
| − | $proc = sprintf("CALL %s",$proc);
| |
| − | if ($conex->multi_query($proc)){
| |
| − | $result = $conex->store_result();
| |
| − | if ($result->num_rows>0){
| |
| − | echo "<table border='1'>";
| |
| − | while($row = $result->fetch_assoc()) {
| |
| − | echo '<tr>';
| |
| − | printf("<td>%s</td>",$row["titulo"]);
| |
| − | echo '</tr>';
| |
| − | }
| |
| − | echo '</table>';
| |
| − | $result->free(); // Liberamos da memoria os recursos
| |
| − | }
| |
| − | else {
| |
| − | echo "Non hai datos que amosar!!!!";
| |
| − | }
| |
| − | }
| |
| − | ?>
| |
| − |
| |
| − | </body>
| |
| − | </html>
| |
| − |
| |
| − | <?php
| |
| − | } // Pecha o try do principio da páxina
| |
| − | catch (mysqli_sql_exception $e) {
| |
| − | die("Erro mysql: " . $e->getMessage());
| |
| − | }
| |
| − | catch (Expcetion $e) {
| |
| − | die("Erro xeral: " . $e->getMessage());
| |
| − | }
| |
| − | finally{
| |
| − | if(!empty($conex)){
| |
| − | $conex->close();
| |
| − | }
| |
| − | }
| |
| − | ?>
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − | * Liña 8: Indicamos o número de libros a amosar en cada páxina.
| |
| − | * Liñas 31-42: Obtemos o número de libros que temos na base de datos. Estamos a aproveitar o [http://wiki.cifprodolfoucha.es/index.php?title=PHP_Operaci%C3%B3ns_BD_Mysql#Caso_pr.C3.A1ctico:_Consulta_chamando_a_procedementos_almacenados_con_par.C3.A1metros_de_sa.C3.ADda código dun exercicio anterior] no que amosábamos como traballar cun parámetro de saída, pero neste caso sería máis sinxelo crear un novo procedemento ou unha función, que devolvera o número de libros.
| |
| − | * Liñas 48-68: Amosamos as páxinas en base ao número de rexistros totais e o número de libros a amosar por páxina.
| |
| − | * Liñas 71-72: Pasamos ao procedemento almacenado a partires de que número de libro e a cantidade de libros que vai devolver o select no procedemento almacenado.
| |
| − |
| |
| − |
| |
| − |
| |
| − |
| |
| − |
| |
| − |
| |
| − | <br />
| |
| − | ===== Optimización =====
| |
| − |
| |
| − | * O exercicio anterior poderíamos optimizalo para non ter que repetir a chamada a consultar o número de rexistros totais.
| |
| − | : Esta chamada a estamos a facer en cada consulta á páxina.
| |
| − | : Se non estamos nun entorno multi-usuario no que se poida dar o caso de que se estean a dar de alta novos rexistros de forma continua, podemos optimizar o código e facer que a chamada a consultar o número de rexistros totais se faga unha vez.
| |
| − |
| |
| − |
| |
| − |
| |
| − | * Deberemos gardar nalgunha variable o número de rexistros totais e que dita información se manteña entre chamadas cando cambiamos de número de páxina.
| |
| − | * <u>Posibles solucións</u>:
| |
| − | :* Utilizar coookies.
| |
| − | :* Utilizar variables de sesión.
| |
| − | :* Aproveitar o formulario da paxinación e enviar en forma de campo oculto o número de rexistros totais.
| |
| − | :* Enviar o dato na URL a maiores do número de páxina.
| |
| − |
| |
| − |
| |
| − |
| |
| − | * Posible solución enviando o número total de libros na URL:
| |
| − |
| |
| − | '''Arquivo consultar_libros_con_paxinacion.php'''
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="9-11,47" >
| |
| − | .......................
| |
| − |
| |
| − |
| |
| − |
| |
| − | <?php
| |
| − | // OBTEMOS O NUMERO DE LIBROS
| |
| − | // Este código ven dun caso práctico anterior que devolve o número de libros
| |
| − | // facendo uso dun procedemento almacenado e dun parámetro de saída.
| |
| − | if (!empty($_GET['numtotal'])){ // Ven o número de libros totais na URL
| |
| − | $numLibros = htmlentities($_GET['numtotal']);
| |
| − | }
| |
| − | else{
| |
| − | $numLibros=0;
| |
| − | $proc= sprintf("CALL %s",PROCEDEMENTO_CONSULTAR_NUM_LIBROS);
| |
| − | if ($conex->multi_query($proc)){
| |
| − | $result = $conex->store_result();
| |
| − | if (!($resultado = $conex->query("SELECT @numero_libros as _p_out"))) {
| |
| − | die ("Fallou a obtención: (" . $conex->errno . ") " . $conex->error);
| |
| − | }
| |
| − | else {
| |
| − | $fila = $resultado->fetch_assoc();
| |
| − | $numLibros = $fila['_p_out'];
| |
| − | }
| |
| − | }
| |
| − |
| |
| − | }
| |
| − | ?>
| |
| − | <?php // $numLibros ten o número de libros da nosa táboa
| |
| − | // AMOSAMOS AS PAXINAS EN BASE AO NUMERO TOTAL E O NUMERO POR PAXINA
| |
| − | $total_paxinas = ceil($numLibros/CONST_NUM_LIBROS_PAXINA);
| |
| − |
| |
| − | // Establecemos cal é a páxina actual. Se o usuario elixiu algunha virá en forma de URL $_GET
| |
| − | if(!empty($_GET['paxina'])){
| |
| − | $paxina_actual=htmlentities($_GET['paxina']);
| |
| − | }
| |
| − | else{
| |
| − | $paxina_actual=1; // A primeira vez o usuario non pulsou ningunha páxina e polo tanto a páxina inicial é a 1
| |
| − | }
| |
| − |
| |
| − | // 'Imprimimos as páxinas...
| |
| − | echo "<div>";
| |
| − | for($cont=1;$cont<=$total_paxinas;$cont++){
| |
| − | if ($cont==$paxina_actual){ // A páxina actual non leva link
| |
| − | echo $cont . ' ';
| |
| − | }
| |
| − | else{
| |
| − | printf("<a href='%s?paxina=%d&numtotal=%d'>%d</a> ",htmlentities($_SERVER['PHP_SELF']),$cont,$numLibros,$cont);
| |
| − | }
| |
| − | }
| |
| − | echo "</div>";
| |
| − | ?>
| |
| − |
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − |
| |
| − |
| |
| − | <br />
| |
| − | * Posible solución utilizando [https://wiki.cifprodolfoucha.es/index.php?title=PHP_Sesi%C3%B3ns variables de sesión] (veremos o que son nas seguintes seccións):
| |
| − |
| |
| − | '''Arquivo consultar_libros_con_paxinacion.php'''
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="1-4,9,21,29" >
| |
| − | <?php
| |
| − | session_start(); // Iría ao principio de todo
| |
| − |
| |
| − | ?>
| |
| − | .......................
| |
| − |
| |
| − |
| |
| − | <?php
| |
| − | if (empty($_SESSION['numRexistrosTotais']) {
| |
| − | // OBTEMOS O NUMERO DE LIBROS
| |
| − | $numLibros=0;
| |
| − | $proc= sprintf("CALL %s",PROCEDEMENTO_CONSULTAR_NUM_LIBROS);
| |
| − | if ($conex->multi_query($proc)){
| |
| − | $result = $conex->store_result();
| |
| − | if (!($resultado = $conex->query("SELECT @numero_libros as _p_out"))) {
| |
| − | die ("Fallou a obtención: (" . $conex->errno . ") " . $conex->error);
| |
| − | }
| |
| − | else {
| |
| − | $fila = $resultado->fetch_assoc();
| |
| − | $numLibros = $fila['_p_out'];
| |
| − | $_SESSION['numRexistrosTotais'] = $numLibros;
| |
| − | }
| |
| − | }
| |
| − | }
| |
| − | else {
| |
| − | $numLibros = $_SESSION['numRexistrosTotais'];
| |
| − | }
| |
| − | ?>
| |
| − | ...................
| |
| − |
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − |
| |
| − |
| |
| − |
| |
| − |
| |
| − |
| |
| − |
| |
| − | <br />
| |
| − | === Exercicio proposto ===
| |
| − |
| |
| − | * Crea unha páxina de nome '''buscar_libros.php''' no que se amose nun formulario as seguintes opcións de busca de libros:
| |
| − | :* Busca por título: Será unha caixa de texto limitada a 50 caracteres que fará uso de like (busca por patróns)
| |
| − | :* Busca por prezo: Estará implementada en forma de caixa limitada a 6 caracteres (tres números, unha coma e dous decimais). Deberedes verificar que o formato sexa correcto.
| |
| − | :* Busca por estado: Estará implementada en forma de lista (<select> en html con size) (opcionalmente podedes permitir que o usuario poida escoller varios estados).
| |
| − | :* Busca por autor: Estará implementada en forma de combobox (<select> en html)
| |
| − |
| |
| − | :* Criterios de ordenación: Por título, prezo e autor (implementalo con radiobuttons).
| |
| − |
| |
| − | * O usuario vai poder escoller calquera das opcións combinándoas como prefira.
| |
| − | * Os resultados van poder amosarse paxinados. O número de libros por páxina estará indicado nunha caixa de texto co seu propio formulario e gardado nunha variable de sesión.
| |
| − |
| |
| − | * O resultado vai ser unha lista de libros, os seus autores e os exemplares, ordenados por título
| |
| − | * Implementa a solución utilizando consultas.
| |
| − |
| |
| − | * Quen queira tamén pode intentalo facer con procedementos almacenados pero a consulta teredes que facela dinamicamente dentro do procedementos.
| |
| − | : Podedes consultar como facelo [https://dev.mysql.com/doc/refman/5.7/en/sql-syntax-prepared-statements.html neste enlace].
| |
| − |
| |
| − | == Alta ==
| |
| − |
| |
| − | * Para dar de alta novos rexistros na base de datos temos que facer uso da [http://www.w3schools.com/sql/sql_insert.asp instrución SQL INSERT].
| |
| − | : O proceso é moi parecido ao que fixemos coas consultas, coa diferenza que no caso dunha instrución INSERT non temos rexistros de volta.
| |
| − | : Soamente debemos comprobar se a inserción a fixo correctamente, comprobando se a chamado ao método query devolve false.
| |
| − |
| |
| − | * Comentar que tamén pode sernos útil a instrución [https://dev.mysql.com/doc/refman/5.7/en/insert.html INSERT IGNORE]. Esta instrución non vai emitir ningún erro se intentamos inserir unha clave duplicada nunha táboa. Loxicamente a operación non se fai.
| |
| − |
| |
| − |
| |
| − |
| |
| − | * O proceso para realizar unha inserción é o seguinte:
| |
| − | :* Abrimos a conexión á base de datos.
| |
| − | :::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | $conex = new mysqli(SERVIDOR,USUARIO,PASSWORD,BD);
| |
| − | $conex->set_charset('utf8');
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − |
| |
| − | :* Construímos a orde SQL: INSERT INTO TABOA_1 (col1,col2) VALUES (valor1,'valor2')
| |
| − | :: Neste caso a TABOA_1 está constituída por dúas columnas, a primeira numérica e a segunda de tipo cadea.
| |
| − | :* Executamos a orde e comprobamos que a operación foi correcta:
| |
| − | :::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | $query = "INSERT INTO TABOA_1 (col1,col2) VALUES (valor1,'valor2')";
| |
| − | $result=$conex->query($query);
| |
| − | if ($result) {
| |
| − | echo "Rexistro dado de alta correctamente";
| |
| − | }
| |
| − | else {
| |
| − | echo "Erro na inserción:" . $conex->error;
| |
| − | }
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − | :: Fixarse como a consulta a gardamos nunha variable para non ter problemas coas comiñas na chamada á función query.
| |
| − | :* Pechamos a conexión.
| |
| − | :::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | $conex->close(); // Pechamos a conexión
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − |
| |
| − |
| |
| − |
| |
| − | * Cando o rexistro foi dado de alta correctamente podemos comprobar o número de rexistros afectados (no caso dun único rexistro sempre vai devolver 1, pero tamén se podería aplicar a un insert multifila).
| |
| − | :::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | ..................
| |
| − | $result=$conex->query($query);
| |
| − | if ($result) {
| |
| − | echo "Rexistro dado de alta correctamente";
| |
| − | echo "Num. rexistros afectados:" . $conex->affected_rows;
| |
| − | }
| |
| − | else {
| |
| − | echo "Erro na inserción:" . $conex->error;
| |
| − | }
| |
| − | $conex->close(); // Pechamos a conexión
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − |
| |
| − | === Alta utilizando procedementos almacenados ===
| |
| − |
| |
| − | * O proceso de alta é o mesmo que o visto nas consultas, pero utilizando a orde SQL INSERT dentro do procedemento.
| |
| − |
| |
| − |
| |
| − | * Se estamos a utilizar procedementos almacenados, lembrar que en caso de erro '2014 - Commands out of sync; you can't run this command now', teredes que engadir debemos de engadir a seguinte liña despois de chamar a un procedemento almacenado:
| |
| − |
| |
| − | :::<syntaxhighlight lang="java" line enclose="div" highlight="9" >
| |
| − | if ($conex->multi_query($proc)){
| |
| − | $result = $conex->store_result();
| |
| − | if ($result && ($result->num_rows>0)){
| |
| − | while($fila=$result->fetch_assoc()){
| |
| − | // Procesamos cada fila
| |
| − | }
| |
| − | $result->free();
| |
| − | }
| |
| − | while($conex->next_result() && $conex->store_result());
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − |
| |
| − | * <b><u>IMPORTANTE:</u></b> Se o procedemento de alta non vai devolver datos (cun select) podemos facer uso da orde $conex->query($proc) sendo $proc='CALL NOME_PROC(parámetros)'
| |
| − |
| |
| − |
| |
| − |
| |
| − | * <u>Nota:</u> '''Hai que ter unha correspondencia entre o tipo de dato e tamaño do parámetro do procedemento almacenado e a columna da táboa''' onde imos a inserir o dato.
| |
| − |
| |
| − | :: Así, se a táboa TABOA_1 ten de columnas col1 '''int''', col2 '''varchar(100)'''
| |
| − | :: Se defino un procedemento almacenado para dar de alta unha nova fila: CREATE PROCEDURE ALTA_FILA_TABOA_1(_param1 '''int''', _param2 '''varchar(100)''')
| |
| − |
| |
| − |
| |
| − | * Indicar que dentro dun procedemento almacenado imos poder ter sentenzas:
| |
| − |
| |
| − | :* [http://dev.mysql.com/doc/refman/5.7/en/if.html if - else]
| |
| − | :* [http://dev.mysql.com/doc/refman/5.7/en/while.html while]
| |
| − |
| |
| − | :* E outras de control de fluxo [http://dev.mysql.com/doc/refman/5.7/en/flow-control-statements.html como podedes ver neste enlace].
| |
| − |
| |
| − | === Obtendo o valor dun campo autonumérico ===
| |
| − |
| |
| − | * Normalmente cando deseñamos unha base de datos, certos campos que son claves os definimos como autonuméricos.
| |
| − | : Ao facelo, cando engadimos unha nova fila a un táboa, xérase un novo valor e en certas ocasións necesitamos obter dito valor xa que o podemos utilizar para gardar outros datos noutras táboas que o necesitan por estar relacionadas.
| |
| − |
| |
| − | * Non vale a solución de engadir un dato e inmediatamente facer un 'select' buscando o valor máis alto, xa que non entorno multiusuario, dito valor non ten porqué ser o último engadido.
| |
| − |
| |
| − | * Necesitamos utilizar a [http://php.net/manual/es/mysqli.insert-id.php función mysqli::$insert_id]].
| |
| − |
| |
| − | : Dita función devolve o id autoxerado que se utilizou na última operación.
| |
| − |
| |
| − | * Por exemplo, imaxinemos que temos a táboa TABLE_1 con dúas columnas, col1 int primary key autogenerate e col2 varchar(100).
| |
| − | : Cando executamos a orde: INSERT INTO TABLE_1 (col2) VALUES ('valor1')
| |
| − |
| |
| − | * O proceso é o seguinte:
| |
| − | :* Abrimos a conexión á base de datos.
| |
| − | :* Executamos a orde SQL que engade unha nova fila.
| |
| − | :::<syntaxhighlight lang="java" line enclose="div" highlight="4" >
| |
| − | $result=$conex->query("INSERT INTO TABLE_1 (col2) VALUES ('valor')");
| |
| − | if ($result){
| |
| − | // Operación correcta
| |
| − | printf ("Novo rexistro có id %d.\n", $conex->insert_id);
| |
| − | }
| |
| − | else {
| |
| − | echo 'Erro na execución da operación';
| |
| − | }
| |
| − | </syntaxhighlight>
| |
| − | :* Pechamos a conexión.
| |
| − | :::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | $conex->close(); // Pechamos a conexión
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − |
| |
| − | ==== Caso práctico: Obtendo o valor dun campo autonumérico ====
| |
| − |
| |
| − | * Imos aplicar isto ao noso exemplo.
| |
| − | : Imos crear unha páxina de nome '''Xestionar_Estados.php'''. Dita páxina terá un formulario onde indicaremos cun opcións de radio-button se queremos dar de alta, baixa ou modificar un estado da táboa ESTADOS.
| |
| − | : Nos imos implementar a opción de 'Alta' de novos estados.
| |
| − |
| |
| − | * Ao seleccionar dita opción, aparecerá un novo formulario no que introduciremos nunha caixa de texto o novo estado.
| |
| − | : Ao premer o botón de alta, chamaremos a un procedemento almacenado de nome 'INSERIR_ESTADO()' pasando como parámetro o novo estado.
| |
| − | : Dito procedemento verificará que o estado xa non estea dado de alta previamente. Nese caso devolverá en forma de 'SELECT' o valor -1. En caso de que todo vaia ben, devolverá o id xerado utilizando un SELECT.
| |
| − |
| |
| − |
| |
| − | :<u>Nota:</u> Fixarse que se non utilizáramos procedementos almacenados e o fixeramos con consultas directamente, teríamos que facer un consulta para comprobar se o estado estaba dado de alta previamente (por exemplo con: select count(*) as existe from ESTADOS where upper(descripcion)=upper('valor_enviad') e outra sentenza para engadir o novo estado en caso de que non exista.
| |
| − |
| |
| − | : No exemplo anterior emprégase a [http://dev.mysql.com/doc/refman/5.7/en/string-functions.html#function_upper función UPPER] para converter o texto a maiúsculas.
| |
| − | : Cando traballades con tipos de datos cadeas será algo que teredes que ter en conta. Vai depender se no formulario de entrada de datos permitides ou non as minúsculas, ides necesitar empregar esta copón.
| |
| − |
| |
| − |
| |
| − | * Outro aspecto importante é que <u>ten que haber unha correspondencia de tipo e tamaño</u> entre o campo da táboa onde imos facer a operación (neste caso a táboa ESTADOS, campo descripcion, que é un varchar(100)) e o parámetro que imos definir no procedemento almacenado.
| |
| − |
| |
| − |
| |
| − |
| |
| − | ===== Posible Solución: Utilizando orden INSERT =====
| |
| − |
| |
| − | * Lembrar que dependendo dos casos, teremos que verificar que o valor a engadir non estea dado de alta previamente...
| |
| − | '''Páxina bd.inc.php'''
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | define('CONSULTAR_ESTADO_NUM',"SELECT count(*) as existe FROM ESTADOS WHERE upper(descripcion)=upper('%s')");
| |
| − | define('INSERIR_ESTADO',"INSERT INTO ESTADOS (descripcion) VALUES ('%s')");
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − |
| |
| − |
| |
| − | '''Páxina Xestionar_Estados.php'''
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | <?php
| |
| − |
| |
| − | require ('/var/www/confBD/bd.inc.php');
| |
| − | //error_reporting(0);
| |
| − |
| |
| − | $conex = new mysqli(SERVIDOR,USUARIO,PASSWORD,BD);
| |
| − | $conex->set_charset('utf8');
| |
| − |
| |
| − | if ($conex->connect_error){
| |
| − | die('Erro de Conexión (' . mysqli_connect_errno() . ') ' . mysqli_connect_error());
| |
| − | }
| |
| − | $erro=""; // Variable que a usaremos para amosar os posibles erros que poidan darse
| |
| − | ?>
| |
| − |
| |
| − |
| |
| − | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
| |
| − | "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
| |
| − | <html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es">
| |
| − | <head>
| |
| − | <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
| |
| − | <title>Xestionar táboa Estados BD</title>
| |
| − | </head>
| |
| − |
| |
| − | <body>
| |
| − |
| |
| − |
| |
| − | <!-- Esta parte poderíase facer con javascript -->
| |
| − | <form action="<?php echo htmlentities($_SERVER['PHP_SELF']) ?>" method='post'>
| |
| − | <h2>Escolle a operación a facer sobre a táboa ESTADOS</h2>
| |
| − |
| |
| − | <input <?php echo (isset($_POST['rbtnOperacion']) && $_POST['rbtnOperacion']=='ALTA') ? "checked='checked'" : ""; ?>
| |
| − | onclick='javascript:this.form-submit()' type='radio' name="rbtnOperacion" value='ALTA'>ALTA</input>
| |
| − | <input <?php echo (isset($_POST['rbtnOperacion']) && $_POST['rbtnOperacion']=='BAIXA') ? "checked='checked'" : ""; ?>
| |
| − | onclick='javascript:this.form-submit()' type='radio' name="rbtnOperacion" value='BAIXA'>BAIXA</input>
| |
| − | <input <?php echo (isset($_POST['rbtnOperacion']) && $_POST['rbtnOperacion']=='MODIFICACION') ? "checked='checked'" : ""; ?>
| |
| − | onclick='javascript:this.form-submit()' type='radio' name="rbtnOperacion" value='MODIFICACION'>MODIFICACIÓN</input>
| |
| − | <?php
| |
| − |
| |
| − | if (!empty($_POST['rbtnOperacion'])){ // Premimos unha opción => Xeramos o formulario
| |
| − | switch ($_POST['rbtnOperacion']){
| |
| − | case 'ALTA':
| |
| − | printf("<div>Nome do novo estado:");
| |
| − | printf("<input name='txtEstado' type='text' size='50' maxlength='100' /></div>");
| |
| − | printf("<div><input name='btnOperacion' type='submit' value='ALTA' />");
| |
| − | printf("<input type='reset' value='BORRAR' /></div>");
| |
| − | break;
| |
| − | }
| |
| − | }
| |
| − |
| |
| − | // Xestionamos a operacion do segundo formulario => Alta , Baixa ou Modificacion
| |
| − | if (!empty($_POST['btnOperacion']) && $_POST['btnOperacion']=='ALTA'){
| |
| − | if (strlen($_POST['txtEstado'])>100){
| |
| − | $erro = ">A lonxitude da cadea non pode ter máis de 100 caracteres...";
| |
| − | }
| |
| − | else {
| |
| − | // Eliminamos caracteres non permitidos
| |
| − | $novoEstado = filter_var($_POST['txtEstado'],FILTER_SANITIZE_STRING);
| |
| − | // Escapamos as comillas simples e dobres
| |
| − | $novoEstado = $conex->real_escape_string($novoEstado);
| |
| − |
| |
| − | $query=sprintf(CONSULTAR_ESTADO_NUM,$novoEstado);
| |
| − | $result = $conex->query($query);
| |
| − | if ($result && ($result->num_rows>0)){
| |
| − | $fila=$result->fetch_assoc();
| |
| − | if ($fila['existe']>0) {
| |
| − | $erro="Ese valor xa foi engadido...";
| |
| − | }
| |
| − | $result->free();
| |
| − | }
| |
| − |
| |
| − | if($erro==''){
| |
| − | $query=sprintf(INSERIR_ESTADO,$novoEstado);
| |
| − | $result = $conex->query($query);
| |
| − | if ($result){
| |
| − | printf("<h2>Valor dado de alta correctamente</h2>");
| |
| − | printf("<h3>Nova clave %d</h3>",$conex->insert_id);
| |
| − | }
| |
| − | else{ // Sempre ten que devolver algo como temos implementado o proc.
| |
| − | $erro = 'Houbo un erro na chamada á base de datos';
| |
| − | }
| |
| − | }
| |
| − | }
| |
| − |
| |
| − | } // Fin da operacion de ALTA
| |
| − |
| |
| − | ?>
| |
| − |
| |
| − | </form>
| |
| − |
| |
| − |
| |
| − | <?php
| |
| − | if ($erro!='') {
| |
| − | printf("<h3>%s</h3>",$erro);
| |
| − | }
| |
| − | $conex->close(); // Pechamos a conexión
| |
| − |
| |
| − | ?>
| |
| − |
| |
| − |
| |
| − | </body>
| |
| − | </html>
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − | ===== Posible Solución: Utilizando procedementos almacenados =====
| |
| − |
| |
| − |
| |
| − | * A diferenza do caso anterior:
| |
| − |
| |
| − | :* O id xerado terá que devolvelo o procedemento, mediante SELECT, un parámetro de SAÍDA ou un RETURN se estamos a utilizar unha función.
| |
| − | ::: Teremos que utilizar a [https://dev.mysql.com/doc/refman/5.5/en/information-functions.html#function_last-insert-id función last_insert_id()] para obter o id xerado dende o procedemento de Mysql.
| |
| − |
| |
| − | :* A comprobación de se o dato existe o facemos dentro do procedemento.
| |
| − |
| |
| − |
| |
| − | '''Procedemento almacenado creado en Mysql'''
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="4,22" >
| |
| − | DELIMITER $$
| |
| − |
| |
| − | CREATE DEFINER=`root`@`localhost` PROCEDURE `INSERIR_ESTADO`(_descripcion varchar(100))
| |
| − | etiqueta:BEGIN -- Asociamos unha etiqueta ao código do procedemento para que poidamos facer algo parecido a un exit
| |
| − | declare existe int;
| |
| − |
| |
| − | SELECT count(*)
| |
| − | INTO existe
| |
| − | FROM ESTADOS
| |
| − | WHERE upper(descripcion)=upper(_descripcion);
| |
| − |
| |
| − | IF existe=1 THEN
| |
| − | begin
| |
| − | SELECT -1 as valor_devolto;
| |
| − | leave etiqueta; -- Para que saia do procedemento
| |
| − | end;
| |
| − | END IF; -- Fin do IF
| |
| − |
| |
| − | INSERT INTO ESTADOS (descripcion)
| |
| − | VALUES (_descripcion);
| |
| − |
| |
| − | SELECT last_insert_id() as valor_devolto;
| |
| − |
| |
| − | END
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − | * Liña 4: Utilizamos unha etiqueta para que poidamos utilizar a [https://dev.mysql.com/doc/refman/5.7/en/leave.html orde LEAVE] xa que Mysql non ten outra forma de poder saír dun procedemento almacenado.
| |
| − | :: Neste caso en concreto quizais podería ser más aconsellable utilizar unha función, xa que estamos a devolver un único dato (neste caso -1 ou a id xerada). Desta maneira, poñendo un return xa sairiamos da función.
| |
| − | * Liña 16: Utilizamos a funciuón last_insert_id() para obter o último id xerado da inserción.
| |
| − |
| |
| − |
| |
| − |
| |
| − | * Comentar que dentro dos xestores de base de datos, nos procedementos/funcións podemos empregar as consultas directamente como expresións, por exemplo, no caso anterior:
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="6-11" >
| |
| − | DELIMITER $$
| |
| − |
| |
| − | CREATE DEFINER=`root`@`localhost` PROCEDURE `INSERIR_ESTADO`(_descripcion varchar(100))
| |
| − | etiqueta:BEGIN -- Asociamos unha etiqueta ao código do procedemento para que poidamos facer algo parecido a un exit
| |
| − |
| |
| − | IF (SELECT count(*) FROM ESTADOS WHERE upper(descripcion)=upper(_descripcion))>0 THEN
| |
| − | begin
| |
| − | SELECT -1 as valor_devolto;
| |
| − | leave etiqueta; -- Para que saia do procedemento
| |
| − | end;
| |
| − | END IF; -- Fin do IF
| |
| − |
| |
| − | INSERT INTO ESTADOS (descripcion)
| |
| − | VALUES (_descripcion);
| |
| − |
| |
| − | SELECT last_insert_id() as valor_devolto;
| |
| − |
| |
| − | END
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − |
| |
| − |
| |
| − |
| |
| − | '''Páxina bd.inc.php'''
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | define('PROCEDEMENTO_INSERIR_ESTADO',"INSERIR_ESTADO('%s')");
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − |
| |
| − |
| |
| − | '''Páxina Xestionar_Estados_Proc.php'''
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | <?php
| |
| − |
| |
| − | require ('/var/www/confBD/bd.inc.php');
| |
| − | //error_reporting(0);
| |
| − |
| |
| − | $conex = new mysqli(SERVIDOR,USUARIO,PASSWORD,BD);
| |
| − | $conex->set_charset('utf8');
| |
| − |
| |
| − | if ($conex->connect_error){
| |
| − | die('Erro de Conexión (' . mysqli_connect_errno() . ') ' . mysqli_connect_error());
| |
| − | }
| |
| − | $erro=""; // Variable que a usaremos para amosar os posibles erros que poidan darse
| |
| − | ?>
| |
| − |
| |
| − |
| |
| − | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
| |
| − | "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
| |
| − | <html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es">
| |
| − | <head>
| |
| − | <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
| |
| − | <title>Xestionar táboa Estados BD</title>
| |
| − | </head>
| |
| − |
| |
| − | <body>
| |
| − | <!-- Esta parte poderíase facer con javascript -->
| |
| − | <form action="<?php echo htmlentities($_SERVER['PHP_SELF']) ?>" method='post'>
| |
| − | <h2>Escolle a operación a facer sobre a táboa ESTADOS</h2>
| |
| − |
| |
| − | <input <?php echo (isset($_POST['rbtnOperacion']) && $_POST['rbtnOperacion']=='ALTA') ? "checked='checked'" : ""; ?>
| |
| − | onclick='javascript:this.form-submit()' type='radio' name="rbtnOperacion" value='ALTA'>ALTA</input>
| |
| − | <input <?php echo (isset($_POST['rbtnOperacion']) && $_POST['rbtnOperacion']=='BAIXA') ? "checked='checked'" : ""; ?>
| |
| − | onclick='javascript:this.form-submit()' type='radio' name="rbtnOperacion" value='BAIXA'>BAIXA</input>
| |
| − | <input <?php echo (isset($_POST['rbtnOperacion']) && $_POST['rbtnOperacion']=='MODIFICACION') ? "checked='checked'" : ""; ?>
| |
| − | onclick='javascript:this.form-submit()' type='radio' name="rbtnOperacion" value='MODIFICACION'>MODIFICACIÓN</input>
| |
| − | <?php
| |
| − |
| |
| − | if (!empty($_POST['rbtnOperacion'])){ // Premimos unha opción => Xeramos o formulario
| |
| − | switch ($_POST['rbtnOperacion']){
| |
| − | case 'ALTA':
| |
| − | printf("<div>Nome do novo estado:");
| |
| − | printf("<input name='txtEstado' type='text' size='50' maxlength='100' /></div>");
| |
| − | printf("<div><input name='btnOperacion' type='submit' value='ALTA' />");
| |
| − | printf("<input type='reset' value='BORRAR' /></div>");
| |
| − | break;
| |
| − | }
| |
| − | }
| |
| − |
| |
| − |
| |
| − | // Xestionamos a operacion do segundo formulario => Alta , Baixa ou Modificacion
| |
| − | if (!empty($_POST['btnOperacion']) && $_POST['btnOperacion']=='ALTA'){
| |
| − | if (strlen($_POST['txtEstado'])>100){
| |
| − | $erro = ">A lonxitude da cadea non pode ter máis de 100 caracteres...";
| |
| − | }
| |
| − | else {
| |
| − | // Eliminamos caracteres non permitidos
| |
| − | $novoEstado = filter_var($_POST['txtEstado'],FILTER_SANITIZE_STRING);
| |
| − | // Escapamos as comillas simples e dobres
| |
| − | $novoEstado = $conex->real_escape_string($novoEstado);
| |
| − | $proc=sprintf(PROCEDEMENTO_INSERIR_ESTADO,$novoEstado);
| |
| − | $proc = sprintf("CALL %s",$proc);
| |
| − | if ($conex->multi_query($proc)){
| |
| − | $result = $conex->store_result();
| |
| − | if ($result && ($result->num_rows>0)){
| |
| − | $fila=$result->fetch_assoc();
| |
| − | switch($fila['valor_devolto']){
| |
| − | case -1:
| |
| − | $erro = "Ese valor xa está dado de alta na base de datos...";
| |
| − | break;
| |
| − | default:
| |
| − | printf("<h2>Valor dado de alta correctamente</h2>");
| |
| − | printf("<h3>Nova clave %d</h3>",$fila['valor_devolto']);
| |
| − | }
| |
| − | $result->free();
| |
| − | }
| |
| − | else{ // Sempre ten que devolver algo como temos implementado o proc.
| |
| − | $erro = 'Houbo un erro na chamada á base de datos';
| |
| − | }
| |
| − | }
| |
| − | else{
| |
| − | $erro = 'Houbo un erro na chamada á base de datos';
| |
| − | }
| |
| − | }
| |
| − |
| |
| − | } // Fin da operacion de ALTA
| |
| − |
| |
| − | ?>
| |
| − | </form>
| |
| − |
| |
| − |
| |
| − | <?php
| |
| − | if ($erro!='') {
| |
| − | printf("<h3>%s</h3>",$erro);
| |
| − | }
| |
| − | $conex->close(); // Pechamos a conexión
| |
| − |
| |
| − | ?>
| |
| − |
| |
| − |
| |
| − | </body>
| |
| − | </html>
| |
| − |
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − |
| |
| − | * Neste caso é necesario utilizar a <u>opción multi_query</u> xa que recuperamos un dato que devolve o procedemento.
| |
| − |
| |
| − |
| |
| − |
| |
| − |
| |
| − | === Como tratar aos caracteres 'estraños' ===
| |
| − |
| |
| − | * Con caracteres 'estraños' queremos dicir caracteres fora do abecedario e caracteres numéricos, coma por exemplo: ', '' ,\n,....
| |
| − |
| |
| − | * Aquí debemos diferenciar dous casos.
| |
| − | :* Cando queremos gardar caracteres estraños dentro da base de datos.
| |
| − | :* Cando queremos impedir que o usuario poida utilizar ese tipo de caracteres.
| |
| − |
| |
| − |
| |
| − | ==== Necesitamos gardar caracteres 'estraños' na base de datos ====
| |
| − |
| |
| − | * Neste caso, teremos que 'codificar' e 'descodificar' os datos.
| |
| − | :: Codificaremos os datos cando os gardemos (ou modifiquemos) coa orde SQL INSERT e UPDATE.
| |
| − | :: Decodificaremos: Pode non ser necesario, dependerá do dato e o que queiramos facer con él.
| |
| − |
| |
| − | * As funcións que podemos empregar para isto son:
| |
| − | :* [http://php.net/manual/es/function.htmlentities.php Función htmlentities]: Codifica os caracteres en entidades html. Por exemplo, '<' o codifica como '<'. Fixarse nas 'opcións' xa que por defecto codifica as comiñas dobres pero non as simples.
| |
| − | :* [http://php.net/manual/es/function.html-entity-decode.php Función html_entity_decode]: Realiza o proceso inverso ao anterior.
| |
| − |
| |
| − | * Por exemplo, imaxinade que o usuario engade nunha caixa de texto un código HTML que queredes gardar na base de datos, pero que o navegador non debe 'interpretar'. Por exemplo a cadea: <script>alert('ola);</script>
| |
| − | : Isto podería ser interpretado como un ataque [http://wiki.cifprodolfoucha.es/index.php?title=PHP_Paso_de_datos_con_formularios#Seguridade_no_uso_dos_formularios como xa vimos anteriormente]. Codificando a cadea coa función htmlentities isto non é posible.
| |
| − | : Cando amosemos o resultado de aplicar a función, non deberemos decodificalo, xa que o que queremos é que o navegador 'amose' a cadea, non que 'interprete' o código e execute o código javascript.
| |
| − |
| |
| − |
| |
| − | <u>IMPORTANTE:</u> Lembrar que para utilizar esta solución, todos os datos (ou os que nos interesen que poidan ter este tipo de caracteres) deben ser codificados.
| |
| − | : Se <u>un usuario fai unha busca</u> sobre algún destes campos, teremos que codificar tamén a cadea escrita polo usuario.
| |
| − |
| |
| − |
| |
| − |
| |
| − |
| |
| − | * Outra forma de 'codificar' caracteres 'estraños' é empregando a [http://php.net/manual/es/mysqli.real-escape-string.php función mysqli::real_escape_string].
| |
| − | : Esta función, aplicada a unha cadea de caracteres, 'escapa' os seguintes caracteres: NUL (ASCII 0), \n, \r, \, ', ", e Control-Z. Isto quere dicir que antepón a ditos caracteres o carácter \ e isto fai que o Mysql interprete o seguinte carácter como o que é.
| |
| − |
| |
| − | '''Nota:'''<u>Deberemos aplicar sempre esta función para impedir a inxección de código</u> se non aplicamos outra función que 'codifique' as comiñas simples, antes de enviar os datos á base de datos.
| |
| − |
| |
| − |
| |
| − | : Vexamos un exemplo coa seguinte cadea:
| |
| − | :: $valor = "O'Really";
| |
| − | :: Queremos facer a seguinte consulta: INSERT INTO AUTORES (nome) VALUES ('$valor')
| |
| − | :: Se substituímos $valor polo que vale, vemos que temos un problema coas comiñas simples e daría un erro de sintaxe SQL.
| |
| − |
| |
| − | :* Para solucionalo,podemos aplicar a función 'real_escape_string' a $valor da forma:
| |
| − |
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | <?php
| |
| − | $mysqli = new mysqli("localhost", "usuario", "contrasinal", "bd");
| |
| − |
| |
| − | if (mysqli_connect_errno()) {
| |
| − | printf("Erro na conexión: %s\n", mysqli_connect_error());
| |
| − | exit();
| |
| − | }
| |
| − |
| |
| − |
| |
| − | $valor = "O'Really";
| |
| − |
| |
| − | /* esta consulta faia debido a que non escapa $cidade */
| |
| − | if (!$mysqli->query("INSERT INTO AUTORES (nome) VALUES ('$valor')")) {
| |
| − | printf("Erro: %s\n", $mysqli->sqlstate);
| |
| − | }
| |
| − |
| |
| − | $valor = $mysqli->real_escape_string($valor);
| |
| − |
| |
| − | /* esta consulta con $valor escapada funcionará */
| |
| − | if (!$mysqli->query("INSERT INTO AUTORES (nome) VALUES ('$valor')")) {
| |
| − | printf("%d fila inserida.\n", $mysqli->affected_rows);
| |
| − | }
| |
| − |
| |
| − | $mysqli->close();
| |
| − | ?>
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − | ==== Non queremos que o usuario poida utilizar este tipo de caracteres ====
| |
| − |
| |
| − | * A forma máis sinxela é utilizar os filtros [http://wiki.cifprodolfoucha.es/index.php?title=PHP_Paso_de_datos_con_formularios#Funci.C3.B3n_para_validar_.2F_corrixir_campos xa vistos anteriormente nesta wiki].
| |
| − |
| |
| − | == Baixa ==
| |
| − |
| |
| − | * O proceso de baixa faise por medio da [http://www.w3schools.com/sql/sql_delete.asp orde sql DELETE].
| |
| − |
| |
| − | * Podemos eliminar unha fila ou un conxunto de filas en base a uns criterios (parte WHERE da cláusula DELETE)
| |
| − |
| |
| − | * Ao igual que fixemos anteriormente, podemos borrar directamente en PHP facendo uso da orde DELETE ou chamar a un procedemento almacenado, enviando os datos necesarios e executando a sentenza.
| |
| − |
| |
| − |
| |
| − | * '''IMPORTANTE:''' Temos que ter en conta que nas bases de datos relacionais, dar de baixa un rexistro pode implicar borrar rexistros relacionados.
| |
| − | : Cando creamos as táboas indicamos as relacións entre elas por medio de claves foráneas, nas que indicamos como queremos que se comporten cando se produce un borrado ou unha modificación. Máis información [https://dev.mysql.com/doc/refman/5.7/en/create-table-foreign-keys.html neste enlace].
| |
| − |
| |
| − |
| |
| − | : Por exemplo, na táboa LIBROS_AUTORES:
| |
| − | :::<syntaxhighlight lang="java" line enclose="div" highlight="6,7" >
| |
| − | CREATE TABLE `LIBROS_AUTORES` (
| |
| − | `libro_id` int(11) NOT NULL,
| |
| − | `autor_id` int(11) NOT NULL,
| |
| − | PRIMARY KEY (`libro_id`,`autor_id`),
| |
| − | KEY `fk_LIBROS_AUTORES_AUTORES_idx` (`autor_id`),
| |
| − | CONSTRAINT `fk_LIBROS_AUTORES_AUTORES` FOREIGN KEY (`autor_id`) REFERENCES `AUTORES` (`id_autor`) ON DELETE NO ACTION ON UPDATE CASCADE,
| |
| − | CONSTRAINT `fk_LIBROS_AUTORES_LIBROS` FOREIGN KEY (`libro_id`) REFERENCES `LIBROS` (`id_libro`) ON DELETE NO ACTION ON UPDATE CASCADE
| |
| − | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_spanish2_ci;
| |
| − |
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − | : No exemplo estamos a indicar que en caso de borrar unha fila de táboa AUTORES, non se vai permitir se existen filas na táboa LIBROS_AUTORES.
| |
| − | : Se actualizamos a clave primaria da táboa AUTORES, actualizará a clave na táboa AUTORES_LIBROS (parte ON UPDATE CASCADE).
| |
| − |
| |
| − |
| |
| − | * Polo tanto, será tarefa do programador comprobar se os datos están sendo utilizados noutras táboas e avisar en caso de que o usuario intente borralos ou ben permitir borrados en cascada ou ben capturar o erro que dará o mysql en caso de intentar borrar datos relacionados e informar ao usuario do problema.
| |
| − |
| |
| − |
| |
| − | === Caso Práctico: Baixa ===
| |
| − |
| |
| − | * Imos implementar esta opción na páxina '''Xestionar_Estados.php'''.
| |
| − |
| |
| − |
| |
| − | : Cargaremos nunha <select> todos os estados e premendo o botón 'Baixa' daremos de baixa o estado seleccionado.
| |
| − |
| |
| − | : Faremos uso dun procedemento almacenado de nome '''BORRAR_ESTADO''' que terá como parámetro o id do estado a borrar e fará a sentenza sql DELETE.
| |
| − | : Faremos uso do procedemento almacenado 'CONSULTAR_ESTADOS' que devolverá nun select o id e descrición dos estados para cargar na lista.
| |
| − |
| |
| − | : Nesta solución non comprobamos se xa existen datos relacionados con ese estado antes de borralos e usaremos como base o código feito na solución '''Xestionar_Estados_Proc.php''' feito anteriormente.
| |
| − |
| |
| − |
| |
| − | * Se estamos a utilizar procedementos almacenados, lembrar que en caso de erro '2014 - Commands out of sync; you can't run this command now', debemos engadir a seguinte liña despois de chamar a un procedemento almacenado:
| |
| − |
| |
| − | :::<syntaxhighlight lang="java" line enclose="div" highlight="9" >
| |
| − | if ($conex->multi_query($proc)){
| |
| − | $result = $conex->store_result();
| |
| − | if ($result && ($result->num_rows>0)){
| |
| − | while($fila=$result->fetch_assoc()){
| |
| − | // Procesamos cada fila
| |
| − | }
| |
| − | $result->free();
| |
| − | }
| |
| − | while($conex->next_result() && $conex->store_result());
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − |
| |
| − |
| |
| − | * A xestión do erro en caso de querer borrar un rexistro relacionado a imos facer na páxina PHP comprobando que o valor numérico do erro sexa o 1451, que se corresponde co erro 'Cannot delete or update a parent row: a foreign key constraint fails'.
| |
| − | : Lembrar que dito control poderíamos facelo no propio procedemento almacenado capturando a excepción ou comprobando cun select previo se existe o rexistro relacionado.
| |
| − |
| |
| − |
| |
| − |
| |
| − | * <u>Nota:</u> Lembrar que nesta solución estamos a utilizar a chamada a '''multi_query''' pero se podería utilizar a chamada a '''query''' no caso de que o procedemento non devolva resultados (teña un select que queiramos recoller en PHP).
| |
| − |
| |
| − |
| |
| − |
| |
| − | '''Posible solución Caso Práctico: Baixa'''
| |
| − |
| |
| − |
| |
| − | '''Procedemento almacenado en Mysql: CONSULTAR_ESTADOS'''
| |
| − | :::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | DELIMITER $$
| |
| − |
| |
| − | CREATE DEFINER=`root`@`localhost` PROCEDURE `CONSULTAR_ESTADOS`()
| |
| − | BEGIN
| |
| − | SELECT id_estado,descripcion
| |
| − | FROM ESTADOS
| |
| − | ORDER BY descripcion;
| |
| − |
| |
| − | END
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − |
| |
| − | '''Procedemento almacenado en Mysql: BORRAR_ESTADO'''
| |
| − | :::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | DELIMITER $$
| |
| − |
| |
| − | CREATE DEFINER=`root`@`localhost` PROCEDURE `BORRAR_ESTADO`(_id_estado int)
| |
| − | BEGIN
| |
| − |
| |
| − | DELETE FROM ESTADOS
| |
| − | WHERE id_estado = _id_estado;
| |
| − |
| |
| − | END
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − |
| |
| − |
| |
| − | '''Arquivo bd.inc.php'''
| |
| − | :::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | define ('PROCEDEMENTO_BORRAR_ESTADO',"BORRAR_ESTADO(%d)");
| |
| − | define ('PROCEDEMENTO_CONSULTAR_ESTADOS',"CONSULTAR_ESTADOS()");
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − |
| |
| − |
| |
| − |
| |
| − | '''Arquivo Xestionar_Estados_Proc.php'''
| |
| − | :::<syntaxhighlight lang="java" line enclose="div" highlight="36-57,69-86" >
| |
| − | <?php
| |
| − |
| |
| − | require ('/var/www/confBD/bd.inc.php');
| |
| − | //error_reporting(0);
| |
| − |
| |
| − | $conex = new mysqli(SERVIDOR,USUARIO,PASSWORD,BD);
| |
| − | $conex->set_charset('utf8');
| |
| − |
| |
| − | if ($conex->connect_error){
| |
| − | die('Erro de Conexión (' . mysqli_connect_errno() . ') ' . mysqli_connect_error());
| |
| − | }
| |
| − | $erro=""; // Variable que a usaremos para amosar os posibles erros que poidan darse
| |
| − | ?>
| |
| − |
| |
| − |
| |
| − | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
| |
| − | "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
| |
| − | <html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es">
| |
| − | <head>
| |
| − | <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
| |
| − | <title>Xestionar táboa Estados BD</title>
| |
| − | </head>
| |
| − |
| |
| − | <body>
| |
| − | <!-- Esta parte poderíase facer con javascript -->
| |
| − | <form action="<?php echo htmlentities($_SERVER['PHP_SELF']) ?>" method='post'>
| |
| − | <h2>Escolle a operación a facer sobre a táboa ESTADOS</h2>
| |
| − | <input <?php echo (isset($_POST['rbtnOperacion']) && $_POST['rbtnOperacion']=='ALTA') ? "checked='checked'" : ""; ?>
| |
| − | onclick='javascript:this.form-submit()' type='radio' name="rbtnOperacion" value='ALTA'>ALTA</input>
| |
| − | <input <?php echo (isset($_POST['rbtnOperacion']) && $_POST['rbtnOperacion']=='BAIXA') ? "checked='checked'" : ""; ?>
| |
| − | onclick='javascript:this.form-submit()' type='radio' name="rbtnOperacion" value='BAIXA'>BAIXA</input>
| |
| − | <input <?php echo (isset($_POST['rbtnOperacion']) && $_POST['rbtnOperacion']=='MODIFICACION') ? "checked='checked'" : ""; ?>
| |
| − | onclick='javascript:this.form-submit()' type='radio' name="rbtnOperacion" value='MODIFICACION'>MODIFICACIÓN</input>
| |
| − | <?php
| |
| − |
| |
| − | // Operación BAIXA => TEN QUE IR ANTES XA QUE DESPOIS CARGAMOS A LISTA COS ESTADOS!!!
| |
| − | if (!empty($_POST['btnOperacion']) && $_POST['btnOperacion']=='BAIXA'){
| |
| − | $id_estado = $_POST['lstEstados'];
| |
| − | if (!filter_var($id_estado,FILTER_VALIDATE_INT)){
| |
| − | $error='<h4>O estado indicado non é válido...</h4>';
| |
| − | }
| |
| − | else {
| |
| − | $proc = sprintf(PROCEDEMENTO_BORRAR_ESTADO,$conex->mysql_real_escape($id_estado));
| |
| − | $proc = sprintf("CALL %s",$proc);
| |
| − | if ($conex->multi_query($proc)){
| |
| − | printf("<h4>Num. rexistros borrados:%d</h4>", $conex->affected_rows);
| |
| − | echo "<script>alert('Rexistro dado de baixa correctamente');</script>";
| |
| − | while($conex->next_result() && $conex->store_result());
| |
| − | }
| |
| − | else{
| |
| − | echo "<h5>Falló CALL: (" . $conex->errno . ") " . $conex->error . "</h5>";
| |
| − | if ($conex->errno==1451){ // Clave foránea relacionada
| |
| − | echo "<script>alert('O rexistro non se pode dar de baixa. Está sendo utilizado por outras táboas.');</script>";
| |
| − | }
| |
| − | }
| |
| − | }
| |
| − | } // Fin da operación de BAIXA
| |
| − |
| |
| − |
| |
| − | // CARGAMOS AS DIFERENTES OPCIONS DE FORMULARIO EN BASE A OPERACION ESCOLLIDA DOS RADIOBUTTON: ALTA-BAIXA-MODIFICACION
| |
| − | if (!empty($_POST['rbtnOperacion'])){ // Premimos unha opción => Xeramos o formulario
| |
| − | switch ($_POST['rbtnOperacion']){
| |
| − | case 'ALTA':
| |
| − | printf("<div>Nome do novo estado:");
| |
| − | printf("<input name='txtEstado' type='text' size='50' maxlength='100' /></div>");
| |
| − | printf("<div><input name='btnOperacion' type='submit' value='ALTA' />");
| |
| − | printf("<input type='reset' value='BORRAR' /></div>");
| |
| − | break;
| |
| − | case 'BAIXA':
| |
| − | printf("<div>Lista de estados:");
| |
| − | $proc = sprintf("CALL %s",PROCEDEMENTO_CONSULTAR_ESTADOS);
| |
| − | printf("<select name='lstEstados'>");
| |
| − | if ($conex->multi_query($proc)){
| |
| − | $result = $conex->store_result();
| |
| − | if ($result && ($result->num_rows>0)){
| |
| − | while($fila=$result->fetch_assoc()){
| |
| − | printf("<option value='%d'>%s</option>",$fila['id_estado'],$fila['descripcion']);
| |
| − | }
| |
| − | $result->free();
| |
| − | }
| |
| − | while($conex->next_result() && $conex->store_result());
| |
| − | }
| |
| − | printf("</select>");
| |
| − | printf("</div>");
| |
| − | printf("<div><input name='btnOperacion' type='submit' value='BAIXA' />");
| |
| − | break;
| |
| − | }
| |
| − | }
| |
| − | ?>
| |
| − | </form>
| |
| − |
| |
| − | <?php
| |
| − | // Xestionamos a operacion do segundo formulario => Alta
| |
| − | if (!empty($_POST['btnOperacion']) && $_POST['btnOperacion']=='ALTA'){
| |
| − | if (strlen($_POST['txtEstado'])>100){
| |
| − | $erro = ">A lonxitude da cadea non pode ter máis de 100 caracteres...";
| |
| − | }
| |
| − | else {
| |
| − | // Eliminamos caracteres non permitidos
| |
| − | $novoEstado = filter_var($_POST['txtEstado'],FILTER_SANITIZE_STRING);
| |
| − | // Escapamos as comillas simples e dobres
| |
| − | $novoEstado = $conex->real_escape_string($novoEstado);
| |
| − | $proc=sprintf(PROCEDEMENTO_INSERIR_ESTADO,$novoEstado);
| |
| − | $proc = sprintf("CALL %s",$proc);
| |
| − | if ($conex->multi_query($proc)){
| |
| − | $result = $conex->store_result();
| |
| − | if ($result && ($result->num_rows>0)){
| |
| − | $fila=$result->fetch_assoc();
| |
| − | switch($fila['valor_devolto']){
| |
| − | case -1:
| |
| − | $erro = "Ese valor xa está dado de alta na base de datos...";
| |
| − | break;
| |
| − | default:
| |
| − | printf("<h2>Valor dado de alta correctamente</h2>");
| |
| − | printf("<h3>Nova clave %d</h3>",$fila['valor_devolto']);
| |
| − |
| |
| − | break;
| |
| − | }
| |
| − | $result->free();
| |
| − | }
| |
| − | else{ // Sempre ten que devolver algo como temos implementado o proc.
| |
| − | $erro = 'Houbo un erro na chamada á base de datos';
| |
| − | }
| |
| − | while($conex->next_result() && $conex->store_result());
| |
| − | }
| |
| − | else{
| |
| − | $erro = 'Houbo un erro na chamada á base de datos';
| |
| − | }
| |
| − | }
| |
| − |
| |
| − | } // Fin da operacion de ALTA
| |
| − |
| |
| − |
| |
| − |
| |
| − | ?>
| |
| − | <?php
| |
| − | if ($erro!='') {
| |
| − | printf("<h3>%s</h3>",$erro);
| |
| − | }
| |
| − | $conex->close(); // Pechamos a conexión
| |
| − |
| |
| − | ?>
| |
| − |
| |
| − |
| |
| − | </body>
| |
| − | </html>
| |
| − |
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − | == Modificación ==
| |
| − |
| |
| − |
| |
| − | * A modificación se produce cando executamos a [http://www.w3schools.com/sql/sql_update.asp orde SQL UPDATE].
| |
| − |
| |
| − | * Ao igual que no caso INSERT, se modificamos as claves primarias, farase o action recollido na creación das táboas.
| |
| − | : Normalmente non se debe modificar a clave primaria das táboas e o que modificaremos será algún dos atributos da táboa.
| |
| − |
| |
| − |
| |
| − | * A orde SQL UPDATE levará como parámetros os novos valores a modificar e enviarase a clave primaria para que poida buscar a fila concreta que vai modificarse.
| |
| − |
| |
| − | * Ao igual que no caso da orde INSERT, podemos saber o número de rexistros afectados chamando á [http://php.net/manual/es/mysqli.affected-rows.php función mysqi_affected_rows].
| |
| − | : Neste exercicio dita función debería devolver sempre 1, pero podería aplicarse a outros exemplo no que queremos informar ao usuario de cantos rexistros foron afectados pola opción.
| |
| − |
| |
| − |
| |
| − |
| |
| − | * <u>Nota:</u> Lembrar que nesta solución estamos a utilizar a chamada a '''multi_query''' pero se podería utilizar a chamada a '''query''' no caso de que o procedemento non devolva resultados (teña un select que queiramos recoller en PHP).
| |
| − |
| |
| − |
| |
| − |
| |
| − |
| |
| − | === Caso Práctico: Modificación ===
| |
| − |
| |
| − | * Seguindo có noso exemplo imos a rematar a páxina 'Xestionar_Estados_Proc.php' engadindo a funcionalidade para modificar os estados.
| |
| − |
| |
| − | : Cando se seleccione esta opción aparecerá unha lista. Deberemos escoller un estado da lista e teremos unha caixa de texto para darlle o novo valor ao elemento seleccionado.
| |
| − | : Ao premer o botón 'Modificar' modificarase o estado do elemento seleccionado.
| |
| − |
| |
| − | : Comprobaremos antes de facer a modificación, que estea seleccionado un elemento da lista.
| |
| − |
| |
| − |
| |
| − |
| |
| − | <u>Nota:</u> Neste curso non entramos en temas de 'presentación' e 'javascript/jquery'. Por iso a 'presentación' de cara a un usuario final non é a adecuada.
| |
| − | : Neste caso poderíamos implementar, con 'javascript', que o usuario teña que seleccionar un elemento da lista antes de premer o botón 'Modificar'.
| |
| − | : Tamén poderíamos implementar con jquery/javascript unha lista 'editable'.
| |
| − |
| |
| − |
| |
| − |
| |
| − | '''Posible solución Caso Práctico: Modificación'''
| |
| − |
| |
| − | '''Procedemento almacenado de Mysql MODIFICAR_ESTADO'''
| |
| − | ::<syntaxhighlight lang="java" enclose="div" highlight="" >
| |
| − | DELIMITER $$
| |
| − |
| |
| − | CREATE DEFINER=`root`@`localhost` PROCEDURE `MODIFICAR_ESTADO`(_id_estado int,_descripcion varchar(100))
| |
| − | BEGIN
| |
| − |
| |
| − | UPDATE ESTADOS
| |
| − | SET descripcion=_descripcion
| |
| − | WHERE id_estado = _id_estado;
| |
| − |
| |
| − | END
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − |
| |
| − |
| |
| − |
| |
| − | '''Arquivo: bd.inc.php'''
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | define ('PROCEDEMENTO_MODIFICAR_ESTADO',"BORRAR_ESTADO(%d,'%s')");
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − |
| |
| − |
| |
| − |
| |
| − | '''Arquivo: Xestionar_Estados_Proc.php'''
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="59-88,120-140" >
| |
| − | <?php
| |
| − |
| |
| − | require ('/var/www/confBD/bd.inc.php');
| |
| − | //error_reporting(0);
| |
| − |
| |
| − | $conex = new mysqli(SERVIDOR,USUARIO,PASSWORD,BD);
| |
| − | $conex->set_charset('utf8');
| |
| − |
| |
| − | if ($conex->connect_error){
| |
| − | die('Erro de Conexión (' . mysqli_connect_errno() . ') ' . mysqli_connect_error());
| |
| − | }
| |
| − | $erro=""; // Variable que a usaremos para amosar os posibles erros que poidan darse
| |
| − | ?>
| |
| − |
| |
| − |
| |
| − | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
| |
| − | "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
| |
| − | <html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es">
| |
| − | <head>
| |
| − | <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
| |
| − | <title>Xestionar táboa Estados BD</title>
| |
| − | </head>
| |
| − |
| |
| − | <body>
| |
| − | <!-- Esta parte poderíase facer con javascript -->
| |
| − | <form action="<?php echo htmlentities($_SERVER['PHP_SELF']) ?>" method='post'>
| |
| − | <h2>Escolle a operación a facer sobre a táboa ESTADOS</h2>
| |
| − | <input <?php echo (isset($_POST['rbtnOperacion']) && $_POST['rbtnOperacion']=='ALTA') ? "checked='checked'" : ""; ?>
| |
| − | onclick='javascript:this.form-submit()' type='radio' name="rbtnOperacion" value='ALTA'>ALTA</input>
| |
| − | <input <?php echo (isset($_POST['rbtnOperacion']) && $_POST['rbtnOperacion']=='BAIXA') ? "checked='checked'" : ""; ?>
| |
| − | onclick='javascript:this.form-submit()' type='radio' name="rbtnOperacion" value='BAIXA'>BAIXA</input>
| |
| − | <input <?php echo (isset($_POST['rbtnOperacion']) && $_POST['rbtnOperacion']=='MODIFICACION') ? "checked='checked'" : ""; ?>
| |
| − | onclick='javascript:this.form-submit()' type='radio' name="rbtnOperacion" value='MODIFICACION'>MODIFICACIÓN</input>
| |
| − | <?php
| |
| − |
| |
| − | // Operación BAIXA => TEN QUE IR ANTES XA QUE DESPOIS CARGAMOS A LISTA COS ESTADOS!!!
| |
| − | if (!empty($_POST['btnOperacion']) && $_POST['btnOperacion']=='BAIXA'){
| |
| − | $id_estado = $_POST['lstEstados'];
| |
| − | if (!filter_var($id_estado,FILTER_VALIDATE_INT)){
| |
| − | $error='<h4>O estado indicado non é válido...</h4>';
| |
| − | }
| |
| − | else {
| |
| − | $proc = sprintf(PROCEDEMENTO_BORRAR_ESTADO,$conex->mysql_real_escape($id_estado));
| |
| − | $proc = sprintf("CALL %s",$proc);
| |
| − | if ($conex->multi_query($proc)){
| |
| − | printf("<h4>Num. rexistros borrados:%d</h4>", $conex->affected_rows);
| |
| − | echo "<script>alert('Rexistro dado de baixa correctamente');</script>";
| |
| − | while($conex->next_result() && $conex->store_result());
| |
| − | }
| |
| − | else{
| |
| − | echo "<h5>Falló CALL: (" . $conex->errno . ") " . $conex->error . "</h5>";
| |
| − | if ($conex->errno==1451){ // Clave foránea relacionada
| |
| − | echo "<script>alert('O rexistro non se pode dar de baixa. Está sendo utilizado por outras táboas.');</script>";
| |
| − | }
| |
| − | }
| |
| − | }
| |
| − | } // Fin da operación de BAIXA
| |
| − |
| |
| − | // OPERACION DE MODIFICAR
| |
| − | if (!empty($_POST['btnOperacion']) && $_POST['btnOperacion']=='MODIFICAR'){
| |
| − | if (empty($_POST['lstEstados'])){
| |
| − | $erro='É necesario seleccionar un estado da lista...';
| |
| − | }
| |
| − | else {
| |
| − | $id_estado = $_POST['lstEstados'];
| |
| − | if (!filter_var($id_estado,FILTER_VALIDATE_INT)){
| |
| − | $erro='<h4>O estado indicado non é válido...</h4>';
| |
| − | }
| |
| − | elseif (strlen($_POST['txtEstado'])>100){
| |
| − | $erro = ">A lonxitude da cadea non pode ter máis de 100 caracteres...";
| |
| − | }
| |
| − | else {
| |
| − | // Eliminamos caracteres non permitidos
| |
| − | $novoEstado = filter_var($_POST['txtEstado'],FILTER_SANITIZE_STRING);
| |
| − | // Escapamos as comillas simples e dobres
| |
| − | $novoEstado = $conex->real_escape_string($novoEstado);
| |
| − | $proc=sprintf(PROCEDEMENTO_MODIFICAR_ESTADO,$conex->mysql_real_escape($id_estado),$novoEstado);
| |
| − | $proc = sprintf("CALL %s",$proc);
| |
| − | if ($conex->multi_query($proc)){
| |
| − | printf("<h4>Num. rexistros modificados:%d</h4>", $conex->affected_rows);
| |
| − | while($conex->next_result() && $conex->store_result());
| |
| − | }
| |
| − | else{
| |
| − | echo "<h5>Falló CALL: (" . $conex->errno . ") " . $conex->error . "</h5>";
| |
| − | }
| |
| − |
| |
| − | }
| |
| − | }
| |
| − | } // Fin da operación de MODIFICAR
| |
| − |
| |
| − |
| |
| − |
| |
| − | // CARGAMOS AS DIFERENTES OPCIONS DE FORMULARIO EN BASE A OPERACION ESCOLLIDA DOS RADIOBUTTON: ALTA-BAIXA-MODIFICACION
| |
| − | if (!empty($_POST['rbtnOperacion'])){ // Premimos unha opción => Xeramos o formulario
| |
| − | switch ($_POST['rbtnOperacion']){
| |
| − | case 'ALTA':
| |
| − | printf("<div>Nome do novo estado:");
| |
| − | printf("<input name='txtEstado' type='text' size='50' maxlength='100' /></div>");
| |
| − | printf("<div><input name='btnOperacion' type='submit' value='ALTA' />");
| |
| − | printf("<input type='reset' value='BORRAR' /></div>");
| |
| − | break;
| |
| − | case 'BAIXA':
| |
| − | printf("<div>Lista de estados:");
| |
| − | $proc = sprintf("CALL %s",PROCEDEMENTO_CONSULTAR_ESTADOS);
| |
| − | printf("<select name='lstEstados'>");
| |
| − | if ($conex->multi_query($proc)){
| |
| − | $result = $conex->store_result();
| |
| − | if ($result && ($result->num_rows>0)){
| |
| − | while($fila=$result->fetch_assoc()){
| |
| − | printf("<option value='%d'>%s</option>",$fila['id_estado'],$fila['descripcion']);
| |
| − | }
| |
| − | $result->free();
| |
| − | }
| |
| − | while($conex->next_result() && $conex->store_result());
| |
| − | }
| |
| − | printf("</select>");
| |
| − | printf("</div>");
| |
| − | printf("<div><input name='btnOperacion' type='submit' value='BAIXA' />");
| |
| − | break;
| |
| − | case 'MODIFICACION':
| |
| − | printf("<div>");
| |
| − | $proc = sprintf("CALL %s",PROCEDEMENTO_CONSULTAR_ESTADOS);
| |
| − | printf("<select name='lstEstados' size='10'>");
| |
| − | if ($conex->multi_query($proc)){
| |
| − | $result = $conex->store_result();
| |
| − | if ($result && ($result->num_rows>0)){
| |
| − | while($fila=$result->fetch_assoc()){
| |
| − | printf("<option value='%d'>%s</option>",$fila['id_estado'],$fila['descripcion']);
| |
| − | }
| |
| − | $result->free();
| |
| − | }
| |
| − | while($conex->next_result() && $conex->store_result());
| |
| − | }
| |
| − | printf("</select>");
| |
| − | printf("Novo valor: <input name='txtEstado' type='text' size='50' maxlength='100' />");
| |
| − | printf("</div>");
| |
| − | printf("<div><input name='btnOperacion' type='submit' value='MODIFICAR' />");
| |
| − | break;
| |
| − |
| |
| − | }
| |
| − | }
| |
| − | ?>
| |
| − | </form>
| |
| − |
| |
| − | <?php
| |
| − | // Xestionamos a operacion do segundo formulario => Alta
| |
| − | if (!empty($_POST['btnOperacion']) && $_POST['btnOperacion']=='ALTA'){
| |
| − | if (strlen($_POST['txtEstado'])>100){
| |
| − | $erro = ">A lonxitude da cadea non pode ter máis de 100 caracteres...";
| |
| − | }
| |
| − | else {
| |
| − | // Eliminamos caracteres non permitidos
| |
| − | $novoEstado = filter_var($_POST['txtEstado'],FILTER_SANITIZE_STRING);
| |
| − | // Escapamos as comillas simples e dobres
| |
| − | $novoEstado = $conex->real_escape_string($novoEstado);
| |
| − | $proc=sprintf(PROCEDEMENTO_INSERIR_ESTADO,$novoEstado);
| |
| − | $proc = sprintf("CALL %s",$proc);
| |
| − | if ($conex->multi_query($proc)){
| |
| − | $result = $conex->store_result();
| |
| − | if ($result && ($result->num_rows>0)){
| |
| − | $fila=$result->fetch_assoc();
| |
| − | switch($fila['valor_devolto']){
| |
| − | case -1:
| |
| − | $erro = "Ese valor xa está dado de alta na base de datos...";
| |
| − | break;
| |
| − | default:
| |
| − | printf("<h2>Valor dado de alta correctamente</h2>");
| |
| − | printf("<h3>Nova clave %d</h3>",$fila['valor_devolto']);
| |
| − |
| |
| − | break;
| |
| − | }
| |
| − | $result->free();
| |
| − | }
| |
| − | else{ // Sempre ten que devolver algo como temos implementado o proc.
| |
| − | $erro = 'Houbo un erro na chamada á base de datos';
| |
| − | }
| |
| − | while($conex->next_result() && $conex->store_result());
| |
| − | }
| |
| − | else{
| |
| − | $erro = 'Houbo un erro na chamada á base de datos';
| |
| − | }
| |
| − | }
| |
| − |
| |
| − | } // Fin da operacion de ALTA
| |
| − |
| |
| − |
| |
| − |
| |
| − | ?>
| |
| − | <?php
| |
| − | if ($erro!='') {
| |
| − | printf("<h3>%s</h3>",$erro);
| |
| − | }
| |
| − | $conex->close(); // Pechamos a conexión
| |
| − |
| |
| − | ?>
| |
| − |
| |
| − |
| |
| − | </body>
| |
| − | </html>
| |
| − |
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − | == Transaccións ==
| |
| − |
| |
| − | * En certas ocasións imos necesitar facer varias operacións á vez nas táboas da base de datos.
| |
| − |
| |
| − | * Por exemplo, no exemplo que estamos a desenvolver:
| |
| − | :* Podemos crear unha páxina PHP cun formulario para dar de alta un libro xunto cos exemplares.
| |
| − | :* Podemos crear unha páxina PHP que relacione libros xunto cos autores que o escribiron.
| |
| − | :* Podemos dar de baixa un libro. Isto leva consigo dar de baixa o libro máis os exemplares máis os datos da táboas LIBROS_AUTORES.
| |
| − |
| |
| − | * Outro caso típico é o carrito da compra. Cando o usuario remata o proceso de compra e procede a realizar o pedido, necesitaremos gardar os produtos que conforman o pedido na nosa base de datos.
| |
| − | : Queremos que se garden todos os produtos.
| |
| − |
| |
| − |
| |
| − | : Normalmente imos queres que ditas operacións se fagan todas e en caso de erro, necesitaremos volver ao estado anterior ao comezo do inicio das operacións.
| |
| − |
| |
| − | * Isto o podemos facer grazas as '''TRANSACCIÓNS'''.
| |
| − | : Unha transacción nos garante que o conxunto de operacións que están dentro dunha transacción se farán todas ou non se fará ningunha.
| |
| − |
| |
| − | * Normalmente cada operación individual é 'considerada' coma unha transacción en si mesma, de tal forma o ou ten éxito ou os cambios non se gardan na base de datos.
| |
| − |
| |
| − | * Máis información sobre Transaccións:
| |
| − | :* [https://es.wikipedia.org/wiki/Transacci%C3%B3n_(base_de_datos) Definición Wikipedia].
| |
| − | :* [https://es.wikipedia.org/wiki/ACID Características dunha transacción].
| |
| − | :* [https://dev.mysql.com/doc/refman/5.7/en/commit.html Transaccións en Mysql].
| |
| − |
| |
| − |
| |
| − |
| |
| − |
| |
| − | * Cando falamos de transaccións outro factor a ter en conta é o chamado '''Nivel de illamento'''.
| |
| − | : Imaxinemos que estamos dentro dunha transacción e estou lendo o número de filas dunha táboa. Se outro usuario engade unha nova fila despois de que eu lera o número, e eu dentro da transacción volvo a ler o número de filas. Terei dúas cantidades diferentes dentro da mesma transacción.
| |
| − | : Para evitalo podemos facer que a táboa que estou a utilizar dentro da transacción quede 'bloqueada' e non se poida modificar ata que eu remate.
| |
| − | : Este e outros problemas que poden darse no uso de transaccións se intentan evitar cun nivel de illamento determinado.
| |
| − | : Cada nivel de illamento soluciona algún dos problemas que se poden dar.
| |
| − |
| |
| − | * Máis información sobre Niveis de illamento en Mysql:
| |
| − | :* [http://download.nust.na/pub6/mysql/doc/refman/5.0/es/innodb-transaction-isolation.html Niveis de illamento en Mysql].
| |
| − | :* [http://recetasinformaticas.blogspot.com.es/2007/09/mysql-niveles-de-aislamiento-isolations.html Exemplos de problemas no uso de transaccións].
| |
| − |
| |
| − |
| |
| − |
| |
| − |
| |
| − | * Dos niveis de illamento (que se basean en bloquear rexistros de táboas) se pode provocar outro problema, que é o '''Interbloqueo''' ou '''Deadlock'''.
| |
| − | : Isto prodúcese cando unha transacción_1 está esperando o acceso a un recurso que está sendo bloqueado por outra transacción_2 e esta outra transacción_2 está esperando por un recurso que está sendo bloqueado pola transacción_1.
| |
| − |
| |
| − | * Máis información sobre os DeadLock [http://mysql.localhost.net.ar/doc/refman/5.0/es/innodb-deadlocks.html neste enlace].
| |
| − |
| |
| − |
| |
| − |
| |
| − | * En PHP, para poder utilizar transaccións necesitamos:
| |
| − | :* MySQL 5.6
| |
| − | :* O [https://es.wikipedia.org/wiki/InnoDB motor InnoDB].
| |
| − |
| |
| − |
| |
| − |
| |
| − | * Unha transacción a podemos facer dende a páxina PHP ou dende o xestor Mysql dentro dun procedemento almacenado.
| |
| − |
| |
| − | * Para realizar unha transacción o proceso sempre é o mesmo en todas as linguaxes e xestores.
| |
| − | :# Iniciar a transacción. Normalmente farase coa orde 'START TRANSACTION' (outros xestores teñen coma orde BEGIN TRANSACTION)
| |
| − | :# Realizar as operacións que conforman as transaccións.
| |
| − | :# En caso de erro en algunha das operacións anteriores executar a orde 'ROLLBACK' e saír
| |
| − | :# En caso de que todo se execute correctamente, entón confirmamos a transacción e os cambios se gardan definitivamente na base de datos, coa orde: 'COMMIT'
| |
| − |
| |
| − |
| |
| − | === Transaccións en PHP ===
| |
| − |
| |
| − | * Para iniciar unha transacción temos dúas opcións:
| |
| − |
| |
| − | :* Inhabilitar as transaccións implícitas, coa [http://php.net/manual/es/mysqli.autocommit.php función autocommit]:
| |
| − | ::* Versión procedimental:
| |
| − | ::::<syntaxhighlight lang="java" line enclose="div" highlight="2" >
| |
| − | $conex = mysqli_connect(HOST, USER, PASSWORD, BD);
| |
| − | mysqli_autocommit($conex,false);
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − | ::* Versión obxectual:
| |
| − | ::::<syntaxhighlight lang="java" line enclose="div" highlight="2" >
| |
| − | $conex= new mysqli(HOST, USER, PASSWORD, BD);
| |
| − | $conex->autocommit(false);
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − | :: Desta forma as operacións que vaian a continuación desta orde non se farán efectivas ata atopar un commit.
| |
| − |
| |
| − | :* Iniciar unha transacción coa [http://php.net/manual/es/mysqli.begin-transaction.php función mysqli::begin_transaction].
| |
| − | :: '''NECESITAMOS QUE A VERSIÓN PHP SEXA 5.5 OU SUPERIOR'''.
| |
| − |
| |
| − | ::* Versión procedimental:
| |
| − | ::::<syntaxhighlight lang="java" line enclose="div" highlight="2" >
| |
| − | $conex = mysqli_connect(HOST, USER, PASSWORD, BD);
| |
| − | mysqli_begin_transaction($conex,MYSQLI_TRANS_START_READ_ONLY);
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − | ::* Versión obxectual:
| |
| − | ::::<syntaxhighlight lang="java" line enclose="div" highlight="2" >
| |
| − | $conex= new mysqli(HOST, USER, PASSWORD, BD);
| |
| − | $conex->begin_transaction(MYSQLI_TRANS_START_READ_ONLY);
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − |
| |
| − | :: Esta segunda forma leva como parámetro unha bandeira que vai indicar que dentro da transacción non vai poderse facer operacións que modifiquen os contidos das táboas da base de datos.
| |
| − | :: Se a transacción necesita facer operacións contra a base de datos, levaría o flag MYSQLI_TRANS_START_READ_WRITE.
| |
| − | :: Máis información [https://dev.mysql.com/doc/refman/5.6/en/commit.html neste enlace].
| |
| − |
| |
| − |
| |
| − | * Unha vez iniciada a transacción faremos as operacións necesarias sobre a base de datos. En caso de erro nalgunha delas, teremos que facer un rollback coa [http://php.net/manual/es/mysqli.rollback.php función rollback].
| |
| − |
| |
| − | ::* Versión procedimental:
| |
| − | ::::<syntaxhighlight lang="java" line enclose="div" highlight="7" >
| |
| − | $conex = mysqli_connect(HOST, USER, PASSWORD, BD);
| |
| − | mysqli_begin_transaction($conex,MYSQLI_TRANS_START_READ_ONLY);
| |
| − |
| |
| − | .........
| |
| − | OPERACIÓN
| |
| − | Si erro
| |
| − | mysqli_rollback($conex);
| |
| − | die('Houbo un erro...');
| |
| − |
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − | ::* Versión obxectual:
| |
| − | ::::<syntaxhighlight lang="java" line enclose="div" highlight="7" >
| |
| − | $conex= new mysqli(HOST, USER, PASSWORD, BD);
| |
| − | $conex->begin_transaction(MYSQLI_TRANS_START_READ_ONLY);
| |
| − |
| |
| − | .........
| |
| − | OPERACIÓN
| |
| − | Si erro
| |
| − | $conex->rollback();
| |
| − | die('Houbo un erro...');
| |
| − |
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − |
| |
| − | * Exemplo (obxectual):
| |
| − | :::<syntaxhighlight lang="java" line enclose="div" highlight="7,14-16,26-28" >
| |
| − | $conex = new mysqli(SERVIDOR,USUARIO,PASSWORD,BD);
| |
| − | $conex->set_charset('utf8');
| |
| − |
| |
| − | if ($conex->connect_error){
| |
| − | die('Erro de Conexión (' . mysqli_connect_errno() . ') ' . mysqli_connect_error());
| |
| − | }
| |
| − | $conex->begin_transaction(MYSQLI_TRANS_START_READ_WRITE);
| |
| − |
| |
| − | $result=$conex->query("INSERT INTO TABLE_1 (col2) VALUES ('valor')");
| |
| − | if ($result){
| |
| − | // Operación correcta
| |
| − | printf ("Novo rexistro có id %d.\n", $conex->insert_id);
| |
| − | }
| |
| − | else {
| |
| − | $conex->rollback();
| |
| − | $conex->close();
| |
| − | die 'Erro na execución da operación';
| |
| − | }
| |
| − |
| |
| − |
| |
| − | $result=$conex->query("INSERT INTO TABLE_2 (col2) VALUES ('valor')");
| |
| − | if ($result){
| |
| − | // Operación correcta
| |
| − | printf ("Novo rexistro có id %d.\n", $conex->insert_id);
| |
| − | }
| |
| − | else {
| |
| − | $conex->rollback();
| |
| − | $conex->close();
| |
| − | die 'Erro na execución da operación';
| |
| − | }
| |
| − |
| |
| − |
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − |
| |
| − |
| |
| − | * Unha vez rematadas as operacións facemos o '''commit''' coa [http://php.net/manual/es/mysqli.commit.php función commit()]:
| |
| − | ::* Versión procedimental:
| |
| − | ::::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | mysqli_commit($conex);
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − | ::* Versión obxectual:
| |
| − | ::::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | $conex->commit();
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − |
| |
| − |
| |
| − |
| |
| − | * Se establecimos autocommit(false) deberemos volvelo a establecer en True.
| |
| − | :::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | $conex->autocommit(true);
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − |
| |
| − | * Ao rematar todas as operacións sobre a base de datos pecharemos a conexión.
| |
| − | :::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | $conex->close();
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − | === Transaccións en MYSQL dentro de procedementos almacenados ===
| |
| − |
| |
| − |
| |
| − | * Outra forma de realizar as transaccións é que poidamos enviar os datos a un procedemento almacenado de Mysql e que dende o procedemento se faga a transacción.
| |
| − |
| |
| − | * O proceso é moi parecido ao feito dende PHP.
| |
| − | : Lembrar que se o facemos dende o propio procedemento <u>NON TEMOS QUE FACELO</u> dende PHP.
| |
| − |
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="5-9,11,20" >
| |
| − | DELIMITER $$
| |
| − |
| |
| − | CREATE PROCEDURE `PROCEDEMENTO_CON_TRANSACCION`(_param1 int,_param2 varchar(50),_param3 varchar(200))
| |
| − | BEGIN
| |
| − | DECLARE EXIT HANDLER FOR SQLEXCEPTION
| |
| − | BEGIN
| |
| − | ROLLBACK;
| |
| − | SELECT -1 as error;
| |
| − | END;
| |
| − |
| |
| − | START TRANSACTION;
| |
| − |
| |
| − | INSERT INTO TABLE_1(col1,col2)
| |
| − | VALUES (_param2,_param3); -- Supoñemos que esta táboa devolve un valor clave autonumérico.
| |
| − |
| |
| − | INSERT INTO TABLE_2(col1,col2)
| |
| − | VALUES (_param1,last_insert_id());
| |
| − |
| |
| − |
| |
| − | COMMIT;
| |
| − | SELECT 0 AS error;
| |
| − |
| |
| − | END
| |
| − | $$
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − | * Liñas 5-9: Capturamos calquera erro que se produza e nese caso enviamos a PHP un código -1 no select facendo un rollback da transacción.
| |
| − | * Liña 11: Iniciamos a transacción.
| |
| − | * Liñas 20 e 21: Confirmamos a transacción se todo foi ben e enviamos un 0 como código ao PHP.
| |
| − |
| |
| − |
| |
| − | * Comentar que se facemos as transaccións dende procedementos almacenados teremos que capturar as posibles excepcións que se produzan para facer o rollback.
| |
| − | : Máis información: [http://www.mysqltutorial.org/mysql-error-handling-in-stored-procedures/ Captura de erros en Mysql].
| |
| − |
| |
| − | * A partires de Mysql 5.6 temos outra forma de capturar as excepcións: [https://dev.mysql.com/doc/refman/5.6/en/get-diagnostics.html Mensaxes e códigos de erro en Mysql '''a partires da versión 5.6''']
| |
| − |
| |
| − |
| |
| − |
| |
| − | ==== Caso práctico: Transaccións en MYSQL dentro de procedementos almacenados ====
| |
| − |
| |
| − | * Imos crear un procedemento almacenado que dea de alta a un único autor xunto co libro.
| |
| − | : Partimos da base que xa coñecemos o id do libro e enviamos o nome e apelidos do autor.
| |
| − | : Isto leva consigo que o procedemento vai ter que dar de alta datos en dúas táboas: AUTORES e LIBROS_AUTORES.
| |
| − |
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | DELIMITER $$
| |
| − |
| |
| − | CREATE DEFINER=`root`@`localhost` PROCEDURE `INSERIR_AUTORES_LIBRO`(_id_libro int,_nome varchar(50),_apelido varchar(200))
| |
| − | BEGIN
| |
| − | DECLARE EXIT HANDLER FOR SQLEXCEPTION
| |
| − | BEGIN
| |
| − | ROLLBACK;
| |
| − | SELECT -1 as error;
| |
| − | END;
| |
| − |
| |
| − | START TRANSACTION;
| |
| − |
| |
| − | INSERT INTO AUTORES(nome,apelido)
| |
| − | VALUES (_nome,_apelido);
| |
| − |
| |
| − | INSERT INTO LIBROS_AUTORES (libro_id,autor_id)
| |
| − | VALUES (_id_libro,last_insert_id());
| |
| − |
| |
| − | COMMIT;
| |
| − | SELECT 0 AS error;
| |
| − |
| |
| − | END
| |
| − | $$
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − | === Caso práctico: Transaccións ===
| |
| − |
| |
| − | '''Exercicio Proposto:''' Asociar Libros a Autores.
| |
| − | : A idea é escoller dunha lista (<select>) un libro.
| |
| − | : Unha vez escollido e premido o botón 'AUTORES' desaparecerá a lista.
| |
| − | : Se indicará nun header o nome do libro e aparecerá nunha lista na que se van poder seleccionar varios autores, os autores da base de datos.
| |
| − | : Seleccionaremos un ou varios autores e premeremos o botón de ALTA.
| |
| − | : Isto levará consigo as operacións de alta sobre a táboa LIBROS_AUTORES de cada un dos autores escollidos.
| |
| − |
| |
| − |
| |
| − | * Melloras:
| |
| − | :* Posibilidade de dar de alta a novos autores. Estes autores non aparecerán la lista ata que se dean de alta.
| |
| − | :* Polo tanto aparecerán nunha checkboxlist con opción para ser eliminados (non da base de datos xa que aínda non foron engadidos). Poderiades gardar os datos nun array dentro dunha variable de sesión.
| |
| − | :* Ao premer o botón de ALTA, engadiranse á base de datos e se asociarán ao libro seleccionado.
| |
| − | :* Isto levará consigo as operacións de alta sobre a táboa AUTORES e LIBROS_AUTORES de cada un dos autores.
| |
| − |
| |
| − |
| |
| − |
| |
| − |
| |
| − | '''Exercicio proposto/resolto'''
| |
| − | * Imos crear unha páxina de nome '''Alta_Libro_Exemplares.php'''.
| |
| − | : Dita páxina terá un formulario onde indicaremos o título do libro e o número de exemplares que queremos dar de alta.
| |
| − | : Ao premer o botón de 'Alta' aparecerán en forma de lista (o podedes implementar cunha táboa) un formulario (unha por cada exemplar) cos datos: imaxe-prezo-estado.
| |
| − | : Os datos anteriores (título e número de exemplares) non se poderán cambiar.
| |
| − |
| |
| − | * Os campos do formulario serán dos seguintes tipo:
| |
| − | :* Estado: Será un combobox (select en html) cos estados da táboa estados.
| |
| − | :* Imaxe: cambiaremos isto máis adiante. Por agora deixade un campo de texto.
| |
| − | :* Prezo: Prezo da imaxe.
| |
| − |
| |
| − | * Como sempre deberedes validar os datos e que teñan o tipo/formato correcto.
| |
| − | * No segundo formulario aparecerá un botón de 'Alta' e outro de 'Cancelar'.
| |
| − | :* Ao premer o botón de alta daremos de alta o libro, obteremos o id autoxerado e usaremos ese id para dar de alta aos exemplares.
| |
| − | :* Se prememos o botón 'Cancelar' desaparecerá o segundo formulario e limparase o primeiro para que poidamos dar de alta un novo libro.
| |
| − |
| |
| − |
| |
| − | * '''POSIBLE SOLUCIÓN''' (algúns dos procedementos xa foron creados en solucións anteriores)
| |
| − |
| |
| − | '''MYSQL'''
| |
| − |
| |
| − | '''Procedemento: CONSULTAR_ESTADOS'''
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | DELIMITER $$
| |
| − | CREATE DEFINER=`root`@`localhost` PROCEDURE `CONSULTAR_ESTADOS`()
| |
| − | BEGIN
| |
| − | SELECT id_estado,descripcion
| |
| − | FROM ESTADOS
| |
| − | ORDER BY descripcion;
| |
| − |
| |
| − | END
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − |
| |
| − |
| |
| − |
| |
| − | '''Procedemento: INSERIR_LIBRO'''
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | DELIMITER $$
| |
| − |
| |
| − | CREATE DEFINER=`root`@`localhost` PROCEDURE `INSERIR_LIBRO`(_titulo varchar(300))
| |
| − | etiqueta:BEGIN -- Asociamos unha etiqueta ao código do procedemento para que poidamos facer algo parecido a un exit
| |
| − | declare existe int;
| |
| − |
| |
| − | SELECT count(*)
| |
| − | INTO existe
| |
| − | FROM LIBROS
| |
| − | WHERE upper(titulo)=upper(_titulo);
| |
| − |
| |
| − | IF existe=1 THEN
| |
| − | begin
| |
| − | SELECT -1 as valor_devolto;
| |
| − | leave etiqueta; -- Para que saia do procedemento
| |
| − | end;
| |
| − | END IF; -- Fin do IF
| |
| − |
| |
| − | INSERT INTO LIBROS (titulo)
| |
| − | VALUES (_titulo);
| |
| − |
| |
| − | SELECT last_insert_id() as valor_devolto;
| |
| − | END
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − |
| |
| − |
| |
| − |
| |
| − | '''Procedemento: INSERIR_EXEMPLAR'''
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | DELIMITER $$
| |
| − | CREATE DEFINER=`root`@`localhost` PROCEDURE `INSERIR_EXEMPLAR`(_libro_id int,_id_exemplar int,_imaxe varchar(100),_prezo decimal(6,2),_estado_id int)
| |
| − | BEGIN
| |
| − | INSERT INTO EXEMPLARES (libro_id,id_exemplar,imaxe,prezo,estado_id)
| |
| − | VALUES (_libro_id,_id_exemplar,_imaxe,_prezo,_estado_id);
| |
| − |
| |
| − | END
| |
| − | $$
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − |
| |
| − |
| |
| − |
| |
| − | '''Arquivo bd.inc.php''':
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | define ('PROCEDEMENTO_INSERIR_LIBRO',"INSERIR_LIBRO('%s')");
| |
| − | define ('PROCEDEMENTO_INSERIR_EXEMPLAR',"INSERIR_EXEMPLAR(%d,%d,'%s',%f,%d)");
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − |
| |
| − |
| |
| − |
| |
| − |
| |
| − | '''Arquivo Alta_Libro_Exemplares.php'''
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | <?php
| |
| − |
| |
| − | require ('/var/www/confBD/bd.inc.php');
| |
| − | //error_reporting(0);
| |
| − |
| |
| − | $conex = new mysqli(SERVIDOR,USUARIO,PASSWORD,BD);
| |
| − | $conex->set_charset('utf8');
| |
| − |
| |
| − | if ($conex->connect_error){
| |
| − | die('Erro de Conexión (' . mysqli_connect_errno() . ') ' . mysqli_connect_error());
| |
| − | }
| |
| − |
| |
| − | $error=''; // Usado para amosar as mensaxes de erro
| |
| − | // *********************************ALTA ************************************************************************//
| |
| − | if (!empty($_POST['btnAlta'])){ // DAMOS DE ALTA A EXAMPLARES E O LIBRO
| |
| − | foreach ($_POST['lstEstado'] as $indice => $estado){ // Usamos o índice para buscar os datos no resto dos campos
| |
| − | $prezo = $_POST['txtPrezo'][$indice];
| |
| − | $imaxe = $_POST['txtImaxe'][$indice];
| |
| − |
| |
| − | // Xa temos os datos de cada un dos exemplares
| |
| − | // Verifacamos que cumpran os criterios de tipo e formato => PRIMEIRO VALIDAMOS. SE SON CORRECTOS TODOS INICIAMOS A OPERACION DE ALTA
| |
| − | if (!filter_var($estado,FILTER_VALIDATE_INT) || $estado<0){
| |
| − | $error .= "O estado é incorrecto...";
| |
| − | break;
| |
| − | }
| |
| − | if (filter_var(str_replace(',', '.', $prezo),FILTER_VALIDATE_FLOAT)==false || $prezo<=0 || !preg_match("/[0-9]{1,6}(,[0-9]{1,2})?/",$prezo)){
| |
| − | $error .= "O prezo é incorrecto...";
| |
| − | break;
| |
| − | }
| |
| − | $imaxe=filter_var($imaxe,FILTER_SANITIZE_STRING);
| |
| − | echo strlen($imaxe);
| |
| − | if(((strlen($imaxe))>100) || (strlen($imaxe)==0)) { // O número de caracteres está establecido na táboa da base de datos
| |
| − | $error .= "O nome da imaxe é demasiado grande ou non foi posto...";
| |
| − | break;
| |
| − | }
| |
| − | // Gardamos os datos validados para dalos de alta todos xuntos
| |
| − | $dato['prezo'] = $prezo;
| |
| − | $dato['imaxe'] = $imaxe;
| |
| − | $dato['estado'] = $estado;
| |
| − | $datos[] = $dato;
| |
| − | } // Fin do for
| |
| − |
| |
| − |
| |
| − | if(empty($error)){ // Non houbo error, empezamos a operación de alta con transaccións
| |
| − | $conex->autocommit(false); // Iniciamos a transcción
| |
| − |
| |
| − | // Damos de alta ao libro e obtemos o id xerado
| |
| − | $titulo = $conex->real_escape_string($_POST['txtTitulo']);
| |
| − | $proc=sprintf(PROCEDEMENTO_INSERIR_LIBRO,$titulo);
| |
| − | $proc = sprintf("CALL %s",$proc);
| |
| − | if ($conex->multi_query($proc)){
| |
| − | $result = $conex->store_result();
| |
| − | if ($result && ($result->num_rows>0)){
| |
| − | $fila=$result->fetch_assoc();
| |
| − | switch($fila['valor_devolto']){
| |
| − | case -1:
| |
| − | $error = "Ese libro xa está dado de alta na base de datos...<br />";
| |
| − | break;
| |
| − | default:
| |
| − | $id_libro = $fila['valor_devolto']; // XA TEMOS O ID AUTONUMERICO DO LIBRO
| |
| − | }
| |
| − | $result->free();
| |
| − | while($conex->next_result() && $conex->store_result());
| |
| − | }
| |
| − | else{ // Sempre ten que devolver algo como temos implementado o proc.
| |
| − | $error = 'Houbo un erro na chamada á base de datos<br />';
| |
| − | }
| |
| − | }
| |
| − | else{
| |
| − | $error = 'Houbo un erro na chamada á base de datos <br/>';
| |
| − | }
| |
| − |
| |
| − |
| |
| − | if(empty($error)){ // Non houbo erro e temos o id do libro xerado
| |
| − | // Temos que dar de alta aos exemplares
| |
| − | $cont_num_exemplar=1; // Número de exemplar
| |
| − | foreach ($datos as $dato){
| |
| − | $prezo = $conex->real_escape_string($dato['prezo']);
| |
| − | $estado = $conex->real_escape_string($dato['estado']);
| |
| − | $imaxe = $conex->real_escape_string($dato['imaxe']);
| |
| − | $proc=sprintf(PROCEDEMENTO_INSERIR_EXEMPLAR,$id_libro,$cont_num_exemplar,$imaxe,$prezo,$estado);
| |
| − | $proc = sprintf("CALL %s",$proc);
| |
| − | if ($conex->multi_query($proc)){
| |
| − | $result = $conex->store_result(); // Neste caso o procedemento non devolve nada => vale 'false'
| |
| − | if ($conex->errno>0){
| |
| − | $error .= 'Houbo un erro na alta dos exemplares';
| |
| − | break; //Saimos do for
| |
| − | }
| |
| − | while($conex->next_result() && $conex->store_result());
| |
| − | }
| |
| − | else{
| |
| − | $error = 'Houbo un erro na chamada á base de datos:' . $conex->error;
| |
| − | break; //Saimos do for
| |
| − | }
| |
| − | $cont_num_exemplar+=1;
| |
| − | } // Fin do For por cada exemplar
| |
| − | }
| |
| − | if (!empty($error)){ // Fallos nas operacións
| |
| − | $conex->rollback();
| |
| − | }
| |
| − | else { // Todo correcto
| |
| − | $conex->commit();
| |
| − | }
| |
| − |
| |
| − | } // Fin de (empty($error)
| |
| − | }
| |
| − | // *********************************FIN DE ALTA ************************************************************************//
| |
| − |
| |
| − | ?>
| |
| − |
| |
| − |
| |
| − | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
| |
| − | "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
| |
| − | <html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es">
| |
| − | <head>
| |
| − | <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
| |
| − | <title>Transaccións</title>
| |
| − | </head>
| |
| − |
| |
| − | <body>
| |
| − | <?php
| |
| − | $titulo='';
| |
| − | $num_exemplares='';
| |
| − | if (!empty($_POST['btnEnviar'])) { // Prememos o botón que indica o título e o número de exemplares
| |
| − | $titulo = filter_var($_POST['txtTitulo'],FILTER_SANITIZE_STRING);
| |
| − | if (strlen($titulo)>200){ // Non pode ter unha lonxitude maior que o campo da base de datos
| |
| − | $error='A lonxitude do título non pode ser maior que 200 caracteres...<br />';
| |
| − | }
| |
| − | $num_exemplares = $_POST['txtNumExemplares'];
| |
| − | if (!filter_var($num_exemplares,FILTER_VALIDATE_INT) || $num_exemplares > 9 || $num_exemplares < 1){
| |
| − | $error.="O número de exemplares ten que ser numérico e non pode ser maior que 9<br />";
| |
| − | }
| |
| − | if (strlen($titulo)==0) {
| |
| − | $error.='O título é obrigatorio...<br />';
| |
| − | }
| |
| − | }
| |
| − | ?>
| |
| − |
| |
| − | <?php
| |
| − | $inhabilitar='';
| |
| − | if (!empty($_POST['btnEnviar']) && (empty($error))) { // Facemos que a caixa de texto e todo e demais quede inhabilitado
| |
| − | $inhabilitar="disabled='disabled'"; // Unha vez enviamos os datos do titulo e num exemplares
| |
| − | }
| |
| − | ?>
| |
| − | <form action="<?php echo htmlentities($_SERVER['PHP_SELF']) ?>" method='post'>
| |
| − | <div>Titulo Libro: <input <?php echo $inhabilitar ?> <?php printf(" value='%s'",$titulo); ?> type='text' size='100' maxlength="300" name='txtTitulo' /></div>
| |
| − | <div>Num. Exemplares: <input <?php echo $inhabilitar ?> <?php printf(" value='%s'",$num_exemplares); ?> type='number' min='1' max='9' size="1" maxlength="1" name='txtNumExemplares' /> (<10) </div>
| |
| − | <input <?php echo $inhabilitar ?> type="submit" value='Enviar' name='btnEnviar' />
| |
| − | </form>
| |
| − |
| |
| − | <?php
| |
| − | if (!empty($error)) { // En caso de atopar erros non amosa nada máis que a mensaxe
| |
| − | die("<h4>$error</h4>");
| |
| − | }
| |
| − | ?>
| |
| − |
| |
| − |
| |
| − | <?php
| |
| − | if ($titulo && $num_exemplares){ // Enviamos datos
| |
| − | // NON IMOS A FACER UNHA CONSULTA A TABOA ESTADOS POR CADA UN DOS EXEMPLARES. NON TEN SENTIDO.
| |
| − | // PODERIAMOS PERCORRER O ARRAY DE RESULTADOS E AO REMATAR VOLVER AO PRINCIPIO: http://php.net/manual/es/mysqli-result.data-seek.php
| |
| − | $proc = sprintf("CALL %s",PROCEDEMENTO_CONSULTAR_ESTADOS);
| |
| − | if ($conex->multi_query($proc)){
| |
| − | $result = $conex->store_result();
| |
| − | if ($result && ($result->num_rows>0)){
| |
| − | while($valor = $result->fetch_assoc()){
| |
| − | $estados[]=$valor;
| |
| − | }
| |
| − | $result->free();
| |
| − | while($conex->next_result() && $conex->store_result());
| |
| − | }
| |
| − | }
| |
| − | printf("<form action=\"%s\" method='post'>",htmlentities($_SERVER['PHP_SELF']));
| |
| − | printf("<input type='hidden' value='%s' name='txtTitulo' />",$_POST['txtTitulo']); // Como temos dous formularios, temos que gardar o texto do título nun campo oculto
| |
| − | printf ("<table>");
| |
| − | for($cont=0;$cont<$num_exemplares;$cont++){
| |
| − | echo "<th>Estado</th><th>Imaxe</th><th>Prezo</th>";
| |
| − | echo "<tr>";
| |
| − | echo "<td>"; // ESTADO
| |
| − | printf ("<select name='lstEstado[]>"); // Poñemos o nome en forma de array xa que imos recibir varios exemplares con datos.
| |
| − | foreach ($estados as $estado){
| |
| − | printf("<option value='%d'>%s</option>",$estado['id_estado'],$estado['descripcion']);
| |
| − | }
| |
| − | printf ("</select>");
| |
| − | echo "</td>";
| |
| − | echo "<td>"; // IMAXE
| |
| − | printf("<input type='text' size='50' maxlength='70' name='txtImaxe[]' />");
| |
| − | echo "</td>";
| |
| − | echo "<td>"; // PREZO
| |
| − | printf("<input type='text' size='9' maxlength='9' name='txtPrezo[]' /> Euros (XXXXXX,YY)");
| |
| − | echo "</td>";
| |
| − | echo "</tr>";
| |
| − | }
| |
| − |
| |
| − | printf ("</table>");
| |
| − | printf("<input type='submit' value='ALTA' name='btnAlta' />");
| |
| − | printf("<input type='reset' value='CANCELAR' name='btnCancelar' />");
| |
| − | printf("</form>");
| |
| − |
| |
| − | }
| |
| − | ?>
| |
| − | </body>
| |
| − | </html>
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − |
| |
| − |
| |
| − |
| |
| − |
| |
| − | * '''Exercicio proposto'''
| |
| − | : Tedes [http://wiki.cifprodolfoucha.es/index.php?title=PHP_Paso_de_datos_con_formularios#Gardando_imaxes_no_servidor neste enlace] da WIKI a forma de subir unha imaxe ao servidor. Modificade o código anterior para facelo.
| |
| − |
| |
| − |
| |
| − |
| |
| − | * '''Exercicio proposto'''
| |
| − | : Amplía a funcionalidade da páxina anterior para poder asociar a os autores cos libros na mesma operación de alta.
| |
| − | : Para iso fai que os autores se carguen nunha lista (<select con size en HTML>) ordenados alfabeticamente e que se poidan seleccionar varios.
| |
| − | : En caso de que os autores non estean dados de alta, poderás premer nun enlace que abrirá unha xanela nova (sen o toolbar, botóns,...) e que permitirá dar de alta a un novo autor. Ao pechar a xanela deberán refrescarse os datos da lista para amosar o novo autor, mantendo os datos previamente introducidos.
| |
| − |
| |
| − |
| |
| − |
| |
| − |
| |
| − |
| |
| − | ==== Variante Caso práctico: Transaccións con control de excepcións ====
| |
| − |
| |
| − | * É o mesmo exercicio que no punto anterior, pero utilizando [http://php.net/manual/es/language.exceptions.php Excepcións] dentro do bloque de instrucións que conforman a transacción.
| |
| − |
| |
| − | '''Arquivo Alta_Libro_Exemplares.php'''
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="45,56,64,68,84,89,97-103" >
| |
| − | <?php
| |
| − |
| |
| − | require ('/var/www/confBD/bd.inc.php');
| |
| − | //error_reporting(0);
| |
| − |
| |
| − | $conex = new mysqli(SERVIDOR,USUARIO,PASSWORD,BD);
| |
| − | $conex->set_charset('utf8');
| |
| − |
| |
| − | if ($conex->connect_error){
| |
| − | die('Erro de Conexión (' . mysqli_connect_errno() . ') ' . mysqli_connect_error());
| |
| − | }
| |
| − |
| |
| − | $error=''; // Usado para amosar as mensaxes de erro
| |
| − | // *********************************ALTA ************************************************************************//
| |
| − | if (!empty($_POST['btnAlta'])){ // DAMOS DE ALTA A EXAMPLARES E O LIBRO
| |
| − | foreach ($_POST['lstEstado'] as $indice => $estado){ // Usamos o índice para buscar os datos no resto dos campos
| |
| − | $prezo = $_POST['txtPrezo'][$indice];
| |
| − | $imaxe = $_POST['txtImaxe'][$indice];
| |
| − |
| |
| − | // Xa temos os datos de cada un dos exemplares
| |
| − | // Verifacamos que cumpran os criterios de tipo e formato => PRIMEIRO VALIDAMOS. SE SON CORRECTOS TODOS INICIAMOS A OPERACION DE ALTA
| |
| − | if (!filter_var($estado,FILTER_VALIDATE_INT) || $estado<0){
| |
| − | $error .= "O estado é incorrecto...";
| |
| − | break;
| |
| − | }
| |
| − | if (filter_var(str_replace(',', '.', $prezo),FILTER_VALIDATE_FLOAT)==false || $prezo<=0 || !preg_match("/[0-9]{1,6}(,[0-9]{1,2})?/",$prezo)){
| |
| − | $error .= "O prezo é incorrecto...";
| |
| − | break;
| |
| − | }
| |
| − | $imaxe=filter_var($imaxe,FILTER_SANITIZE_STRING);
| |
| − | if(((strlen($imaxe))>100) || (strlen($imaxe)==0)) { // O número de caracteres está establecido na táboa da base de datos
| |
| − | $error .= "O nome da imaxe é demasiado grande ou non foi posto...";
| |
| − | break;
| |
| − | }
| |
| − | // Gardamos os datos validados para dalos de alta todos xuntos
| |
| − | $dato['prezo'] = $prezo;
| |
| − | $dato['imaxe'] = $imaxe;
| |
| − | $dato['estado'] = $estado;
| |
| − | $datos[] = $dato;
| |
| − | } // Fin do for
| |
| − |
| |
| − |
| |
| − | if(empty($error)){ // Non houbo error, empezamos a operación de alta con transaccións
| |
| − | $conex->autocommit(false); // Iniciamos a transcción
| |
| − | try{
| |
| − | // Damos de alta ao libro e obtemos o id xerado
| |
| − | $titulo = $conex->real_escape_string($_POST['txtTitulo']);
| |
| − | $proc=sprintf(PROCEDEMENTO_INSERIR_LIBRO,$titulo);
| |
| − | $proc = sprintf("CALL %s",$proc);
| |
| − | if ($conex->multi_query($proc)){
| |
| − | $result = $conex->store_result();
| |
| − | if ($result && ($result->num_rows>0)){
| |
| − | $fila=$result->fetch_assoc();
| |
| − | switch($fila['valor_devolto']){
| |
| − | case -1:
| |
| − | throw new Exception("Ese libro xa está dado de alta na base de datos...<br />", 1); // Co 1 (true) indicamos que debemos facer rollback
| |
| − | default:
| |
| − | $id_libro = $fila['valor_devolto']; // XA TEMOS O ID AUTONUMERICO DO LIBRO
| |
| − | }
| |
| − | $result->free();
| |
| − | while($conex->next_result() && $conex->store_result());
| |
| − | }
| |
| − | else{ // Sempre ten que devolver algo como temos implementado o proc.
| |
| − | throw new Exception('Houbo un erro na chamada á base de datos<br />',1);
| |
| − | }
| |
| − | }
| |
| − | else{
| |
| − | throw new Exception('Houbo un erro na chamada á base de datos <br/>',1);
| |
| − | }
| |
| − |
| |
| − |
| |
| − | // Temos que dar de alta aos exemplares
| |
| − | $cont_num_exemplar=1; // Número de exemplar
| |
| − | foreach ($datos as $dato){
| |
| − | $prezo = $conex->real_escape_string($dato['prezo']);
| |
| − | $estado = $conex->real_escape_string($dato['estado']);
| |
| − | $imaxe = $conex->real_escape_string($dato['imaxe']);
| |
| − |
| |
| − | $proc=sprintf(PROCEDEMENTO_INSERIR_EXEMPLAR,$id_libro,$cont_num_exemplar,$imaxe,$prezo,$estado);
| |
| − | $proc = sprintf("CALL %s",$proc);
| |
| − | if ($conex->multi_query($proc)){
| |
| − | $result = $conex->store_result(); // Neste caso o procedemento non devolve nada => vale 'false'
| |
| − | if ($conex->errno>0){
| |
| − | throw new Exception('Houbo un erro na alta dos exemplares',1);
| |
| − | }
| |
| − | while($conex->next_result() && $conex->store_result());
| |
| − | }
| |
| − | else{
| |
| − | throw new Exception('Houbo un erro na chamada á base de datos:' . $conex->error,1); // Ao usuario non debemos amosarlle as mensaxes do xestor. Estamos na fase de desenvolvemento
| |
| − | }
| |
| − | $cont_num_exemplar+=1;
| |
| − | } // Fin do For por cada exemplar
| |
| − |
| |
| − |
| |
| − | $conex->commit(); // Todo correcto, confirmamos a transacción
| |
| − | $error="Libro e examplares dados de alta correctamente...";
| |
| − | } catch (Exception $ex) {
| |
| − | if ($ex->getCode()==1){
| |
| − | $conex->rollback();
| |
| − | }
| |
| − | $error = $ex->getMessage();
| |
| − |
| |
| − | }
| |
| − |
| |
| − |
| |
| − | } // Fin de (empty($error)
| |
| − | }
| |
| − | // *********************************FIN DE ALTA ************************************************************************//
| |
| − |
| |
| − | ?>
| |
| − |
| |
| − |
| |
| − | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
| |
| − | "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
| |
| − | <html xmlns="http://www.w3.org/1999/xhtml" lang="es" xml:lang="es">
| |
| − | <head>
| |
| − | <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
| |
| − | <title>Transaccións</title>
| |
| − | </head>
| |
| − |
| |
| − | <body>
| |
| − | <?php
| |
| − | $titulo='';
| |
| − | $num_exemplares='';
| |
| − | if (!empty($_POST['btnEnviar'])) { // Prememos o botón que indica o título e o número de exemplares
| |
| − | $titulo = filter_var($_POST['txtTitulo'],FILTER_SANITIZE_STRING);
| |
| − | if (strlen($titulo)>200){ // Non pode ter unha lonxitude maior que o campo da base de datos
| |
| − | $error='A lonxitude do título non pode ser maior que 200 caracteres...<br />';
| |
| − | }
| |
| − | $num_exemplares = $_POST['txtNumExemplares'];
| |
| − | if (!filter_var($num_exemplares,FILTER_VALIDATE_INT) || $num_exemplares > 9 || $num_exemplares < 1){
| |
| − | $error.="O número de exemplares ten que ser numérico e non pode ser maior que 9<br />";
| |
| − | }
| |
| − | if (strlen($titulo)==0) {
| |
| − | $error.='O título é obrigatorio...<br />';
| |
| − | }
| |
| − | }
| |
| − | ?>
| |
| − |
| |
| − | <?php
| |
| − | $inhabilitar='';
| |
| − | if (!empty($_POST['btnEnviar']) && (empty($error))) { // Facemos que a caixa de texto e todo e demais quede inhabilitado
| |
| − | $inhabilitar="disabled='disabled'"; // Unha vez enviamos os datos do titulo e num exemplares
| |
| − | }
| |
| − | ?>
| |
| − | <form action="<?php echo htmlentities($_SERVER['PHP_SELF']) ?>" method='post'>
| |
| − | <div>Titulo Libro: <input <?php echo $inhabilitar ?> <?php printf(" value='%s'",$titulo); ?> type='text' size='100' maxlength="300" name='txtTitulo' /></div>
| |
| − | <div>Num. Exemplares: <input <?php echo $inhabilitar ?> <?php printf(" value='%s'",$num_exemplares); ?> type='number' min='1' max='9' size="1" maxlength="1" name='txtNumExemplares' /> (<10) </div>
| |
| − | <input <?php echo $inhabilitar ?> type="submit" value='Enviar' name='btnEnviar' />
| |
| − | </form>
| |
| − |
| |
| − | <?php
| |
| − | if (!empty($error)) { // En caso de atopar erros non amosa nada máis que a mensaxe
| |
| − | die("<h4>$error</h4>");
| |
| − | }
| |
| − | ?>
| |
| − |
| |
| − |
| |
| − | <?php
| |
| − | if ($titulo && $num_exemplares){ // Enviamos datos
| |
| − | // NON IMOS A FACER UNHA CONSULTA A TABOA ESTADOS POR CADA UN DOS EXEMPLARES. NON TEN SENTIDO.
| |
| − | // PODERIAMOS PERCORRER O ARRAY DE RESULTADOS E AO REMATAR VOLVER AO PRINCIPIO: http://php.net/manual/es/mysqli-result.data-seek.php
| |
| − | $proc = sprintf("CALL %s",PROCEDEMENTO_CONSULTAR_ESTADOS);
| |
| − | if ($conex->multi_query($proc)){
| |
| − | $result = $conex->store_result();
| |
| − | if ($result && ($result->num_rows>0)){
| |
| − | while($valor = $result->fetch_assoc()){
| |
| − | $estados[]=$valor;
| |
| − | }
| |
| − | $result->free();
| |
| − | while($conex->next_result() && $conex->store_result());
| |
| − | }
| |
| − | }
| |
| − | printf("<form action=\"%s\" method='post'>",htmlentities($_SERVER['PHP_SELF']));
| |
| − | printf("<input type='hidden' value='%s' name='txtTitulo' />",$_POST['txtTitulo']); // Como temos dous formularios, temos que gardar o texto do título nun campo oculto
| |
| − | printf ("<table>");
| |
| − | for($cont=0;$cont<$num_exemplares;$cont++){
| |
| − | echo "<th>Estado</th><th>Imaxe</th><th>Prezo</th>";
| |
| − | echo "<tr>";
| |
| − | echo "<td>"; // ESTADO
| |
| − | printf ("<select name='lstEstado[]>"); // Poñemos o nome en forma de array xa que imos recibir varios exemplares con datos.
| |
| − | foreach ($estados as $estado){
| |
| − | printf("<option value='%d'>%s</option>",$estado['id_estado'],$estado['descripcion']);
| |
| − | }
| |
| − | printf ("</select>");
| |
| − | echo "</td>";
| |
| − | echo "<td>"; // IMAXE
| |
| − | printf("<input type='text' size='50' maxlength='70' name='txtImaxe[]' />");
| |
| − | echo "</td>";
| |
| − | echo "<td>"; // PREZO
| |
| − | printf("<input type='text' size='9' maxlength='9' name='txtPrezo[]' /> Euros (XXXXXX,YY)");
| |
| − | echo "</td>";
| |
| − | echo "</tr>";
| |
| − | }
| |
| − |
| |
| − | printf ("</table>");
| |
| − | printf("<input type='submit' value='ALTA' name='btnAlta' />");
| |
| − | printf("<input type='reset' value='CANCELAR' name='btnCancelar' />");
| |
| − | printf("</form>");
| |
| − |
| |
| − | }
| |
| − | ?>
| |
| − | </body>
| |
| − | </html>
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − | == Permisos mínimos ==
| |
| − |
| |
| − | * Sempre debemos ter en mente a seguridade de acceso aos datos gardados nunha base de datos.
| |
| − |
| |
| − | : Ata o de agora, o usuario que usamos para acceder á base de datos tiña asinado todos os permisos sobre a mesma, pero esta práctica non é aconsellable.
| |
| − | : O ideal é que teña asinado os permisos mínimos necesarios para que poida utilizar a aplicación.
| |
| − |
| |
| − |
| |
| − | * Cando creamos unha aplicación WEB podemos ter varios supostos no que se refire ao acceso á base de datos.
| |
| − | :* Temos creado en MYSQL un usuario diferente por cada usuario, de tal forma que a cadea de conexión á base de datos a crearemos en base ao usuario/password introducido dende unha páxina PHP.
| |
| − | :* Utilizamos o mesmo usuario MYSQL para acceder á base de datos e temos unha táboa de usuario/password. En función do usuario validado, este terá acceso a diferentes páxinas PHP con diferentes funcionalidades.
| |
| − | :* Utilizamos o mesmo usuario MYSQL para acceder á base de datos e todos os usuarios do sitio web poden ter acceso ás mesmas páxinas.
| |
| − |
| |
| − | * En calquera destes supostos, o usuario que empreguemos para acceder a MYSQL debe ter os permisos mínimos para poder funcionar no noso sitio web.
| |
| − |
| |
| − |
| |
| − |
| |
| − |
| |
| − | * Partimos de que temos creado un usuario en Mysql, que vai ser o usuario que imos utilizar no noso sitio web para conectarnos a MYSQL e queremos darlle os permisos mínimos.
| |
| − |
| |
| − |
| |
| − | * '''CASO 1:''' A nosa aplicación fai as operacións contra a base de datos empregando sentenzas SQL (SELECT/INSERT/UPDATE/DELETE) deberemos outorgar os permisos e so os permisos necesarios para facer as operacións sobre as táboas da nosa base de datos.
| |
| − |
| |
| − | :* Para facelo deberemos facer uso da [https://dev.mysql.com/doc/refman/5.5/en/grant.html orde grant] e [https://dev.mysql.com/doc/refman/5.5/en/revoke.html orde revoke].
| |
| − | :: A primeira (grant) serve para outorgar un permiso a un usuario.
| |
| − | :: A segunda (revoke) serve para quitar un permiso a un usuario.
| |
| − |
| |
| − | :* No caso de SELECT e UPDATE, podemos outorgar permisos a nivel de columnas, de tal forma que podemos especificar que columnas dunha táboa vai ter permiso para poder ser seleccionadas ou actualizadas.
| |
| − |
| |
| − |
| |
| − | : Se estamos a utilizar o WorkBench, podemos asociar os permisos graficamente.
| |
| − |
| |
| − | [[Imagen:Php_Perm_Minimos_1.jpg|600px|center]]
| |
| − |
| |
| − | ::* Prememos na sección 'User and privileges' (1).
| |
| − | ::* Prememos o botón 'Add entry' e escollemos o schema (base de datos) sobre a que queremos outorgar os permisos (2).
| |
| − | ::* Aparecerá o schema na lista (3)^.
| |
| − | ::* Agora na parte baixa aparecerán os permisos que podemos outorgar. Fixarse que os marcados con (4) son os permisos de SELECT/INSERT/UPDATE/DELETE
| |
| − |
| |
| − | :* Se eliximos este 'tipo de permisos' estaremos dando o permiso sobre todas as táboas creadas na base de datos.
| |
| − |
| |
| − |
| |
| − | : Se estamos a utilizar a '''ferramenta Phpmyadmin''' poderemos facer o mesmo <u>seleccionado a base de datos</u> e indo a opción '''Privilegios'''. Aparecerá o usuario creado e poderemos editar os seus permisos.
| |
| − | [[Imagen:Drupal_instalacion_mysql_7.jpg|600px|center]]
| |
| − |
| |
| − |
| |
| − |
| |
| − |
| |
| − | :* Se queremos dar un permiso individual a unha táboa usaremos a orde '''grant'''. <u>Esta será a opción a escoller se queremos otorgar un <b>permisos mínimos</b> a nosa base de datos</u>:
| |
| − | ::<u>Nota:</u> A orde grant, cando fai referencia a un obxecto (coma unha táboa) pode indicarse o nome da base de datos e o nome do obxecto desta forma: bd.obxecto
| |
| − | ::: Nos exemplos seguintes omitimos o nome da base de datos e supoñemos que xa está seleccionada (activa) a base de datos sobre a que imos a executar as ordes.
| |
| − |
| |
| − | :::Permiso 'select' sobre unha táboa.
| |
| − | :::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | grant select on TABOA to 'novo_usuario';
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − | :::Permiso 'select' sobre unha columna dunha táboa.
| |
| − | :::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | grant select (columna) on TABOA to 'novo_usuario';
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − | :::Permiso 'update' sobre unha táboa.
| |
| − | :::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | grant update on TABOA to 'novo_usuario';
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − | :::Permiso 'update' sobre unha columna dunha táboa.
| |
| − | :::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | grant update (columna) on TABOA to 'novo_usuario';
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − | :: Para os permisos DELETE e INSERT soamente é necesario poñer na orde grant a operación igual que nos casos anteriores.
| |
| − |
| |
| − |
| |
| − |
| |
| − |
| |
| − |
| |
| − | * '''CASO 2:''' A nosa aplicación fai as operacións contra a base de datos empregando <u>procedementos almacenados</u>.
| |
| − |
| |
| − | :* No caso anterior, aínda que melloramos a seguridade, o usuario aínda ten acceso ás táboas directamente e pode ver a estrutura das mesmas.
| |
| − | :: Se queremos aumentar a seguridade podemos utilizar procedementos almacenados.
| |
| − |
| |
| − |
| |
| − | :* Xa vimos anteriormente que empregar ditos procedementos leva consigo:
| |
| − | ::* Mellor rendemento xa que as sentenzas xa están compiladas e analizadas polo xestor.
| |
| − | ::* Mellor seguridade xa que é imposible facer ataques de inxección de código.
| |
| − | ::* Mellor seguridade xa que o usuario non pode ver a estrutura de táboas.
| |
| − | ::* Mantemento máis doado, xa que podo cambiar a estrutura de táboas sempre que o procedemento continúe devolvendo o mesmo ao usuario.
| |
| − |
| |
| − |
| |
| − | * <u>IMPORTANTE</u>: Se utilizamos este tipo de 'seguridade' xa non necesitamos outorgar ningún tipo de permiso sobre as táboas.
| |
| − |
| |
| − | :* O permiso que debemos outorgar para que poida executar un procedemento almacenado é 'EXECUTE'.
| |
| − | ::* Podemos facelo como no punto anterior facendo uso do WORKBENCH (neste caso daremos permiso de execución sobre todos os procedementos almacenados).
| |
| − | ::* Usar a orde '''grant''':
| |
| − |
| |
| − | ::: Dando permiso de execución a todos os procedementos almacenados.
| |
| − | :::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | grant execute on *.* to 'novo_usuario'
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − | ::: Dando permiso de execución a un procedemento almacenado.
| |
| − | :::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | grant execute on procedure NOME_PROCEDEMENTO to 'novo_usuario'
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − | == Optimización: Sentenzas Prepared ==
| |
| − |
| |
| − | * A idea das sentenzas 'prepared' é a de mandar previamente ao Mysql a sentenza que imos executar pero sen datos.
| |
| − | : Despois asociaremos a dita sentenza 'prepared' os datos e a executaremos.
| |
| − |
| |
| − | : Por exemplo:
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | select col1 from TABOA1
| |
| − | where col2=?
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − | : Neste caso, a sentenza a executar leva un parámetro (o indicado pola interrogación).
| |
| − | : Este parámetro se asociará posteriormente e se executará a sentenza. Podemos executar a sentenza moitas veces cambiando o valor do parámetro.
| |
| − |
| |
| − |
| |
| − | * Preparar as sentenzas ten as vantaxes:
| |
| − | :* É máis rápido que enviar as sentenzas da forma 'tradicional' sobre todo cando repetimos a mesma sentenza (con valores diferentes) varias veces. Isto é debido a que cando 'preparamos' a sentenza a estamos a enviar ao xestor MYSQL o cal xa fai as operacións de análise da sentenza e outras optimizacións. Isto soamente o terá que facer unha vez.
| |
| − | :* Maior seguridade, xa que fai imposible a inxección de código (o mesmo que pasa cos procedementos almacenados) como [http://wiki.cifprodolfoucha.es/index.php?title=PHP_Paso_de_datos_con_formularios#Inxecci.C3.B3n_de_CSS vimos anteriormente nesta wiki].
| |
| − |
| |
| − |
| |
| − |
| |
| − | === PHP ===
| |
| − | [http://php.net/manual/es/mysqli.prepare.php Enlace].
| |
| − |
| |
| − |
| |
| − |
| |
| − | === MySQL ===
| |
| − | [http://download.nust.na/pub6/mysql/doc/refman/5.4/en/sql-syntax-prepared-statements.html Enlace].
| |
| − |
| |
| − | * Dende o propio xestor tamén se poden 'executar' sentenzas 'prepared'.
| |
| − |
| |
| − | * Esta forma pode sernos moi útil se queremos executar sentenzas SQL que se cren dinamicamente dentro dun procedemento almacenado.
| |
| − |
| |
| − | * Por exemplo, se temos unha páxina de busca en base a varios criterios (por exemplo, nome e idade). A sentenza SQL terá unha parte WHERE que pode ser:
| |
| − | :* nome=valor1
| |
| − | :* idade=valor2
| |
| − | :* nome=valor1 and idade=valor2
| |
| − |
| |
| − | : Vai depender se enviamos ou non o dato teremos que executar unha instrución ou outra.
| |
| − | : En vez de ter 3 instrucións SQL dentro do procedemento almacenado en base a se enviamos ou non o dato, podemos 'construír' a sentenza SQL coma unha cadea e cando a teñamos executala con 'PREPARED'
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | DELIMITER $$
| |
| − |
| |
| − | CREATE DEFINER=`root`@`localhost` PROCEDURE `proba`(_apelido varchar(100),_nome varchar(50))
| |
| − | BEGIN
| |
| − |
| |
| − | set @cadea = 'select nome,apelido from AUTORES where 1=1';
| |
| − | if (_apelido is not null) then
| |
| − | set @cadea=concat(@cadea,' and apelido=','\'', _apelido,'\'');
| |
| − | end if;
| |
| − | if (_nome is not null) then
| |
| − | set @cadea=concat(@cadea,' and nome=','\'', _nome,'\'');
| |
| − | end if;
| |
| − |
| |
| − | prepare stmt from @cadea;
| |
| − |
| |
| − | execute stmt;
| |
| − |
| |
| − | deallocate prepare stmt;
| |
| − |
| |
| − |
| |
| − | END
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − | :* Neste caso o procedemento buscará nunha táboa AUTORES por un nome, apelido ou nome e apelido concreto. En caso de enviar null nos dous parámetros, seleccionará todas as filas.
| |
| − | :: Fixarse nos espazos en branco cando concatenedes xa que tedes que formar a frase: WHERE 1=1 and cond=valor. Entre o 1=1 e a condición ten que ir un espazo en branco e o mesmo se engadides máis condicións.
| |
| − |
| |
| − | == Gardando campos a protexer na base de datos ==
| |
| − |
| |
| − | * En moitas ocasións imos necesitar gardar nunha base de datos información que non queremos que sexa 'visible' en caso de que alguén poida acceder a nosa base de datos.
| |
| − |
| |
| − | * Un exemplo típico é o das contrasinais dos usuarios do noso sitio web, no caso de implantar un sitio web cunha validación de usuarios.
| |
| − |
| |
| − | * Información sobre a [https://www.schneier.com/blog/archives/2006/12/realworld_passw.html seguridade das passwords].
| |
| − | : Como normas, nunca debe estar baseada nun dicionario.
| |
| − | : Debe levar caracteres alfanuméricos, maiúsculas e minúsculas e algún carácter non alfanumérico.
| |
| − | : Debe ser de a lo menos 10 ou máis caracteres
| |
| − |
| |
| − |
| |
| − |
| |
| − | * Para gardar a información dunha forma segura temos que aplicar un [https://es.wikipedia.org/wiki/Funci%C3%B3n_hash algoritmo de hash].
| |
| − |
| |
| − | : En PHP temos dispoñible a partires da versión 5, [http://php.net/manual/es/function.hash.php a función de hash].
| |
| − |
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | <?php
| |
| − | echo hash('ripemd160', 'The quick brown fox jumped over the lazy dog.');
| |
| − | ?>
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − | :: Dará como resultado:
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | ec457d0a974c48d5685a7efa03d137dc8bbde7e3
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − |
| |
| − | * En PHP temos funcións de hash específicas dun tipo de algoritmo. Por exemplo a [http://php.net/manual/es/function.md5.php función md5].
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | <?php
| |
| − | echo md5('The quick brown fox jumped over the lazy dog.') // As dúas funcións devolven o mesmo
| |
| − | echo hash('md5', 'The quick brown fox jumped over the lazy dog.'); // As dúas funcións devolven o mesmo
| |
| − | ?>
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − |
| |
| − |
| |
| − | * Pero esta forma de 'tratar' a información non chega, xa que se podería descubrir o valor orixinal pola forza bruta.
| |
| − |
| |
| − | : Para complicalo un pouco máis debemos facer uso de '''SALT'.
| |
| − |
| |
| − | * Salt é unha cadea que se aplica no proceso de hash e fai que sexa practicamente imposible utilizar mecanismos de forza bruta ou [https://es.wikipedia.org/wiki/Tabla_arco%C3%ADris Táboas Rainbow] para obter a cadea orixinal.
| |
| − |
| |
| − |
| |
| − | * Exemplo de como utilizar salt na función md5.
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | <?php
| |
| − | $password='q121ahgsha..'; // Password introducida polo usuario. Viría dun formulario con $_POST['txtPassword'].
| |
| − | $cadeaSalt='unha cadea ao chou';
| |
| − |
| |
| − | echo md5($cadeaSalt,$password)
| |
| − | ?>
| |
| − | </syntaxhighlight>
| |
| − | : Cabe sinalar que se a contrasinal é débil, é posible chegar a rompela, polo que é moi importante que a contrasinal sexa 'forte'.
| |
| − | : A cadea SALT pode ser calquera cadea e mellor que non estea baseada en ningún dicionario.
| |
| − |
| |
| − |
| |
| − | '''Opción recomendada'''
| |
| − |
| |
| − | * A cadea SALT a podemos proporcionar nos ou podemos xerar unha por medio da [http://php.net/manual/es/function.password-hash.php función password_hash].
| |
| − |
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | $password = password_hash('contrasinal_a_codificar', PASSWORD_DEFAULT);
| |
| − | </syntaxhighlight>
| |
| − |
| |
| − | : Codifica a contrasinal utilizando o algoritmo bcrypt e o resultado é unha cadea de 60 caracteres de lonxitude. Seguramente en versións posteriores, o algoritmo vai devolve lonxitudes máis grandes, polo que é <u>recomendable gardar a contrasinal nun campo de 256 caracteres de lonxitede</u>.
| |
| − |
| |
| − | : Esta forma de codificar fai que o valor SALT cambie de cada vez.
| |
| − |
| |
| − |
| |
| − | : O resultado será o valor que teremos que gardar na base de datos.
| |
| − |
| |
| − |
| |
| − | * Para realizar o proceso contrario, e comprobar se o password introducido por un usuario se corresponde con o gardado na base de datos, teremos que facer uso da [http://php.net/manual/es/function.password-verify.php función password_verify()].
| |
| − |
| |
| − | ::<syntaxhighlight lang="java" line enclose="div" highlight="" >
| |
| − | $orixinal = 'password_introdicada_polo_usuario';
| |
| − | $codificado = '2y$10$ETpCoo11Gd3w2kx4x1me1OQtx.p67c6eRzg6OSRgPxUsBGaQnff4W';
| |
| − |
| |
| − | $iguais = password_verify($orixinal, $codificado);
| |
| − |
| |
| − | if ($iguais) {
| |
| − | echo 'Password correcto';
| |
| − | } else {
| |
| − | echo 'Password incorrecto';
| |
| − | }</syntaxhighlight>
| |
| − |
| |
| − |
| |
| − |
| |
| − |
| |
| − |
| |
| − | * Máis información [http://php.net/manual/en/faq.passwords.php neste enlace].
| |
| − |
| |
| − |
| |
| − |
| |
| − |
| |
| − |
| |
| − | <br />
| |
| | == Subindo unha imaxe ao servidor == | | == Subindo unha imaxe ao servidor == |
| | | | |