diff --git a/src/Peachpie.Library.MySql/MySqlConnectionManager.cs b/src/Peachpie.Library.MySql/MySqlConnectionManager.cs
index db19ee14f3..3e5b56df43 100644
--- a/src/Peachpie.Library.MySql/MySqlConnectionManager.cs
+++ b/src/Peachpie.Library.MySql/MySqlConnectionManager.cs
@@ -29,5 +29,11 @@ internal MySqlConnectionResource CreateConnection(IDbConnection dbconnection)
return connection;
}
+
+ public virtual void ReportException(Exception exception, string exceptionMessage)
+ {
+ // MySql outputs the error to php error handler
+ PhpException.Throw(PhpError.Warning, exceptionMessage);
+ }
}
}
diff --git a/src/Peachpie.Library.MySql/MySqlConnectionResource.cs b/src/Peachpie.Library.MySql/MySqlConnectionResource.cs
index 429890d7d6..8eb03d9359 100644
--- a/src/Peachpie.Library.MySql/MySqlConnectionResource.cs
+++ b/src/Peachpie.Library.MySql/MySqlConnectionResource.cs
@@ -168,5 +168,26 @@ internal long LastInsertedId
return command != null ? MySqlExtensions.LastInsertedId(command) : -1;
}
}
+
+ public override int GetLastErrorNumber()
+ {
+ if (LastException == null)
+ {
+ return (int)MySqlErrorCode.None; // success
+ }
+ else if (LastException is MySqlException me)
+ {
+ return (int)me.ErrorCode;
+ }
+ else
+ {
+ return (int)MySqlErrorCode.UnknownError; // unk erro number
+ }
+ }
+
+ protected override void ReportException(Exception exception, string exceptionMessage)
+ {
+ _manager.ReportException(exception, exceptionMessage);
+ }
}
}
diff --git a/src/Peachpie.Library.MySql/MySqli/Constants.cs b/src/Peachpie.Library.MySql/MySqli/Constants.cs
index c93e64e7bf..1f1295dcad 100644
--- a/src/Peachpie.Library.MySql/MySqli/Constants.cs
+++ b/src/Peachpie.Library.MySql/MySqli/Constants.cs
@@ -248,20 +248,30 @@ public static class Constants
///
public const int MYSQLI_SET_CHARSET_NAME = 7;
- //MYSQLI_REPORT_INDEX
- //Report if no index or bad index was used in a query.
+ ///
+ /// Turns reporting off.
+ ///
+ public const int MYSQLI_REPORT_OFF = 0;
- //MYSQLI_REPORT_ERROR
- //Report errors from mysqli function calls.
+ ///
+ /// Report errors from mysqli function calls.
+ ///
+ public const int MYSQLI_REPORT_ERROR = 1;
- //MYSQLI_REPORT_STRICT
- //Throw a mysqli_sql_exception for errors instead of warnings.
+ ///
+ /// Throw a for errors instead of warnings.
+ ///
+ public const int MYSQLI_REPORT_STRICT = 2;
- //MYSQLI_REPORT_ALL
- //Set all options on (report all).
+ ///
+ /// Report if no index or bad index was used in a query.
+ ///
+ public const int MYSQLI_REPORT_INDEX = 4;
- //MYSQLI_REPORT_OFF
- //Turns reporting off.
+ ///
+ /// Set all options on (report all).
+ ///
+ public const int MYSQLI_REPORT_ALL = 0xff;
//MYSQLI_DEBUG_TRACE_ENABLED
//Is set to 1 if mysqli_debug() functionality is enabled.
diff --git a/src/Peachpie.Library.MySql/MySqli/Functions.cs b/src/Peachpie.Library.MySql/MySqli/Functions.cs
index 9c5527430f..06d0de6b5e 100644
--- a/src/Peachpie.Library.MySql/MySqli/Functions.cs
+++ b/src/Peachpie.Library.MySql/MySqli/Functions.cs
@@ -12,16 +12,6 @@ namespace Peachpie.Library.MySql.MySqli
[PhpExtension(Constants.ExtensionName)]
public static class Functions
{
- ///
- /// Internal object used to store per-request (context) data.
- ///
- internal class MySqliContextData
- {
- public static MySqliContextData/*!*/GetContextData(Context ctx) => ctx.GetStatic();
-
- public string LastConnectionError { get; set; }
- }
-
///
/// Initializes MySQLi and returns a resource for use with mysqli_real_connect().
///
@@ -96,7 +86,7 @@ public static mysqli mysqli_connect(Context ctx, IDbConnection dbconnection/*, b
/// The connection error message. Otherwise null.
///
public static string mysqli_connect_error(Context ctx, mysqli link = null)
- => (link != null) ? link.connect_error : MySqliContextData.GetContextData(ctx).LastConnectionError;
+ => (link != null) ? link.connect_error : MySqliConnectionManager.GetInstance(ctx).LastConnectionError;
///
/// Returns the error code from last connect call.
@@ -104,7 +94,7 @@ public static string mysqli_connect_error(Context ctx, mysqli link = null)
public static int mysqli_connect_errno(Context ctx, mysqli link = null)
=> (link != null)
? link.connect_errno
- : string.IsNullOrEmpty(MySqliContextData.GetContextData(ctx).LastConnectionError) ? 0 : -1;
+ : string.IsNullOrEmpty(MySqliConnectionManager.GetInstance(ctx).LastConnectionError) ? 0 : -1;
///
/// Returns the error code for the most recent function call.
@@ -369,10 +359,13 @@ public static bool mysqli_ssl_set(mysqli link, string key = null, string cert =
///
/// Sets mysqli error reporting mode.
///
- public static bool mysqli_report(int flags)
+ public static bool mysqli_report(Context ctx, ReportMode flags)
{
- PhpException.FunctionNotSupported(nameof(mysqli_report));
- return false;
+ var manager = MySqliConnectionManager.GetInstance(ctx);
+
+ manager.ReportMode = flags;
+
+ return true;
}
}
}
diff --git a/src/Peachpie.Library.MySql/MySqli/MySqliConnectionManager.cs b/src/Peachpie.Library.MySql/MySqli/MySqliConnectionManager.cs
new file mode 100644
index 0000000000..d71f9b332b
--- /dev/null
+++ b/src/Peachpie.Library.MySql/MySqli/MySqliConnectionManager.cs
@@ -0,0 +1,69 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using MySqlConnector;
+using Pchp.Core;
+
+namespace Peachpie.Library.MySql.MySqli
+{
+ class MySqliConnectionManager : MySqlConnectionManager
+ {
+ ///
+ /// Gets the manager singleton within runtime context.
+ ///
+ public static new MySqliConnectionManager GetInstance(Context ctx) => ctx.GetStatic();
+
+ public string LastConnectionError { get; set; }
+
+ ///
+ /// The error handling mode.
+ ///
+ public ReportMode ReportMode { get; set; } = ReportMode.Default;
+
+ public override void ReportException(Exception exception, string exceptionMessage)
+ {
+ if (exception == null)
+ {
+ throw new ArgumentNullException(nameof(exception));
+ }
+
+ if ((ReportMode & ReportMode.Error) != 0)
+ {
+ if ((ReportMode & ReportMode.Strict) != 0)
+ {
+ var errcode = exception is MySqlException me ? me.ErrorCode : MySqlErrorCode.UnknownError;
+
+ throw new mysqli_sql_exception(exceptionMessage, (int)errcode);
+ }
+ else
+ {
+ // outputs the error to php error handler
+ PhpException.Throw(PhpError.Warning, exceptionMessage);
+ }
+ }
+ }
+ }
+
+ ///
+ /// MYSQLI_REPORT_*** constants.
+ ///
+ [PhpHidden, Flags]
+ public enum ReportMode
+ {
+ ///
+ Off = Constants.MYSQLI_REPORT_OFF,
+ ///
+ Error = Constants.MYSQLI_REPORT_ERROR,
+ ///
+ Strict = Constants.MYSQLI_REPORT_STRICT,
+ ///
+ Index = Constants.MYSQLI_REPORT_INDEX,
+ ///
+ All = Constants.MYSQLI_REPORT_ALL,
+
+ /// Default error handling flags.
+ Default = Error | Strict, // as it is in PHP 8.1
+ }
+}
diff --git a/src/Peachpie.Library.MySql/MySqli/mysqli.cs b/src/Peachpie.Library.MySql/MySqli/mysqli.cs
index a8ac5eed00..4adad1640b 100644
--- a/src/Peachpie.Library.MySql/MySqli/mysqli.cs
+++ b/src/Peachpie.Library.MySql/MySqli/mysqli.cs
@@ -52,7 +52,7 @@ internal mysqli(Context ctx, IDbConnection dbconnection)
{
// create connection resource and
// register it in the list of active connections
- this.Connection = MySqlConnectionManager
+ this.Connection = MySqliConnectionManager
.GetInstance(ctx)
.CreateConnection(dbconnection);
}
@@ -306,6 +306,7 @@ public PhpValue query(PhpString query, int resultmode = Constants.MYSQLI_STORE_R
public bool real_connect(Context ctx, string host = null, string username = null, string passwd = null, string dbname = "", int port = -1, string socket = null, int flags = 0)
{
var config = ctx.Configuration.Get();
+ var manager = MySqliConnectionManager.GetInstance(ctx);
// string $host = ini_get("mysqli.default_host")
// string $username = ini_get("mysqli.default_user")
@@ -346,12 +347,12 @@ public bool real_connect(Context ctx, string host = null, string username = null
}
}
- Connection = MySqlConnectionManager.GetInstance(ctx)
- .CreateConnection(connection_string.ToString(), true, -1, out bool success);
+ Connection = manager.CreateConnection(connection_string.ToString(), true, -1, out bool success);
if (success)
{
Connection.Server = host;
+ manager.LastConnectionError = null;
if (!string.IsNullOrEmpty(dbname))
{
@@ -360,9 +361,7 @@ public bool real_connect(Context ctx, string host = null, string username = null
}
else
{
- MySqliContextData.GetContextData(ctx).LastConnectionError
- = connect_error
- = Connection.GetLastErrorMessage();
+ manager.LastConnectionError = connect_error = Connection.GetLastErrorMessage();
}
//
diff --git a/src/Peachpie.Library/Database/ConnectionManager.cs b/src/Peachpie.Library/Database/ConnectionManager.cs
index a7b6e3ae4f..7dceae8a58 100644
--- a/src/Peachpie.Library/Database/ConnectionManager.cs
+++ b/src/Peachpie.Library/Database/ConnectionManager.cs
@@ -16,8 +16,7 @@ public abstract class ConnectionManager : IStaticInit where TConnec
///
/// Associated runtime context.
///
- public Context Context => _ctx;
- Context _ctx;
+ public Context Context { get; private set; }
///
/// List of connections established by the manager.
@@ -69,7 +68,7 @@ public TConnection CreateConnection(string connectionString, bool newConnection,
if ((success = connection.Connect()) == true)
{
AddConnection(connection);
- _ctx.RegisterDisposable(connection);
+ this.Context.RegisterDisposable(connection);
}
return connection;
@@ -108,7 +107,7 @@ protected void AddConnection(TConnection connection)
public bool RemoveConnection(TConnection connection)
{
//
- _ctx.UnregisterDisposable(connection);
+ this.Context.UnregisterDisposable(connection);
//
if (_connections.Count != 0)
@@ -132,7 +131,7 @@ public TConnection GetLastConnection()
void IStaticInit.Init(Context ctx)
{
- _ctx = ctx;
+ this.Context = ctx;
}
}
}
diff --git a/src/Peachpie.Library/Database/ConnectionResource.cs b/src/Peachpie.Library/Database/ConnectionResource.cs
index 5a17c9a365..cbbcc055f3 100644
--- a/src/Peachpie.Library/Database/ConnectionResource.cs
+++ b/src/Peachpie.Library/Database/ConnectionResource.cs
@@ -29,15 +29,38 @@ public abstract class ConnectionResource : PhpResource
///
/// Last result resource.
///
- public ResultResource LastResult => _lastResult;
- private ResultResource _lastResult;
+ public ResultResource LastResult { get; private set; }
+
+ ///
+ /// Handle exception thrown by the last DB operation.
+ ///
+ /// Thrown exception
+ /// Optional formatted message to be reported.
+ private void OnException(Exception exception, string exceptionMessage)
+ {
+ LastException = exception;
+
+ ReportException(exception, exceptionMessage ?? exception.Message);
+ }
+
+ protected virtual void ReportException(Exception exception, string exceptionMessage)
+ {
+ PhpException.Throw(PhpError.Warning, exceptionMessage);
+ }
+
+ ///
+ /// Handle success by the last DB operation.
+ ///
+ protected virtual void OnSuccess()
+ {
+ LastException = null;
+ }
///
/// Gets an exception thrown by last performed operation or a null reference
/// if that operation succeeded.
///
- public Exception LastException => _lastException;
- protected Exception _lastException;
+ public Exception LastException { get; protected set; }
///
/// Gets the number of rows affected by the last query executed on this connection.
@@ -46,10 +69,10 @@ public int LastAffectedRows
{
get
{
- if (_lastResult == null) return -1;
+ if (LastResult == null) return -1;
// SELECT gives -1, UPDATE/INSERT gives the number:
- return (_lastResult.RecordsAffected >= 0) ? _lastResult.RecordsAffected : _lastResult.RowCount;
+ return (LastResult.RecordsAffected >= 0) ? LastResult.RecordsAffected : LastResult.RowCount;
}
}
@@ -85,12 +108,12 @@ public virtual bool Connect()
try
{
ActiveConnection.Open(); // TODO: Async
- _lastException = null;
+
+ OnSuccess();
}
catch (Exception e)
{
- _lastException = e;
- PhpException.Throw(PhpError.Warning, LibResources.cannot_open_connection, GetExceptionMessage(e));
+ OnException(e, string.Format(LibResources.cannot_open_connection, GetExceptionMessage(e)));
return false;
}
@@ -112,12 +135,11 @@ protected override void FreeManaged()
connection.Close();
}
- _lastException = null;
+ OnSuccess();
}
catch (Exception e)
{
- _lastException = e;
- PhpException.Throw(PhpError.Warning, LibResources.error_closing_connection, GetExceptionMessage(e));
+ OnException(e, string.Format(LibResources.error_closing_connection, GetExceptionMessage(e)));
}
}
@@ -241,7 +263,7 @@ protected virtual ResultResource ExecuteCommandProtected(IDbCommand command, boo
{
command.Parameters.Clear();
- for (int iparam = 0; iparam < parameters.Count; iparam ++)
+ for (int iparam = 0; iparam < parameters.Count; iparam++)
{
command.Parameters.Add(parameters[iparam]);
}
@@ -261,21 +283,20 @@ protected virtual ResultResource ExecuteCommandProtected(IDbCommand command, boo
}
else
{
- _lastResult = null;
+ LastResult = null;
// read all data into PhpDbResult:
result = GetResult(reader, convertTypes);
result.command = command;
- _lastResult = result;
+ LastResult = result;
}
- _lastException = null;
+ OnSuccess();
}
catch (Exception e)
{
- _lastException = e;
- PhpException.Throw(PhpError.Warning, LibResources.command_execution_failed, GetExceptionMessage(e));
+ OnException(e, string.Format(LibResources.command_execution_failed, GetExceptionMessage(e)));
}
//
@@ -295,11 +316,12 @@ internal void ReexecuteSchemaQuery(ResultResource/*!*/ result)
try
{
result.Reader = _pendingReader = result.Command.ExecuteReader(CommandBehavior.KeyInfo | CommandBehavior.SchemaOnly);
+
+ OnSuccess();
}
catch (Exception e)
{
- _lastException = e;
- PhpException.Throw(PhpError.Warning, LibResources.command_execution_failed, GetExceptionMessage(e));
+ OnException(e, string.Format(LibResources.command_execution_failed, GetExceptionMessage(e)));
}
}
@@ -318,14 +340,13 @@ public bool SelectDb(string databaseName)
if (connection.State == ConnectionState.Open)
{
connection.ChangeDatabase(databaseName);
- _lastException = null;
+ OnSuccess();
return true;
}
}
catch (Exception e)
{
- _lastException = e;
- PhpException.Throw(PhpError.Warning, LibResources.database_selection_failed, GetExceptionMessage(e));
+ OnException(e, string.Format(LibResources.database_selection_failed, GetExceptionMessage(e)));
}
return false;
@@ -338,9 +359,11 @@ public bool SelectDb(string databaseName)
/// Exception.
/// The message.
/// is a null reference.
- public virtual string GetExceptionMessage(System.Exception/*!*/ e)
+ public virtual string GetExceptionMessage(Exception/*!*/ e)
{
- if (e == null) throw new ArgumentNullException(nameof(e));
+ if (e == null)
+ throw new ArgumentNullException(nameof(e));
+
return PhpException.ToErrorMessage(e.Message);
}
diff --git a/src/Peachpie.Runtime/Errors.cs b/src/Peachpie.Runtime/Errors.cs
index ce4c3e6eb1..acb90095f6 100644
--- a/src/Peachpie.Runtime/Errors.cs
+++ b/src/Peachpie.Runtime/Errors.cs
@@ -494,8 +494,10 @@ public static void ObjectConvertError(string classname, string targettype)
/// is a null reference.
public static string ToErrorMessage(string exceptionMessage)
{
- if (exceptionMessage == null) throw new ArgumentNullException("exceptionMessage");
- return exceptionMessage.TrimEnd(new char[] { '.' });
+ if (exceptionMessage == null)
+ throw new ArgumentNullException(nameof(exceptionMessage));
+
+ return exceptionMessage.AsSpan().TrimEnd('.').ToString();
}
internal static void ThrowSelfOutOfClass()