{"id":19647816,"url":"https://github.com/chances/php-bd","last_synced_at":"2026-05-15T02:48:20.699Z","repository":{"id":35338567,"uuid":"39600746","full_name":"chances/php-bd","owner":"chances","description":null,"archived":false,"fork":false,"pushed_at":"2015-07-25T01:51:50.000Z","size":140,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-01-09T22:23:52.703Z","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/chances.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}},"created_at":"2015-07-24T00:05:34.000Z","updated_at":"2015-07-24T00:06:05.000Z","dependencies_parsed_at":"2022-09-17T09:03:21.587Z","dependency_job_id":null,"html_url":"https://github.com/chances/php-bd","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/chances%2Fphp-bd","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chances%2Fphp-bd/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chances%2Fphp-bd/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chances%2Fphp-bd/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/chances","download_url":"https://codeload.github.com/chances/php-bd/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240957063,"owners_count":19884659,"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-11T14:46:07.917Z","updated_at":"2026-05-15T02:48:20.645Z","avatar_url":"https://github.com/chances.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"Superglobals\n===\nPredefined variables that are available in all scopes throughout a script. If\nsomething is not in a superglobal and you want to make it global, you need to do\n`global $variable`\n\nSee superglobal list\n[here](http://php.net/manual/en/language.variables.superglobals.php). \n\n`$GLOBALS`: all variables in global scope. \n\n`$_REQUEST` is a combo of `$_GET`, `$_POST`, `$_COOKIE`\n`$_ENV` contains envrionment variables\n`$_SERVER` contains info about the server\n`$_COOKIE` and `$_SERVER` store info about the current user and session\n\nRegular constants \n===\n`define('CONST_NAME, 'the value goes here')`\nMagic constants\n===\n[look at them](http://php.net/manual/en/language.constants.predefined.php)\n\nCookies and sessions\n===\n\nHTML is stateless (mostly! we do have html5 storage now...). Once the page has\nbeen delivered to your browser, by default the server forgets all about you.\n\nNetscape came up with cookies to remedy this problem. Cookies are stored in the\nbrowser. Sessions solve the same problem, but their data is stored on the\nserver.\n\nCookies:\n- are stored on the client - if you have a cluster of servers, this may be\n  better (the client doesn't have to talk to the same server every time)\n- have a finite lifespan\n- may have a limited size\n- all data must be sent with each request - the client will automatically send\n  all the cookie info related to the server with every request to the server, so\n  you don't want to store too much. In php this data goes to the superglobal\n  `$_COOKIE` automatically.\n- Only choice if you want site to remember the user tomorrow\n- If you have sensitive information, use a cookie to send an id, an store the\n  info in the database based on that id (not on the client)\n- Dangerous? Not really. A site can only read data that it has stored. This\n  doesn't stop the client from doing something nefarious though.\n\nA cookie is set like so:\n```php\n    // languageprefs.php\n\n    \u003c?php\n      if (!isset($_COOKIE['Language'])) {\n        setcookie(\"Language\", $_POST['preferred-language'], time() + 31536000);\n      }\n    ?\u003e\n\n    \u003cform method=\"post\" aciton=\"languageprefs.php\"\u003e\n      \u003cselect name=\"preferred-language\"\u003e\n        \u003coption value=\"en\"\u003eEnglish\u003c/option\u003e\n        \u003coption value=\"es\"\u003eEspañol\u003c/option\u003e\n      \u003c/select\u003e\n    \u003c/form\u003e\n```\n\nAfter this, the \"Language\" cookie would be sent with every request to this web\nsite for a year, and you could find it in the `$_COOKIE` superglobal.\n\nThere are more parameters that let you specify that the cookie can only be sent\nover HTTPS. That said, don't store sensitive information in cookies.\n\nSessions:\n- Combo of a server side cookie and a client side cookie. Client side cookie\n  contains only a reference to the correct data on the server. The browser sends\n  only this reference code. The server side cookie is stored in `$_SESSION`\n  automatically.\n- stored on the server: better for safe information:\n- you don't need to send much information with each request (just an ID)\n- unlimited size\n- cleaned up when the visitor closes their browser\n\nEither way, both are for 'disposable' information. Longer term data should be\nstored in the dataabase. If you store personal information in cookies, you\nshould encrypt it.\n\nTo use a session:\n\n```php\nsession_start();\n```\nthis tells the server sessions are going to be used. When this is called, PHP\nchecks whether the client has sent a session cookie. If it did, it will use that\nidentity to load the session data from the right session file. If not, PHP will\ncreate a session file on the server and send an ID back to the visitor to store\nin a new cookie.\n\nYou must call `session_start()` before accessing any session variables. It needs\nto be at the very top of the file.\n\nOnce this is done, you can set and get any session info for the current user\nusing the `$_SESSION` superglobal.\n\nYou can end a session with `session_destroy()`, but the client side session\ncookie will only persist until the client quits their browser.\n\nisset vs empty \n===\n`isset` checks whether an existing variable is set and is not null - this means\nit will return true for an empty string variable must exist first!  \n\n`empty` checks whether the evariable is an empty string, false, an empty array,\nnull, \"0\", 0, or does not exist, so it will return true for an empty string\n\nBase URLs\n===\n\nOn apache you can do this:\n```\n$_SERVER['REQUEST_SCHEME'] . '://' . $_SERVER['SERVER_NAME'] . $_SERVER['CONTEXT_PREFIX'];\n```\n\nBut you can't always rely on specific server variables. Usually this goes in a config file.\n\nPECL vs PEAR\n===\n\nPECL = PHP Extension Community Library, written in C, can be loaded into PHP. You need admin rights, a C compiler.\n\nUbuntu's `php5-pgsql` installs the `PDO_PGSQL` pecl package.\n\nPEAR = PHP Extension and Application Repository, libraries are IN php. It's a distribution/packageing system. It's possible to use it without root, but it's hard. Kind of like CPAN-hard.\n\nComposer is awesome! We don't use it.\n\nTalking to the DB\n=====\nYou need the php5-pgsql package for this to work.\n\npg_connect() and pg_pconnect()\n\nBoth Return a connection resource needed by other psql functions.\n\nConnecting to a postgres db takes rather more resources than connecting to a mysql db, so pg_connect is a bit more expensive than its php cousin.\n\nIf multiple calls with the same connstring are made to pg_pconnect specifically, the same connection will be returned - the connection stays around in persistent memory.\n\nWhen there are a lot of simultaneous connections, we can use a connection pooler like http://wiki.postgresql.org/wiki/PgBouncer to lighten the load, but pg_pconnect performs well enough.\n\nFPM mitigates the pooling need a bit, since the number of PHP workers is controlled, so pg_pconnect is a bit safer on FPM than mod_php or others.\n\n```php\n$connstring = \"dbname=\" . $config['db']['name']\n. \" host=\" . $config['db']['host']\n. \" user=\" . $config['db']['user']\n. \" password=\" . $config['db']['password'];\n\n// The persistent database connection\n$GLOBALS['DB'] = pg_pconnect($connstring);\nif(!$DB) die(\"Error: Can't connect to the database.\");\n```\n\nConnecting\n----\nGet a connstring and pass it to pg_pconnect():\nhttp://php.net/manual/en/function.pg-pconnect.php\ncheck that pg_pconnect() returned !null\n\n\nWrite simple functions for getting projects stuff out of the database; no joins yet:\n\n```php\nfunction getProjects() {\n  $query = \"SELECT * FROM redmine_projects\";\n  $result = pg_query($GLOBALS['DB'], $query);\n  return pg_fetch_all($result);\n}\n\nfunction getProjectById($id) {\n  $query = \"SELECT * FROM redmine_projects WHERE id = $1\";\n  $result = pg_query_params($GLOBALS['DB'], $query, array($id));\n  return pg_fetch_all($result);\n}\n\nfunction deleteProjectById($id) {\n  $query = \"DELETE FROM redmine_projects WHERE id = $1\";\n  $result = pg_query_params($GLOBALS['DB'], $query, array($id));\n  return pg_fetch_all($result);\n}\n```\n\nThen put it in the index:\n```php\n  \u003c?php\n  $allProjects = getProjects();\n  ?\u003e\n...\n  \u003c?php\n  foreach ($allProjects as $project) {\n  ?\u003e\n    \u003ctr\u003e\n      \u003ctd\u003e\u003c?= $project['name'] ?\u003e\u003c/td\u003e\n      \u003ctd\u003e\u003c?= $project['description'] ?\u003e\u003c/td\u003e\n      \u003ctd\u003e\u003c?= $project['expires'] ?\u003e\u003c/td\u003e\n      \u003ctd\u003e\u003c?= $project['repository_type'] ?\u003e\u003c/td\u003e\n      \u003ctd\u003e\u003ca href=\"detail.php\"\u003eDetails\u003c/a\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n  \u003c?php\n  } // End foreach allProjects\n  ?\u003e\n```\n\nBut we need the relation for project type, so let's add a join:\n\n```php\n  function getProjects() {\n    $query = \"SELECT projects.name, description, expires, repository_types.name as repository_type\n              FROM projects\n              JOIN repository_types\n              ON (projects.repository_type = repository_types.id)\";\n    $result = pg_query($GLOBALS['DB'], $query);\n    return pg_fetch_all($result);\n  }\n```\n\nNow the page displays the actual repo type instead of the id.\n\nMake detail links:\n1. add `projects.id` to the select statement so we get ids out\n2. add `detail.php?id=\u003c?= $project['id'] ?\u003e` to the detail url in the table.\n\nTime to use the info on the detail page, but first make sure we have an id:\n\n```php\nif(!isset($_GET['id'])) {\n  header('Location: ' . $GLOBALS['BASE_URL']);\n  die(); // not all clients respect location headers\n}\n\n$project = getProjectById($_GET['id']);\n```\n\nWe'll also need to modify our project fetching query to join and get the repository type.\n\n```\nfunction getProjectById($id) {\n  $query = \"SELECT projects.id, projects.name, description, expires, repository_types.name as repository_type\n              FROM projects\n              JOIN repository_types\n              ON (projects.repository_type = repository_types.id)\n              WHERE projects.id = $1\";\n  $result = pg_query_params($GLOBALS['DB'], $query, array($id));\n  return pg_fetch_all($result);\n}\n```\n\nWhen you echo $project['name'] to the page, it fails - why? pg_fetch_all returns an array of rows - and even though we only have one row, it's not smart enough to know that. It returns an array of one.\n\nChange `pg_fetch_all` to `pg_fetch_array`, and you'll be able to do $project['name'] successfully.\n\nMake new project form work:\n\nNotice that our repository type dropdown contains nothing. We need to get database info about repo types that are availablei in there.\n\nAdd a function:\n\n```php\nfunction getRepositoryTypes() {\n  $query = \"SELECT * from repository_types\";\n  $result = pg_query($GLOBALS['DB'], $query);\n  return pg_fetch_all($result);\n}\n```\n\nUse this to populate the dropdown:\n\n```php\n$repositoryTypes = getRepositoryTypes();\n...\n\u003cselect\u003e\n  \u003coption value=\"\"\u003eNone\u003c/option\u003e\n  \u003c?php\n  foreach ($repositoryTypes as $repositoryType) {\n  ?\u003e\n      \u003coption value=\"\u003c?= $repositoryType['id'] ?\u003e\"\u003e\u003c?= $repositoryType['name'] ?\u003e\u003c/option\u003e\n  \u003c?php\n  }\n  ?\u003e\n\u003c/select\u003e\n```\n\n(now submit the form and dump `$_POST`)\n\nWe'll need an add function:\n\n```php\nfunction newProject($name, $description, $expires, $repository_type = NULL) {\n  $query = \"INSERT INTO projects (name, description, expires, repository_type) VALUES ( $1, $2, $3, $4)\";\n  $result = pg_query_params($GLOBALS['DB'], $query, array(\n    $name,\n    $description,\n    $expires,\n    $repository_type\n  ));\n  return pg_fetch_all($result);\n}\n```\n\nWe need to add a basic error check to make sure we have minimum information:\n\n```php\n    $repositoryTypes = getRepositoryTypes();\n\n    if(!empty($_POST)) {\n      if(empty($_POST['name']) || empty($_POST['description']) || empty($_POST['expires'])) {\n        $message = \"Please fill in a name, a description, and an expiration date.\";\n      } else {\n        newProject($_POST['name'], $_POST['description'], $_POST['expires'], $_POST['repository_type']);\n        $message = \"Project created.\";\n      }\n    }\n    ?\u003e\n\n...\n    \u003c?php if(isset($message)) { ?\u003e\n    \u003cp\u003e\u003cstrong\u003e\u003c?= $message ?\u003e\u003c/strong\u003e\u003c/p\u003e\n    \u003c?php } ?\u003e\n```\n\nMake the delete form work:\n\nWe need a project id on this form too, so add this to the top:\n\n```\nif(!empty($_POST)) {\n  if(empty($_POST['name']) || empty($_POST['description']) || empty($_POST['expires'])) {\n    $message = \"Please fill in a name, a description, and an expiration date.\";\n  } else {\n    newProject($_POST['name'], $_POST['description'], $_POST['expires'], $_POST['repository_type']);\n    $message = \"Project created.\";\n  }\n}\n?\u003e\n```\n\nAnd make sure we pass in an id from the project detail view:\n\n```php\n  \u003ca class=\"button button-primary\" href=\"delete.php?id=\u003c?= $project['id'] ?\u003e\"\u003eDelete this project\u003c/a\u003e\n```\nNow to get the id of the project to delete into the form: add a hidden input:\n\n```php\n    \u003cform action=\"delete.php\" method=\"post\"\u003e\n      \u003cinput type=\"hidden\" name=\"id\" value=\"\u003c?= $_GET['id'] ?\u003e\"\u003e\n      \u003cdiv class=\"row\"\u003e\n        \u003ca class=\"button\" href=\"index.php\"\u003eNo, cancel\u003c/a\u003e\n        \u003cinput class=\"button button-primary\" type=\"submit\" value=\"Yes, delete it\"\u003e\n      \u003c/div\u003e\n    \u003c/form\u003e\n```\nAnd process it above (BEFORE the `$_GET` check)\n\n```php\nif(!empty($_POST['id'])) {\n  deleteProjectById($_POST['id']);\n  header('Location: ' . $GLOBALS['BASE_URL']);\n  die();\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchances%2Fphp-bd","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchances%2Fphp-bd","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchances%2Fphp-bd/lists"}