{"id":17881273,"url":"https://github.com/sjonhortensius/basic_framework","last_synced_at":"2025-04-03T01:41:09.703Z","repository":{"id":146688065,"uuid":"182091729","full_name":"SjonHortensius/Basic_Framework","owner":"SjonHortensius","description":"Basic PHP Framework that focuses on performance","archived":false,"fork":false,"pushed_at":"2025-02-21T11:54:45.000Z","size":336,"stargazers_count":0,"open_issues_count":1,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-02-21T12:33:28.921Z","etag":null,"topics":["framework","framework-php","performance","php"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/SjonHortensius.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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":"2019-04-18T13:21:26.000Z","updated_at":"2025-02-21T11:54:49.000Z","dependencies_parsed_at":"2024-10-28T13:15:41.419Z","dependency_job_id":"f429004a-1824-4480-a9a2-66250db3a26f","html_url":"https://github.com/SjonHortensius/Basic_Framework","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SjonHortensius%2FBasic_Framework","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SjonHortensius%2FBasic_Framework/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SjonHortensius%2FBasic_Framework/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SjonHortensius%2FBasic_Framework/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SjonHortensius","download_url":"https://codeload.github.com/SjonHortensius/Basic_Framework/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246922237,"owners_count":20855343,"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":["framework","framework-php","performance","php"],"created_at":"2024-10-28T12:36:20.526Z","updated_at":"2025-04-03T01:41:09.678Z","avatar_url":"https://github.com/SjonHortensius.png","language":"PHP","readme":"# The Basic_Framework\n\nThis is a framework I've used for most projects I've developed over the years. It focuses on providing validated user-input,\nand a powerful yet simple ORM, combined with a simple template parser and router. The key focus is performance.\n\n## Development settings and features\n\nStart developing your application by specifying `PRODUCTION_MODE = false` in your config.ini. This enables backtraces from\nExceptions that bubble up, and allows you to use `Basic::debug()` to pretty-print variables. Using this will protect you\nfrom leaking private information when `PRODUCTION_MODE` is actually enabled.\n\nYou are also encouraged to use `Basic::$log` wherever required. When `PRODUCTION_MODE = false`, logs will be appended to\nyour HTML pages - showing you what the framework did, and how long it took.\n\n## Validated user-input\n\nUser-input is wrapped by the `Userinput` class which is exposed through `Basic::$userinput`. Your project should never\nreference any globals like `$_GET` / `$_POST` / `$_SERVER` since they are all untrusted. Instead you configure global inputs\nin your `config.ini`. We overload `$_REQUEST` and insert route-parameters in it, meaning /page/x/y will\nhave `$_REQUEST = ['page', 'x', 'y']`:\n\n```ini\n[Userinput.action]\nsource[superglobal] = REQUEST\nsource[key] = 0\nmaxLength = 16\ndefault = index\n```\n\nThis would expose `Basic::$userinput['action']` which has a maximum length of 16 characters, defaulting to `'index'`.\n\nFor action-specific inputs you define your configuration in your controller (which is called an `Action` since this framework\nuses action-based controllers): The controller is choosen based on the global *action* userinput you defined in\nyour `config.ini`.\n\n```php\nclass MySite_Action_ListThings extends MySite_Action # which would extend Basic_Action\n{\n\tpublic $userinputConfig = [\n\t\t'page' =\u003e [\n\t\t\t'valueType' =\u003e 'integer',\n\t\t\t'source' =\u003e ['superglobal' =\u003e 'REQUEST', 'key' =\u003e 1],\n\t\t\t'default' =\u003e 1,\n\t\t\t'minValue' =\u003e 1,\n\t\t\t'maxValue' =\u003e 9,\n\t\t],\n\t];\n}\n```\nThis would expose `Basic::$userinput['page']` which contains an integer between 1 and 9. If the user passes an invalid value,\nthe framework would simply block the request from ever getting to your Action. More specifically, your `init()` will be\nexecuted, but your `run()` won't.\n\n------------------\n\nCurrently, any `UserinputValue` can use the following configuration options:\n* `valueType` - internal php type to require\n* `required` *boolean* - Actions will not `run()` without all required Values being passed (and valid)\n* `inputType` *string* - used when asking the user for this value through a form. Valid values are any template listed in `Userinput/Type`,\n from either the framework or application\n* `source` - *array* defines where this value comes from. Can contain any superglobal, where `POST` values will be shown in forms\nand `REQUEST` is filled with the URL parts for this request\n* `default` - the default value, presented to the user in forms but also returned to the application when no value was\nspecified by the user\n* `description` - used in forms, to provide additional text relevant to this value\n* `regexp` - a PCRE this value should conform to, tested *after* `preCallback` and `preReplace` are processed\n* `values` - an array of allowed values. When 2 levels deep, will be presented as optgroups by `select.html`\n* `preCallback` - a callback to process this value. Could be `serialize` or any custom method. Applied *before* validation\n* `postCallback` - a callback to process this value. Applied *after* validation\n* `preReplace` - an array (pattern =\u003e replacement, passed to `preg_replace`) of PCREs to apply *before* validation\n* `postReplace` - an array (pattern =\u003e replacement, passed to `preg_replace`) of PCREs to apply *after* validation\n* `mimeTypes` - for uploaded files - allows filtering on specific mime-types, see `Basic_UserinputValue::_handleFile`\n* `options` - unvalidated array of key/value pairs which you can use in your application logic or templates\n\n## Basic ORM\n\nA basic ORM is provided by the `Entity` and `EntitySet` classes. You use these by extending them:\n\n```php\nclass MySite_Page extends Basic_Entity\n{\n\tprotected static $_relations = [\n\t\t'creator' =\u003e MySite_User::class,\n\t];\n\tprotected static $_numerical = ['viewCount'];\n}\n```\n\nYou define your actual properties in your database and they will become available when you retrieve the Entity. For example,\nyou could retrieve the title of your page by doing `MySite_Page::get($pageId)-\u003etitle`; or retrieve the name of it's creator\nby running `MySite_Page::get($pageId)-\u003ecreator-\u003ename`.\n\nAdditional features:\n* `::create(array)` - stores a new Entity in the database\n* `::save(array)` / `delete()` - update or delete an Entity\n* `::find('title = ?', [Basic::$userinput['pageTitle']])` - find all Entities matching the specified sql query\n* `::setUserinputDefault()` - useful for *CRUD* actions, specifies all properties of Entity as default for current Userinput\n* `::getRelated(MySite_Page::class)` - find all specified Entities with a relation to current object, eg. all pages a User\n has created: `MySite_User::get(1)-\u003egetRelated(MySite_Page::class)`\n\nThere is an extremely limited access-control exposed by the `protected _checkPermissions(string $action)` function that\n`Entity` will call on `load` / `save` / `delete` actions. This function should be overloaded by your Entity, and throw\nExceptions whenever something happens which is not allowed. Any property defined in the Entity can be used in these checks.\n\nThe second ORM feature is provided by `EntitySet` which you can also extend. For example:\n\n```php\nclass MySite_PageSet extends Basic_EntitySet\n{\n\tpublic function includeHistory()\n\t{\n\t\t$this-\u003eaddJoin(MySite_PageVersion::class, \"PageVersion.page = Page.id AND !PageVersion.deleted\");\n\t}\n}\n```\n\nThis would enable you to do `$history = MySite_Page::find(\"name = ?\", [Basic::$userinput['action']])-\u003eincludeHistory();`.\n\nAdditional features:\n* `getPage(offset, pageSize)` - retrieve specified page in a list of results\n* `getSingle` - use this when you need to enforce a single Entity from a query\n* `getSubset` - allows chaining multiple filters, eg. `$admins-\u003egetSubset(\"active = ?\", [true])`\n* `getSuperset` - similar to `Entity::getRelated` but for a set, eg. all pages created by a EntitySet of admins:\n `$admins-\u003egetSuperset(MySite_Page::class, \"Page.creator = User.id\")`\n\n## Basic template parser\n\nI believe a template-parser provides a healthy barrier to prevent developers from including too much logic in their templates,\nsince most of any processing should be done in the Action. This is why the Basic Template parser has a small feature-set:\n\n* Globally, any variable output is *untainted*; currently `htmlspecialchars` for html and `json_encode` for json are applied\n* Comments: `{!-- comment --}`\n  * are removed server-side before outputting\n* echo public variables from the `Basic` namespace: `Welcome to {userinput['action']}`\n  * allows using variables from controller/config or userinput.\n  * you are responsible for proper access notation, eg.\n    * `{userinput['index']}` for arrays\n    * `{userinput-\u003eaction-\u003edescription}` for objects\n  * statements without source-prefix will be fetched from the Action\n    * `{currentUser-\u003ename}` will return `Action-\u003ecurrentUser-\u003ename`\n* to circumvent the tainting, eg to output HTML, prefix with `*`: `{*currentPage-\u003egetHtml()}`\n* execute generic PHP statement (note the trailing semicolon): `{print phpversion();}`\n* small inline if-statements: `{if ($this-\u003einput-\u003eoptions['multiple'])}multiple{/}`\n\nFor larger if/else-statements or while / foreach loops, a block syntax is supported. Please note using proper indenting\n(using tabs) is paramount or the ending will not be detected properly:\n\n```html\n{foreach ($this-\u003einput-\u003evalues as $this-\u003evalue =\u003e $this-\u003edescription)}\n\t{if ($this-\u003evalue-\u003einputType == 'radio')}\n\t\t\u003cinput type=\"radio\" name=\"test\" id=\"test\" value=\"{value}\" /\u003e\n\t\t\u003clabel for=\"test\"\u003e{description}\u003c/label\u003e\n\t{:}\n\t\t\u003cinput type=\"text\" name=\"test\" id=\"test\" value=\"{value}\" /\u003e\n\t{/}\n{/}\n```\n\nFor if-statements the *else* part `{:}stuff` can be omitted.\n\n### Templates for Forms\n\nBased on the userinputConfig, the framework can automatically generate a form whenever parameters are missing or invalid.\nThis should simplify CRUD actions as you can have a simple Action for editing Entities:\n\n```php\nclass MySite_Action_UpdateUser extends Basic_Action\n{\n\tpublic $userinputConfig = [\n\t\t'id' =\u003e [\n\t\t\t'valueType' =\u003e 'integer',\n\t\t\t'required' =\u003e true,\n\t\t\t'source' =\u003e ['superglobal' =\u003e 'REQUEST', 'key' =\u003e 1],\n\t\t],\n\t\t'name' =\u003e ['valueType' =\u003e 'string', 'required' =\u003e true],\n\t\t'contact' =\u003e ['valueType' =\u003e 'string', 'inputType' =\u003e 'email'],\n\t];\n\tprotected $_user;\n\n\tpublic function init()\n\t{\n\t\t$this-\u003e_user = MySite_User::get(Basic::$userinput['id']);\n\t\t$this-\u003e_user-\u003esetUserinputDefault();\n\n\t\tparent::init();\n\t}\n\n\tpublic function run()\n\t{\n\t\t$this-\u003e_user-\u003esave(Basic::$userinput-\u003etoArray(false));\n\n\t\tparent::run();\n\t}\n}\n```\n\nTo customize the form displayed to the user you can use templates, eg. to customize the `contact` input, the following\npaths are checked:\n* `Userinput/UpdateUser/Name/contact.html`\n* `Userinput/UpdateUser/Type/email.html`\n* `Userinput/UpdateUser/Input.html`\n* `Userinput/Name/contact.html`\n* `Userinput/Type/email.html`\n* `Userinput/Input.html`\n\nThis allows overloading per Action or globally and per *name* or *inputType*. Any template not found in the application\ncan also be retrieved from the framework `templates/` directory.\n\n## Router\n\nAs an Action based framework, the `userinput.action` is required, to determine which Action (controller) to pass the request to.\nHowever, this routing can be customized by using a base class for your Actions and overloading the `Basic_Action::resolve`\nmethod. This allows you to override the action, for example to forward any unknown action to a *page* action for a cms:\n\n```php\nclass MySite_Action {\n\tpublic static function resolve(string $action, bool $hasClass, bool $hasTemplate): ?string\n\t{\n\t\t// If an Action class is defined, it is not a CMS page - so do not overwrite action\n\t\tif ($hasClass)\n\t\t\treturn null;\n\n\t\t// Otherwise, default to the MySite_Action_Page class\n\t\treturn 'page';\n\t}\n}\n```\n\nThere is also `getRoute` which is used to specify the form.action in generated templates.\n\n## Auto-generated Exceptions\n\nAny class ending with `Exception` will be created when referenced - using a proper hierarchy. Applications are encouraged to\nfollow the same notation used by the framework - which is `Class_Name_SpecificException`. For example - an action could \n`throw new MySite_Action_UpdateUser_UnknownEntityException('User %d does not exist', [$user-\u003eid, 404]);`  which results in\nthe following classes being created:\n\n* class `MySite_Action_UpdateUser_UnknownEntityException`\n* which extends `MySite_Action_UpdateUserException`\n* which extends `MySite_ActionException`\n* which extends `MySite_Exception`\n* which extends `Basic_Exception`\n* which extends `Exception`\n\nAny of these classes can also be actually included in the project, for example - your application could include a\n`MySite_Exception` which translates or pretifies all Exceptions. `printf` Syntax is supported using the first and second\nconstructor argument, while the third is a `HTTP` specific errorcode, being set on the `HTTP` response if this Exception\nisn't catched.","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsjonhortensius%2Fbasic_framework","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsjonhortensius%2Fbasic_framework","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsjonhortensius%2Fbasic_framework/lists"}