{"id":24432377,"url":"https://github.com/kitsunetech-com/velox-server","last_synced_at":"2025-04-12T14:02:59.893Z","repository":{"id":38077130,"uuid":"340801070","full_name":"KitsuneTech-com/Velox-Server","owner":"KitsuneTech-com","description":"Server-side component of the Velox MVA (https://github.com/KitsuneTech-com/Velox-MVA)","archived":false,"fork":false,"pushed_at":"2025-02-24T19:40:39.000Z","size":1156,"stargazers_count":3,"open_issues_count":7,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-26T08:37:28.437Z","etag":null,"topics":["composer","database","json","mysql","odbc-api","php","sql","sql-server"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/KitsuneTech-com.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-02-21T02:34:37.000Z","updated_at":"2025-02-24T19:37:00.000Z","dependencies_parsed_at":"2024-12-24T00:25:35.659Z","dependency_job_id":"d774a926-b803-4d02-9f16-550de5a3c7ec","html_url":"https://github.com/KitsuneTech-com/Velox-Server","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KitsuneTech-com%2FVelox-Server","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KitsuneTech-com%2FVelox-Server/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KitsuneTech-com%2FVelox-Server/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KitsuneTech-com%2FVelox-Server/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/KitsuneTech-com","download_url":"https://codeload.github.com/KitsuneTech-com/Velox-Server/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248578637,"owners_count":21127712,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["composer","database","json","mysql","odbc-api","php","sql","sql-server"],"created_at":"2025-01-20T16:00:16.727Z","updated_at":"2025-04-12T14:02:59.886Z","avatar_url":"https://github.com/KitsuneTech-com.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Velox Server\n\nVelox Server is the primary server-side component of the [Velox MVA](https://github.com/KitsuneTech-com/Velox-MVA)\nframework. It provides a platform-agnostic class structure that eliminates the need to juggle syntax when accessing\nmultiple data sources, including MySQL/MariaDB, Microsoft SQL Server, and ODBC-compatible sources. The Model class\nalso allows for additional data caching, manipulation, and export of the retrieved datasets.\n\n## Requirements\nVelox Server has been built to be as portable and platform-agnostic as possible, though it has yet to be tested on\nnon-POSIX systems. (Users are welcome to try this at their own risk, and feedback in such cases is welcome.) The\nminimum software requirements for Velox Server are as follows:\n\n* PHP 8.0.2+, with Composer 2.0+\n  * One or more of the following extensions, depending on the database engine to be used:\n    * MySQL / MariaDB: either mysqli or pdo_mysql\n    * Microsoft SQL Server: either sqlsrv or pdo_sqlsrv (note: either of these require the Microsoft ODBC Driver for SQL\n      Server.)\n    * ODBC: either odbc or pdo_odbc, along with the necessary drivers for the desired connection\n  * The xmlwriter extension, if Velox Server is to be used standalone and XML output is needed\n* Certain forms of output may require a web server that supports PHP server-side scripting (Apache 2.4+ is specifically\n  supported, but NGINX, IIS, and others may work as well)\n\n## Installation\nVelox Server is built as a Composer package using PSR-4 autoloading. To install it, make sure you first have Composer\ninstalled and initialized in your project if you hadn't already, then include the following in the composer.json file\nand run the ```composer update``` command:\n\n```json\n{\n  \"require\": {\n    \"kitsunetech/velox\": \"\u003e=1.0.0\"\n  }\n}\n```\n\nAlternatively, the source can be downloaded directly from this repository and implemented with a PSR-4 autoloader of\nyour choice. Velox Server can also be implemented without an autoloader, but this is not recommended since the class\nfiles will have to be included/required individually based on dependencies. These methods may work but are not supported.\n\n**Note for web implementation:** When using this or any other Composer application in a web context, it's best to\ninitialize Composer one level below the web root (e.g., if your web root is /srv/www/htdocs, you should install Composer\nat /srv/www). This will limit malicious actors' ability to directly access and exploit server-side libraries, scripts,\nand/or configuration files if the server is not properly configured to execute PHP.\n\n## Usage\n\nHaving been built according to the PSR-4 standard, Velox Server class structure is divided into a set of sub-namespaces\nunderneath the `KitsuneTech\\Velox` base, as reflected in the structure of the src directory. (There is one exception:\nthe Support directory, which contains independent helper functions and constants that are not part of the class\nstructure.) Each of these sub-namespaces handles a different facet of the server-side component.\n\nTo use any of these classes, include your autoloader according to its documentation, then write use statements for\nthe fully qualified class name of each class or function you wish to implement. Remember to include the sub-namespace\nas appropriate.\n\n```php\nuse KitsuneTech\\Velox\\Database\\Connection;\nuse KitsuneTech\\Velox\\Structures\\{Model, VeloxQL};\nuse function KitsuneTech\\Velox\\Transport\\Export;\n```\n\n### Database\n\nThe Database sub-namespace controls database communication. The `Connection` object serves as the interface for this\ncommunication, using whichever PHP extension is needed to connect to the given database. The following are examples of\nhow a Connection object can be instantiated:\n```php\n$pdoMySQLConnection = new Connection($hostname,$database_name,$user_id,$password,$port,Connection::DB_MYSQL,Connection::CONN_PDO);\n$sqlsrvConnection = new Connection($hostname,$database_name,$user_id,$password,$port,Connection::DB_MSSQL,Connection::CONN_NATIVE);\n$odbcDSNConnection = new Connection($dsn_name,null,null,null,null,null,Connection::CONN_ODBC);\n$SQLServerODBCByConnectionString = new Connection(null,null,null,null,null,null,Connection::CONN_ODBC,[\"Driver\"=\u003e\"{ODBC Driver 18 for SQL Server}\",\"server\"=\u003e$hostname,\"database\"=\u003e$database_name,\"Uid\"=\u003e$user_id,\"Pwd\"=\u003e$password]);\n```\nThe first two examples are fairly self-explanatory. If the port is passed as null, the default port for the given\ndatabase type is assumed. The last two arguments shown in these are constants representing the database engine and\nconnection type, respectively. The database type can currently be one of the following:\n* `Connection::DB_MYSQL` (for MySQL / MariaDB)\n* `Connection::DB_MSSQL` (for Microsoft SQL Server)\n* `Connection::DB_ODBC` (for ODBC data sources)\n\nThe connection type can be one of these:\n* `Connection::CONN_PDO` (this uses a PDO library compatible with the given database engine)\n* `Connection::CONN_NATIVE` (this uses a compatible non-PDO library [DB_MYSQL uses mysqli, DB_MSSQL uses sqlsrv])\n* `Connection::CONN_ODBC` (this uses the ODBC library)\n  (note: if CONN_ODBC is used, the DB_ constants are ignored, so they can be left off)\n\nThe second two examples above demonstrate ODBC connections. The first of these connects to a named DSN; the second to a\nDSN-less resource whose connection string attributes are given in the array. If the enormous number of nulls in these\nmakes you cringe, you can instead call the constructor with named arguments:\n```php\n$odbcDSNConnection = new Connection(host: $dsn_name, connectionType: Connection::CONN_ODBC);\n$SQLServerODBCByConnectionString = new Connection(connectionType: Connection::CONN_ODBC, options: [\"Driver\"=\u003e\"{ODBC Driver 18 for SQL Server}\",\"server\"=\u003e$hostname,\"database\"=\u003e$database_name,\"Uid\"=\u003e$user_id,\"Pwd\"=\u003e$password]);\n```\nThat's easier, right? The full list of named parameters are, in order: host, dbName, uid, pwd, port, serverType,\nconnectionType, and options. Any unused parameters can be omitted.\n\nAll queries and procedures are handled through these Connection instances, and the specific functions and/or methods\nnecessary for these are abstracted away, using the following classes contained in the `Database\\Procedures` sub-namespace:\n\n#### Query\n\n`Query` is the most fundamental class in Procedures. This represents a single SQL statement to be run on the Connection\nas supplied to its constructor. Once defined, it is run using its `execute()` method, and the results, if any, are\nretrieved with the `getResults()` method.\n\nTo run a Query, first create a Connection (see above) and then pass it as the first argument to the constructor. (You\ncan reuse a Connection if you already have one open.) The second argument is the SQL query you intend to run on the\nconnection, the third specifies what type of query you are running, and the fourth tells Query how it should return the\nresults to you. (See the full documentation for a complete description of the available options.)\n\nExample:\n```php\n$myConnection = new Connection(host: \"myDatabaseServer.xyz\", dbName: \"myDatabase\", serverType: Connection::DB_MYSQL, connectionType: Connection::CONN_PDO);\n$myQuery = new Query($myConnection, \"SELECT thisColumn FROM myTable WHERE thatColumn BETWEEN 0 AND 9\", Query::QUERY_SELECT, Query::RESULT_ARRAY);\n$myQuery-\u003eexecute();\n$resultsZeroToNine = $myQuery-\u003egetResults();\n```\n\n#### PreparedStatement\n\n`PreparedStatement` is a subclass of Query that extends it with methods that allow named and positional placeholders to\nbe defined and used following the syntax appropriate for the database engine. A single PreparedStatement object can be\nused to batch queries by iterative calls to its `addParameterSet()` method, each call supplied with an associative array\nhaving the placeholders and values to be substituted. The batch of queries can then be run with a single `execute()`\nmethod call, and the combined results are available with a single call to the `getResults()` method.\n\nExample:\n```php\n$myStatement = new PreparedStatement($myConnection, \"SELECT thisColumn FROM myTable WHERE thatColumn = :chosenValue\");\nfor ($anInteger = 0; $anInteger \u003c 10; $anInteger++){\n    $myStatement-\u003eaddParameterSet([\"chosenValue\"=\u003e$anInteger]);\n}\n$myStatement-\u003eexecute();\n$resultsZeroToNine = $myStatement-\u003egetResults();\n```\n\n#### StatementSet\n\n`StatementSet` is the most versatile of these classes. It addresses several shortcomings of the SQL-standard prepared\nstatement by creating its own set of PreparedStatements depending on the values and criteria given to it. Among other\nthings, this behavior allows for operators to be assigned dynamically and for column names and values to only be\nspecified as needed. Because of this unique, non-standard behavior, SQL used to define a StatementSet follows an\naugmented syntax, with placeholders similar to those used by Angular. Three basic placeholders are allowed\n(`\u003c\u003cvalues\u003e\u003e`, `\u003c\u003ccolumns\u003e\u003e`, and `\u003c\u003ccondition\u003e\u003e`), and these are added to a base SQL statement where the appropriate\nclauses would be. The general pattern for each type of query is as follows:\n  ```sql\n  SELECT \u003c\u003ccolumns\u003e\u003e FROM myTable WHERE \u003c\u003ccondition\u003e\u003e\n  INSERT INTO myTable (\u003c\u003ccolumns\u003e\u003e) VALUES (\u003c\u003cvalues\u003e\u003e)\n  UPDATE myTable SET \u003c\u003cvalues\u003e\u003e WHERE \u003c\u003ccondition\u003e\u003e\n  DELETE FROM myTable WHERE \u003c\u003ccondition\u003e\u003e\n  ```\n\nExample:\n```php\n$myStatementSet = new StatementSet($myConnection, \"SELECT \u003c\u003ccolumns\u003e\u003e FROM myTable WHERE \u003c\u003ccondition\u003e\u003e\");\n$myStatementSet-\u003eaddCriteria([\"columns\"=\u003e[\"thisColumn\"], \"where\"=\u003e[[\"thatColumn\"=\u003e[\"BETWEEN\",0,9]]]]);\n$myStatementSet-\u003esetStatements();\n$myStatementSet-\u003eexecute();\n$resultsZeroToNine = $myStatementSet-\u003egetResults();\n```\n#### Transaction\n\n`Transaction` is a representation of a SQL transaction, in which multiple statements are run and only committed when\ncomplete. In Velox, it has the unique capability of performing operations on multiple databases simultaneously using\nprocedures run on several Connections. A Transaction can be set up with consecutive calls to its addQuery method, each\nof which appends the given procedure to its execution plan. `Transaction::addFunction()` can be used to insert\ninterstitial code to be run between procedures; code defined in this way has access to both the previous and subsequent\nprocedures, which allows this code to store and manipulate prior results, and to manipulate the following procedure as\nneeded. The execution plan can then be run all at once, or one step at a time.\n\nAs an example, this is what a simple ETL Transaction would look like, from a MySQL source to a SQL Server destination.\n```php\n//Create connections to the source and destination databases\n$mysqlConnection = new Connection(host: \"mysqlServer.xyz\", dbName: \"sourceDatabase\", serverType: Connection::DB_MYSQL, connectionType: Connection::CONN_PDO);\n$sqlsrvConnection = new Connection(host: \"sqlsrvServer.xyz\", dbName: \"destinationDatabase\", serverType: Connection::DB_MSSQL, connectionType: Connection::CONN_NATIVE);\n\n//Map the source column names to the destination column names\n$columnMap = [\"sourceAbc\" =\u003e \"destinationAbc\", \"sourceXyz\" =\u003e \"destinationXyz\"];\n\n//Create StatementSets for the source SELECT and the destination INSERT\n$sourceStatementSet = new StatementSet($mysqlConnection,\"SELECT \u003c\u003ccolumns\u003e\u003e FROM sourceTable\");\n$destinationStatementSet = new StatementSet($sqlsrvConnection,\"INSERT INTO destinationTable (\u003c\u003ccolumns\u003e\u003e) VALUES \u003c\u003cvalues\u003e\u003e\",QUERY::QUERY_INSERT);\n\n//Add the criteria for the source SELECT (the source column names above)\n$sourceStatementSet-\u003eaddCriteria([\"columns\"=\u003earray_keys($columnMap)]);\n\n//Define a transformation function to perform on the selected data\n$transform = function($source,$destination) use ($columnMap) {\n    //The Transaction will supply the arguments on execution. Each will be an array of two elements: the previous or\n    //next defined procedure, respectively; and the arguments by which it was invoked, passed by reference.\n    $sourceData = $source[0]-\u003egetResults();\n    \n    //Transform the data (here, we're just remapping columns) and feed it to the destination StatementSet\n    foreach ($sourceData as $sourceRow){\n        $destinationRow = [];\n        foreach ($sourceRow as $sourceColumn =\u003e $value){\n            $destinationRow[$columnMap[$sourceColumn]] = $value;\n        }\n        $destination[0]-\u003eaddCriteria([\"values\"=\u003e$destinationRow]);\n    }\n};\n\n//Assemble the Transaction (in order of execution)\n$myTransaction = new Transaction();\n$myTransaction-\u003eaddQuery($sourceStatementSet);\n$myTransaction-\u003eaddFunction($transform);\n$myTransaction-\u003eaddQuery($destinationStatementSet);\n\n//Execute and finally commit it.\n$myTransaction-\u003eexecuteAll();\n$myTransaction-\u003ecommit();\n```\nThere are ways to simplify this process even further, using the classes below.\n\n### Structures\n\nThe `Structures` sub-namespace contains data structure classes used by the server-side component. Two of these -\n`VeloxQL` and `ResultSet` - are used to structure data passing to and from (respectively) the database through the\nDatabase\\Procedures classes. The third - `Model` - is the most crucial of these, as it mediates the data flow between\nthe API interface and the database. A Model is a memory-resident representation of a dataset as defined by the\nprocedures assigned to it, and can be used to abstract away the entire database communication process by way of its\nvarious methods. Once a Model is populated, filtering and sorting can be done without ever touching the database, and\nany changes made through the corresponding methods are automatically forwarded to the database by way of the associated\nprocedures; the Model is subsequently refreshed with current data.\n\n#### VeloxQL\nThe VeloxQL class is a purely structural entity (no methods) which implements an object-oriented equivalent of the\nVALUES and/or WHERE clauses of a query -- in short, the conditional part. By using VeloxQL objects with a StatementSet\nor Model, instances of the latter can be defined one time for a given dataset and easily reused with multiple sets of\nvalues or criteria.\n\nEach VeloxQL instance has four properties, one for each query type -- select, insert, update, and delete. Each\nrepresents an array of operations of that type to be performed, with the clauses appropriate to that query type.\nThus, each element of a given property must be an array having the following keys, respectively:\n\n* select: \"where\"\n* update: \"values\", \"where\"\n* insert: \"values\"\n* delete: \"where\"\n\nThe value for each key must itself be an array, the expected contents of which depend on the key, as described below:\n\n##### \"where\"\nA \"where\" array is an array of arrays, with each array being a set of conditions to be applied to the corresponding\nSELECT query, ORed together; each set of conditions is an associative array wherein each key is a column name for that\ndataset, and the corresponding value is an array representation of a SQL-equivalent comparison expression for that\ncolumn; this array will contain between one and three elements, depending on the expression; the general format of this\nis as follows:\n\n* Unary operations: `[\"IS NULL\"]`, `[\"IS NOT NULL\"]`\n* Binary comparisons: `[\"=\", \"someValue\"]` (all SQL-standard binary comparisons are supported)\n* Trinary comparisons: `[\"BETWEEN\",\"firstValue\",\"secondValue\"]` (the values here should of course be of a type that can\nbe compared in this manner)\n* Set comparisons: `[\"IN\",[\"value1\",\"value2\",\"value3\"]]`\n\nPut together, a \"where\" array might look something like this:\n```php\n[\n    [\"column1\" =\u003e [\"=\",2], \"column2\" =\u003e [\"\u003c\", 3]],\n    [\"column1\" =\u003e [\"\u003c\u003e\", 5], \"column2\" =\u003e [\"IS NULL\"]],\n    [\"column1\" =\u003e [\"BETWEEN\", 1, 10]]\n]\n```\nwhich corresponds to the following SQL WHERE clause:\n```sql\nWHERE\n(column1 = 2 AND column2 \u003c 3)\nOR (column1 \u003c\u003e 5 AND column2 IS NULL)\nOR (column1 BETWEEN 1 AND 10)\n```\n\n##### \"values\"\nA \"values\" array is also an array of arrays, but a much simpler one. Each array represents one set of columns/values\n(as an associative array) to be either inserted or updated, depending on the query type. Only one such array is used\nfor each UPDATE, but several arrays can be used to perform a batch INSERT. For example, this VeloxQL object is set up\nto insert two rows into a dataset, each having different values for the two given columns:\n```php\n$vql = new VeloxQL;\n$vql-\u003einsert = [\n    [\n    \"values\"=\u003e[\n        [\"column1\" =\u003e \"firstValueColumn1\", \"column2\" =\u003e \"firstValueColumn2\"],\n        [\"column1\" =\u003e \"secondValueColumn1\", \"column2\" =\u003e \"secondValueColumn2\"]\n    ]\n];\n```\n\n#### ResultSet\nResultSet is the default return datatype for most Velox procedures, unless specified otherwise. This can be accessed and\niterated as a typical two-dimensional array, but it also includes some extra utility methods that provide metadata about\nthe result data and to be able to merge this data with that of another ResultSet (akin to a SQL UNION operation).\n\n| Method         | Description                                                                                                                                                                                           |\n|----------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| lastAffected() | Returns an array of the indices affected by the procedure that returned this ResultSet [as LAST_INSERT_ID() in MySQL, but run after each SQL statement executed]                                      |\n| columns()      | Returns an array containing the column names from the result                                                                                                                                          |\n| getRawData()   | Sometimes you just need an actual array.                                                                                                                                                              |\n| merge()        | Takes two arguments, in order: another ResultSet, and a boolean. The contents of the given ResultSet are appended to this one, filtering out duplicate rows if true is passed as the second argument. |\n\n#### Model\nModel is the big fish among the Velox structures. Behind the scenes it holds a full representation of the dataset\nretrieved through the SELECT-equivalent Velox procedure used to instantiate it, and changes to this can be synchronized\nwith the data source through the use of the methods corresponding to the given query type [`update()`, `insert()`,\n`delete()`], which use the specified changes to generate parameter sets/criteria for, and subsequently execute, the\nVelox procedures defined for those operations. All these are specified initially in the constructor, similar to the following example:\n\n```php\n$select = new Query($myConnection,\"SELECT firstColumn, secondColumn, thirdColumn FROM myTable\");\n$update = new StatementSet($myConnection,\"UPDATE myTable SET \u003c\u003cvalues\u003e\u003e WHERE \u003c\u003ccriteria\u003e\u003e\");\n$insert = new StatementSet($myConnection,\"INSERT INTO myTable (\u003c\u003ccolumns\u003e\u003e) VALUES \u003c\u003cvalues\u003e\u003e\");\n$delete = new StatementSet($myConnection,\"DELETE FROM myTable WHERE \u003c\u003ccriteria\u003e\u003e\");\n$myModel = new Model($select, $update, $insert, $delete);\n```\nAll procedures are optional; however, only those operations that have been supplied to the Model at instantiation will\nbe available for use. (e.g., if only a SELECT is provided, the Model will be read-only relative to the external\ndata source.) A Model can also be defined without any procedures at all; in such a case, the Model will be created empty\nand the data will need to be populated through direct access. (This may be useful if Model features are desired without\na SQL-compatible data source.)\n\n##### Data source synchronization\nModel contains five methods by which the Model is synchronized with the remote data source. Four of these -- `select()`,\n`update()`,`insert()`, and `delete()` -- correspond to the procedures defined in the constructor, and run the appropriate\nprocedure using the arguments supplied. For `select()`, that argument is a boolean indicating whether the return value\nshould be a VeloxQL object indicating the changes in the remote data since the last `select` call; for the other three,\nthe argument is an array of parameter sets or criteria to be added to the procedure in question. The procedure is\nthen invoked immediately after these parameter sets/criteria are added, and once the operation is complete, `select()`\nis called to refresh the Model with the updated data.\n\n`synchronize()` is a shortcut method to perform all desired DML queries in sequence. It takes as its argument a VeloxQL\nobject containing all changes to be made, applies them to their designated procedures, and then executes them in the\nfollowing order: `update()`,`delete()`,`insert()`, with the `select()` call postponed until the end.\n\n##### Filtering\nTo apply a filter to the Model without altering the underlying data, the `setFilter()` method can be called, passing\neither a [\"where\" array](#where) or a VeloxQL object as the argument. (In the latter case, the \"where\" array will be\nparsed from the VeloxQL object's select property.) The filter will be applied as if it were a WHERE clause of an SQL\nquery, but only affecting the visibility of the data in the Model. Subsequent calls to `setFilter()` will set a new\nfilter, replacing the previous one (the filters do not stack), and passing null to `setFilter()` (or calling it\nwith no arguments) will remove the filter entirely.\n\n##### Sorting\nModels can be sorted using the `sort()` method, in a manner somewhat similar to that of PHP's native\n[array_multisort()](https://www.php.net/manual/en/function.array-multisort.php) function (in fact, this method uses\narray_multisort() to perform the sorting). The method call differs only in that the arrays expected by array_multisort()\nare replaced by the column names by which the Model is to be sorted. For example:\n```php\n$myModel-\u003esort(\"column1\", SORT_ASC, \"column2\", SORT_DESC);\n```\nwill sort $myModel by \"column1\" first in ascending order, then by \"column2\" in descending order. As in array_multisort(),\noptional flags can also be applied to determine the sort behavior (e.g., whether the column is to be sorted\nalphabetically or numerically). See the documentation on array_multisort() for details on what flags are available.\n\n##### Joining\nAny two Models, regardless of their underlying data sources, can be joined in a manner similar to SQL joins, using the\n`join()` method. While not as full-featured as a native SQL join (in particular, only one pair of columns can be\nspecified per join), this allows data from two different sources to be joined without having to export data from one\nsource to the other.\n\nThe `join()` method is invoked on whichever Model is to be used as the left side of the join. It takes three arguments:\na constant specifying the join type (`LEFT_JOIN`, `RIGHT_JOIN`, `INNER_JOIN`, `FULL_JOIN`, or `CROSS_JOIN` -- the\nbehavior corresponds to the SQL standard), the Model to be used as the right side of the join, and a third argument\nspecifying the conditions on which the join is to be performed. This third argument depends on the manner in which the\njoin is to be performed, and can be one of the following:\n\n   * A string indicating a common column name in both Models to be joined upon; if this is used, the join is done as\n      if using the USING predicate. For example, a join written like this in SQL:\n      ```sql\n      SELECT * FROM model1 LEFT JOIN model2 USING (myColumn);\n     ```\n     would be performed with a Model join as follows:\n     ```php\n     $joinedModel = $model1-\u003ejoin(LEFT_JOIN,$model2,\"myColumn\");\n     ```\n   * An array of three strings. These strings are, in order: the left-side column to be joined, the operator to be\n     used, and the right-side column to be joined. These elements represent the comparison that would be\n     specified in SQL using the ON predicate. For example, a join written like this in SQL:\n      ```sql\n      SELECT * FROM model1 LEFT JOIN model2 ON model1.thisColumn = model2.thatColumn;\n     ```\n     would be performed with a Model join as follows:\n     ```php\n     $joinedModel = $model1-\u003ejoin(LEFT_JOIN,$model2,[\"thisColumn\",\"=\",\"thatColumn\"]);\n     ```\n     Note in this case that though in the SQL standard the order of the columns in the expression doesn't matter, in a \n     Model join the columns must be specified in left-to-right order (i.e., the first element must be a column from \n     the left-side Model, and the third must come from the right-side Model). All SQL binary comparison operators are\n     supported.\n   * Null or omitted if a cross join is to be performed, since a cross join matches all rows on the left with all rows\n     on the right unconditionally.\n\nThe join results are returned as a new Model having the columns of both original Models (as appropriate to the manner of\nthe join -- a USING-equivalent join [as in the first example above] would contain only one copy of the specified column).\nIt's important to note that because variable names are not equivalent to table names, ambiguous column names can't be\nresolved and will throw an exception unless each Model has its instanceName property set to a distinct value; if this is\ndone, then any columns having the same name on both sides will be renamed using the Model's instanceName as a prefix\n(\"instanceName.columnName\").\n\nNote also that the resulting Model is independent of the original Models; any changes performed on the original Models\nafter the join will not be propagated to the joined Model, and the joined Model will not have access to either of the\noriginal Models' synchronization procedures. The results should therefore be treated as a static snapshot at the time\nof the join.\n\n##### Pivoting\nIn addition to the above SQL-analogous methods, Model also provides a `pivot()` method that creates a derivative Model \nwith column names generated from the values of a specified pivot column, with the corresponding values of another\ncolumn summarized appropriately. `pivot()` has three required parameters, in order: the name of the pivot column (i.e.,\nthe column having the intended column names), the name of the index column whose values will be used to group the\nresults, and the name of the column where the values for the pivoted columns are to be found. The transformation\napplied will look something like the following:\n\nOriginal:\n\n| index | pivot     | values |\n|-------|-----------|--------|\n| 1     | column1   | 10     |\n| 1     | column2   | 20     |\n| 1     | column3   | value1 |\n| 2     | column1   | 20     |\n| 2     | column2   | 30     |\n| 2     | column2   | 20     |\n| 2     | column3   | value2 |\n| 2     | column3   | value3 |\n\nPivoted:\n\n| index | column1 | column2 | column3       |\n|-------|---------|---------|---------------|\n| 1     | 10      | 20      | value1        |\n| 2     | 20      | 50      | value2,value3 |\n\nAny values having the same pivot and index will be either summed or concatenated (using a comma delimiter) depending on\nthe data type of the values associated with the pivot value (specifically, if any such value is non-numeric, the values\nare concatenated).\n\nThree optional parameters are also available for more fine-grained control over the results. The first optional parameter\n(fourth in the order) is an array of pivot values to be used; the results will only have these values as columns, and\nall other pivot values will be ignored. The second optional parameter is a boolean indicating whether these pivot values\nshould instead be ignored; if passed as true, the results will instead contain columns for every pivot value *except*\nthose specified in the previous argument. Finally, one more boolean parameter allows for suppression of the exception\nthat would otherwise be thrown if one of the pivot values specified do not exist in the Model; in this case, the pivot\ncolumn will be created, but it will be filled with nulls.\n\nAs in the `join()` method above, the Model returned by `pivot()` is independent of the original Model and is not\nupdated when  the data in the original Model is changed.\n\n##### Miscellaneous methods\nAlong with the core methods described above, Model also contains a set of utility methods that can be used to obtain\ndata/metadata about the Model, or to change the structure of the dataset. These methods are as follows:\n\n| Method         | Description                                                                                                                                                |\n|----------------|------------------------------------------------------------------------------------------------------------------------------------------------------------|\n| columns()      | Returns an array of column names for this Model                                                                                                            |\n| data()         | Returns an array representation of the Model's dataset (with any filters applied)                                                                          |\n| diff()         | Returns a VeloxQL representation of the changes resulting from the last synchronization method call.                                                       | \n| lastQuery()    | Returns a Unix timestamp indicating the last update from the Model's data source [or when the Model was created, for those generated by join() or pivot()] |\n| renameColumn() | Takes two arguments: an existing column name, and a desired column name. The existing column by the given name is renamed to the desired name.             |\n| export()       | This is a wrapper method for the Export function (see below). It takes all of the same arguments except for the first (this Model is used specifically).   |\n\n### Transport\n\nThe `Transport` sub-namespace defines classes and functions used to package and transport data between Velox and other\nnon-database media. This currently consists of one primary function: `Export`.\n\n#### Export\nExport's purpose is more or less self-explanatory: it exports the dataset(s) of one or several Models in one of several\nformats (JSON, CSV, XML, and HTML are currently supported) to the specified destination (the browser, a file, a PHP\nstring, or STDOUT). The usage is also quite simple -- it's a single function call, with the following parameters, in order:\n\n1. The Model (or array of Models) to be exported,\n2. A pair of constants added together, indicating the format and destination for the exported data,\n3. A path and/or filename to which the data will be sent (this only applies to file and browser exports),\n4. The number of rows from the Model(s) to be skipped from the beginning of the dataset, if desired (default: 0),\n5. A boolean, true to leave off the column headers (these are included by default), and\n6. An optional string containing either CSS text or a URL to an external style sheet with which the output can be\n   formatted.\n\nThe constants expected in the second parameter are predefined as follows:\n\n| Format  | Description                                                                                                                                          |\n|---------|------------------------------------------------------------------------------------------------------------------------------------------------------|\n| AS_JSON | A JSON representation of the exported Model(s), having a \"data\" property as an array of objects, each of which represent one row in key/value format |\n| AS_CSV  | A CSV spreadsheet containing the exported data in tabular form                                                                                       |\n| AS_XML  | An XML representation of the exported Model(s)                                                                                                       |\n| AS_HTML | An HTML page containing a `\u003ctable\u003e` populated with the exported data                                                                                 |\n\n| Destination | Description                                                                                           |\n|-------------|-------------------------------------------------------------------------------------------------------|\n| TO_BROWSER  | HTTP headers are sent before the data is sent to the web server in the given format                   |\n| TO_FILE     | A local file is created from the exported data                                                        |\n| TO_STRING   | Export() returns a string representation of the data in the given format, without outputting anything |\n| TO_STDOUT   | The results are sent directly to the console (if executing a script from the command line)            |\n\nAny combination of format and destination constants can be provided, added together. For example, `TO_FILE+AS_CSV` will\ncreate a local CSV file, while `TO_BROWSER+AS_HTML` will render an HTML page to a web client.\n\n***Important:*** If using the CSS parameter to specify styling, it's crucial to ensure that the content is safe (either\nthe URL or code is known and trusted, or it's been properly sanitized). Allowing end users to specify their own styling\nwithout first validating it could leave open the possibility of XSS injection.\n\n### Error handling\nVelox defines its own exception class, named VeloxException. Any exceptions raised by the Velox library (see\n[exceptions.md](exceptions.md) for specific details) will be of this class. Velox also defines and implements an\nexception handler that formats and outputs exception details (including, optionally, a call stack) in a chosen manner.\nThis behavior can be specified by calling the veloxErrorReporting() function, passing as an argument any of the\nfollowing constants, added together:\n\n| Constant             | Behavior                                                                            |\n|----------------------|-------------------------------------------------------------------------------------|\n| VELOX_ERR_NONE       | Only an exception code is output (this is overridden by any of the other constants) |\n| VELOX_ERR_STDERR     | Error messages are sent to stderr                                                   |\n| VELOX_ERR_JSONOUT    | Error messages are formatted as JSON                                                |\n| VELOX_ERR_STACKTRACE | A full stack trace is included with the error output                                |\n\nThe default behavior is VELOX_ERR_STDERR + VELOX_ERR_STACKTRACE.\n\n### Utility functions\nWhile not designed to be a core feature of this library, a handful of possibly useful utility functions are available\nin [src/Support/Utility.php](src/Support/Utility.php) that can be used independently of the rest of the library.\nSince these are mostly meant to provide necessary functionality to the core classes, this readme will not cover\ndetails and usage, but this information can be found in the docblocks therein.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkitsunetech-com%2Fvelox-server","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkitsunetech-com%2Fvelox-server","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkitsunetech-com%2Fvelox-server/lists"}