{"id":21146351,"url":"https://github.com/bhallstein/tinymodel","last_synced_at":"2025-06-21T21:35:01.889Z","repository":{"id":143863292,"uuid":"261561121","full_name":"bhallstein/TinyModel","owner":"bhallstein","description":"An easy-peasy way to define a Model and store data using MySQL [legacy]","archived":false,"fork":false,"pushed_at":"2020-05-05T19:11:12.000Z","size":70,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2025-03-14T13:44:16.279Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bhallstein.png","metadata":{"files":{"readme":"Readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-05-05T19:10:49.000Z","updated_at":"2020-05-05T19:11:19.000Z","dependencies_parsed_at":null,"dependency_job_id":"3cedab17-9b14-4c00-afd6-6134de8e6fed","html_url":"https://github.com/bhallstein/TinyModel","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/bhallstein/TinyModel","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bhallstein%2FTinyModel","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bhallstein%2FTinyModel/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bhallstein%2FTinyModel/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bhallstein%2FTinyModel/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bhallstein","download_url":"https://codeload.github.com/bhallstein/TinyModel/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bhallstein%2FTinyModel/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":261197483,"owners_count":23123725,"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":[],"created_at":"2024-11-20T08:53:01.736Z","updated_at":"2025-06-21T21:34:56.874Z","avatar_url":"https://github.com/bhallstein.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# TinyModel\n\nTinyModel is a PHP superclass for defining the Model layer of your web application. It handles the communication between application and database, and returns friendly nested objects, even for arbitrary joins.\n\n## Examples\n\n### Example 1: Authenticating a user\n\n*MyModel.php:*\n    \n    require('TinyModel.php');\n    \n    class User extends TinyModel {\n        const userid = 'int';\n        const username = 'varchar alphanumeric maxlength=30 notnull';\n        const password = 'char maxlength=60 notnull';\n        const email = 'varchar email maxlength=100';\n    }\n    \n    class Favourite extends TinyModel {\n        const faveid = 'int';\n        const userid = 'int not null';\n        const itemid = 'int not null';\n    }\n    \n    class Item extends TinyModel {\n        const itemid = 'int';\n        const name = 'varchar maxlength=40 notnull';\n    }\n\n*login.php:*\n    \n    require('MyModel.php');\n    \n    $p_username = $_POST['username'];\n    $p_password = $_POST['password'];\n    \n    $res = User::fetch(\n        new Condition('username', $p_username)\n    );\n    if ($res-\u003estatus !== TMResult::Success || count($res-\u003eresult) != 1) {\n        invokeView('loginError', $res);\n    }\n    else {\n        $db_pwd = $res-\u003eresult[0]-\u003epassword;\n        $authenticated = password_verify($p_password, $db_pwd);\n        if ($authenticated)\n            invokeView('loginSucceeded', $res);\n        else\n            invokeView('loginError', $res);\n    }\n\n### Example 2: Fetching with joins\n\nTinyModel extracts all results into objects of the classes you create. If there are joins, it extracts joined results into sub-objects, attaching an array of them to the parent object. TinyModel can perform this traversal recursively:\n    \n    require('MyModel.php');\n    \n    $conn = new PDO('mysql:host=127.0.0.1;charset=utf8;...');\n    TinyModel::setConnection($conn);\n    \n    $res = User::fetch(\n        new Condition('userid', $p_userid),\n        new Join('Favourite', 'userid', new Join('Item', 'itemid))\n    );\n    if ($res-\u003estatus === TMResult::Success) {\n        echo json_encode($res-\u003eresult, JSON_PRETTY_PRINT);\n    }\n\nThis might output:\n\n    [\n        {\n            \"userid\": 269,\n            \"username\": \"someone\",\n            \"email\": \"someone@somewhere.com\",\n            \"password\": \"~\",\n            \"favourites\": [\n                {\n                    \"faveid\": 220,\n                    \"userid\": 269,\n                    \"item\": 241,\n                    \"items\": [\n                        {\n                            \"itemid\": 241,\n                            \"name\": \"tshirt\"\n                        }\n                    ]\n                }\n            ]\n        }\n    ]\n\n\n## Defining tables\n\nTo configure TinyModel, you define a set of subclasses, each representing a table in your database. Class constants are then used to specify the name and type of the columns in the table, and optional restrictions on values.\n\n### Class \u0026 table names\n\nThe name of the table must be the (lowercase) plural of the name of the class. e.g. For a table `users`, create a class called `User`. TinyModel pluralises the class name to find the table name (but bear in mind it doesn’t know about invariant words such as 'Sheep').\n\n### Column specification\n\nTo define a column, you create a class constant with the same name as the column, and initialize it with a *column specification string*, with the format `\"type [restrictions]\"`:\n\n- type: one of the following: `int`, `float`, `char/varchar` (these are equivalent), `text`, `timestamp`\n- restrictions: one or more of: `alphabetical`, `alphanumeric`, `email`, `url`, `positive`, `notnull`, `maxlength=N`\n\n**Note on notnull:** ID columns are generally \"not null\" in the database, but should *not* be specified as such in TinyModel column specification. This is because `insert()` and `update()` must allow null values for ID columns.\n\n### Example of a class\n\nIf you have the tables `users` and `items`, you might create two classes as follows:\n\n    class User extends TinyModel {\n        const userid   = 'int';\n        const username = 'varchar alphanumeric maxlength=20 notnull';\n        const email    = 'varchar email notnull';\n    }\n    \n    class Items extends TinyModel {\n        const itemid   = 'int';\n        const userid   = 'int notnull';\n        const itemname = 'varchar maxlength=20 notnull';\n    }\n\nTinyModel then allows you to interact with these tables via the `fetch`, `update` and `insert` methods.\n\n\n## TMResult\n\nAll methods return a TMResult object, returning the status of the query and any returned data.\n\nFields:\n\n- **status:** one of the following statuses:\n\t- *TMResult::Success*\n\t- *InvalidData* – update or insert data failed validation\n\t- *InvalidConditions* – condition(s) failed validation\n\t- *InternalError* – TinyModel encountered an error executing the query\n- **result:** returned data appropriate to the query:\n\t- *fetch:* an array of fetched objects\n\t- *update:* the number of rows updated\n\t- *insert:* the insert ID\n- **errors:** error data appropriate to the query and type of failure:\n    - *InvalidConditions:*\n\t\t- if there were *no* conditions, `errors` will be null\n\t\t- if some conditions did not pass validation, an array of errors in the form `column_name =\u003e validation_error` (see list of validation errors below)\n\t- *InvalidData:*\n\t\t- if some updates/inserts did not pass validation_error, an array of errors in the form `column_name =\u003e validation_error`\n\t- *InternalError:*\n\t\t- `errors` is set to the result of calling `errorInfo()` on the failing PDO statement\n\n**Validation Errors:** these specify the type of error that was encountered when validating insert/update data or a condition:\n\n- *NonexistentColumn:* the column does not exist in the table specification\n- *InvalidValue:* the value failed column restrictions\n- *UnknownObject:* a non-Condition object was passed where a Condition object was expected\n\n\n## Methods\n\nTinyModel’s three public methods all return a TMResult object encapsulating query status (success/failure), and returned data or errors.\n\n### fetch\n\n`fetch($conditions, $joins, $debug = false)`\n\nStatic method. Attempt to fetch items from the database.\n\n*conditions*\n\nA Condition object, or an array of Condition objects, to specify what to fetch. For instance, to only return rows where the userid is 76:\n\n    new Condition('userid', 76)\n\nYou can specify multiple nested conditions, and the relationships between them (`and` or `or`), by passing a nested array. For details, see Conditions. At least one valid Condition object is required for all queries.\n\n*joins*\n\nAn optional Join object, or an array of Join objects.\n\n*debug*\n\nIf true, prints out the generated `select` query before executing it.\n\n*Example:* Fetch user(s) with id 76, joined to any favourites, joined to the favourited items:\n\n    User::fetch(\n        new Condition('userid', 76),\n        new Join('Favourite', 'userid', new Join('Thing', 'thingid')))\n    );\n\n\n### update\n\n`update($updates, $conditions, $debug = false)`\n\nStatic method. Updates objects matching the given condition(s).\n\n*updates*\n\nAn associative array of updates to perform, with the key specifying the column, and the value the new entry for that column.\n\n*conditions*\n\nA Condition object or an array of Condition objects, governing which rows in the table will be updated with new value(s).\n\n*debug*\n\nIf true, prints out the query before executing it.\n\n*Example:* Update username of the user with id 12 to ‘jimmy’:\n\n    User::update(\n        array('username' =\u003e 'jimmy'),\n        new Condition('userid', 12)\n    );\n\n\n### insert\n\n`insert()`\n\nInstance method. Insert an object into its corresponding table. You first create an instance, filling out its properties, then simply call `insert()` on it.\n\n*Example:* Insert a favourite for Jimmy into the `favourites` table.\n\n    $f = new Favourite;\n    $f-\u003euserid = 12;\n    $f-\u003ethingid = $thingid;\n    $f-\u003einsert();\n\n\n### Condition objects\n\nCondition objects are used to control which rows should be fetched or updated. The constructor takes the following values:\n\n- *column:* The name of the column the condition applies to\n- *value:* The value to test against\n- *test:* One of the following constants. The default is Equals.\n    - `Condition::Equals`\n    - `NotEquals`\n    - `LessThan`\n    - `LessThanOrEquals`\n    - `GreaterThan`\n    - `GreaterThanOrEquals`\n    - `Recent`\n        - This is used to secify a timestamp column with a time value up to a certain amount of time in the past. When the `Recent` condition is used, the value field of the Condition specifies the number of seconds before the current timestamp that should be considered a match.\n- *conjunction:* You can pass a nested array of Conditions to the `fetch` or `update` methods, allowing you to specify a set of conditions such as `where userid \u003c 76 or (email = 'a@b.com' and username = 'geoff')`. The `conjunction` parameter specifies the conjunction with which the *following* condition or array of conditions relates to the current one: ‘or’ or ‘and’.\n\ni.e. The aforementioned nested set of conditions would be represented as follows:\n\n    User::fetch(\n        array(\n            new Condition('userid', 76, Condition::LessThan, Condition::_Or),\n            array(\n                new Condition('email', 'a@b.com'),\n                new Condition('username', 'geoff')\n            )\n        )\n    );\n\nThe default conjunction is `Condition::_And`.\n    \n    \n### Join objects\n\nYou can pass a Join object or an array of Join objects to a call to `fetch`, to join the results returned from this table to other tables.\n\nThe constructor has the arguments:\n\n- *class:* The name of the class (*not* the table name). For instance, `'User'`.\n- *columns:* The name of the column(s) on which to join. You can pass:\n    - a string – join tables on a single column \n    - an array of strings – join on several columns\n    - an associative array – the key specifies the home column, and the value specified the away column\n- *joins:* Optionally, a further Join (or array of Joins), from the away table to (an)other table(s).\n\nWhen more than one column is provided for a join, *all* specified columns’ values must be the same in both tables for the row to be joined. i.e. the generated query is as follows:\n\n    new Join(Thing, ['A', 'B'])\n    –\u003e ...left join things on users.A = things.A and users.B = things.B\n\n\n### Password columns\n\nIn previous versions, applying a condition to a column named 'password' was handled separately – the supplied test value was concatenated with an assumed `salt` column, then SHA’d and then MD5’d, and the result tested against the stored value.\n\nThis has changed in TinyModel 0.91. Set password fields as ordinary `char` or `varchar` columns, and use PHP’s new `password_hash()` and `password_verify()` functions to generate values/test stored values against authentication attempts. (These functions use the bcrypt algorithm, and automatically incorporate a per-user salt.)\n\n\n### Length restrictions \u0026 multibyte characters\n\nIt’s very good practice in the modern world to set the charset of tables to UTF8. Indeed, TinyModel considers this an assumption. But, note that:\n\n- For char/varchar columns, as of MySQL 5.1, with a length of N, you can store N *characters* in the field.\n- For text types, you have a byte limit, so the number of characters that can be stored is variable:\n\t- tinytext: 255 bytes\n\t- text: 65,535 bytes\n\t- mediumtext: 16,777,215 bytes\n\t- longtext: 4,294,967,295 bytes\n\ni.e. the maxlength restriction for `char` or `varchar` columns will count characters, whereas for `text` columns it will count bytes.\n\n\n## Usage in a web application\n\nFor a simple web app, you might typically define your TinyModel subclasses in a single file, thereby specifying the tables you wish to communicate with.\n\nThe controller layer includes this file, and can then interact with the subclasses, making calls to TinyModel’s `fetch`, `update` and `insert` methods.\n\nFor a functioning example, see the file `demo.php`. (You will need to need to create the relevant database and tables.)\n\n\n## License\n\nTinyModel is published under the MIT license, and comes with no warranty whatsoever.\n\n© Ben Hallstein\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbhallstein%2Ftinymodel","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbhallstein%2Ftinymodel","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbhallstein%2Ftinymodel/lists"}