{"id":47597846,"url":"https://github.com/omniphx/forrest","last_synced_at":"2026-04-01T18:29:20.445Z","repository":{"id":16598425,"uuid":"19352938","full_name":"omniphx/forrest","owner":"omniphx","description":"A Laravel library for Salesforce","archived":false,"fork":false,"pushed_at":"2026-03-24T16:21:44.000Z","size":648,"stargazers_count":272,"open_issues_count":33,"forks_count":126,"subscribers_count":8,"default_branch":"master","last_synced_at":"2026-03-28T01:19:41.532Z","etag":null,"topics":["laravel","lumen","rest","rest-api","salesforce"],"latest_commit_sha":null,"homepage":"https://omniphx.github.io/forrest/","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/omniphx.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","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,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":["omniphx"],"patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":null}},"created_at":"2014-05-01T18:05:06.000Z","updated_at":"2026-03-24T16:17:43.000Z","dependencies_parsed_at":"2026-02-02T13:29:36.255Z","dependency_job_id":null,"html_url":"https://github.com/omniphx/forrest","commit_stats":{"total_commits":280,"total_committers":52,"mean_commits":5.384615384615385,"dds":0.4107142857142857,"last_synced_commit":"6682a01605a42257f9c9aab0ab44fbcda49f4a3d"},"previous_names":[],"tags_count":81,"template":false,"template_full_name":null,"purl":"pkg:github/omniphx/forrest","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/omniphx%2Fforrest","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/omniphx%2Fforrest/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/omniphx%2Fforrest/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/omniphx%2Fforrest/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/omniphx","download_url":"https://codeload.github.com/omniphx/forrest/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/omniphx%2Fforrest/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31256208,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-31T18:32:52.363Z","status":"ssl_error","status_checked_at":"2026-03-31T18:32:51.507Z","response_time":111,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["laravel","lumen","rest","rest-api","salesforce"],"created_at":"2026-04-01T18:29:17.895Z","updated_at":"2026-04-01T18:29:20.438Z","avatar_url":"https://github.com/omniphx.png","language":"PHP","funding_links":["https://github.com/sponsors/omniphx"],"categories":[],"sub_categories":[],"readme":"# Salesforce REST API Client for Laravel \u003cimg align=\"right\" src=\"https://raw.githubusercontent.com/omniphx/images/master/Forrest.png\"\u003e\n\n[![Laravel](https://img.shields.io/badge/Laravel-6.x--13.x-orange.svg?style=flat-square)](https://laravel.com)\n[![Latest Stable Version](https://img.shields.io/packagist/v/omniphx/forrest.svg?style=flat-square)](https://packagist.org/packages/omniphx/forrest)\n[![Total Downloads](https://img.shields.io/packagist/dt/omniphx/forrest.svg?style=flat-square)](https://packagist.org/packages/omniphx/forrest)\n[![License](https://img.shields.io/packagist/l/omniphx/forrest.svg?style=flat-square)](https://packagist.org/packages/omniphx/forrest)\n[![Actions Status](https://github.com/omniphx/forrest/workflows/Tests/badge.svg)](https://github.com/omniphx/forrest/actions)\n\nForrest is a Salesforce/Force.com REST API client for Laravel and Lumen.\n\n## Installation\n\nInstall Forrest with Composer:\n\n```bash\ncomposer require omniphx/forrest\n```\n\n### Laravel Installation\n\nThe package automatically registers the service provider and `Forrest` alias for supported Laravel versions.\n\n### Lumen Installation\n\n```php\nclass_alias('Omniphx\\Forrest\\Providers\\Laravel\\Facades\\Forrest', 'Forrest');\n$app-\u003eregister(Omniphx\\Forrest\\Providers\\Lumen\\ForrestServiceProvider::class);\n$app-\u003econfigure('forrest');\n$app-\u003ewithFacades();\n```\n\nThen you'll utilize the Lumen service provider by registering it in the `bootstrap/app.php` file.\n\n### Configuration\n\nYou will need a configuration file to add your credentials. Publish the package config using Artisan:\n\n```bash\nphp artisan vendor:publish --provider=\"Omniphx\\\\Forrest\\\\Providers\\\\Laravel\\\\ForrestServiceProvider\"\n```\n\nThis will publish a `config/forrest.php` file that can switch between authentication types as well as other settings.\n\nAfter adding the config file, update your `.env` to include the following values (details for getting a consumer key and secret are outlined below):\n\n```txt\nSF_CONSUMER_KEY=123455\nSF_CONSUMER_SECRET=ABCDEF\nSF_CALLBACK_URI=https://test.app/callback\n\nSF_LOGIN_URL=https://login.salesforce.com\n# For sandbox: SF_LOGIN_URL=https://test.salesforce.com\n\nSF_USERNAME=mattjmitchener@gmail.com\nSF_PASSWORD=password123\n```\n\n\u003e For Lumen, you should copy the config file from `src/config/config.php` and add it to a `forrest.php` configuration file under a config directory in the root of your application.\n\n## Getting Started\n\n### Setting up a Connected App\n\n1. Log into to your Salesforce org\n2. Click on Setup in the upper right-hand menu\n3. Search App in quick find box, and select `App Manager`\n4. Click New Connected App.\n5. Enter the following details for the remote application:\n   - Connected App Name\n   - API Name\n   - Contact Email\n   - Enable OAuth Settings under the API dropdown\n   - Callback URL\n   - Select access scope (If you need a refresh token, specify it here)\n6. Click `Save`\n\nAfter saving, you will now be given a Consumer Key and Consumer Secret. Update your config file with values for `consumerKey`, `consumerSecret`, `loginURL` and `callbackURI`.\n\n### Setup\n\nCreating authentication routes\n\n#### Web Server authentication flow\n\n```php\nRoute::get('/authenticate', function()\n{\n    return Forrest::authenticate();\n});\n\nRoute::get('/callback', function()\n{\n    Forrest::callback();\n\n    return Redirect::to('/');\n});\n```\n\n#### Username-Password authentication flow\n\nWith the Username Password flow, you can directly authenticate with the `Forrest::authenticate()` method.\n\n\u003e To use this authentication you must add your username, and password to the config file. Security token might need to be amended to your password unless your IP address is whitelisted.\n\n```php\nRoute::get('/authenticate', function()\n{\n    Forrest::authenticate();\n    return Redirect::to('/');\n});\n```\n\n#### Client Credentials authentication flow\n\nWith the Client Credentials flow, you can directly authenticate with the `Forrest::authenticate()` method.\n\n\u003e Using this authentication method only requires your consumer secret and key. Your Salesforce Connected app must also have the \"Client Credentials Flow\" Enabled in its settings.\n\n```php\nRoute::get('/authenticate', function()\n{\n    Forrest::authenticate();\n    return Redirect::to('/');\n});\n```\n\n#### SOAP authentication flow\n\n(When you cannot create a connected App in Salesforce)\n\n1. Salesforce allows individual logins via a SOAP Login\n2. The Bearer access token returned from the SOAP login can be used similar to Oauth key\n3. Update your config file and set the `authentication` value to `UserPasswordSoap`\n4. Update your config file with values for `loginURL`, `username`, and `password`.\n   With the Username Password SOAP flow, you can directly authenticate with the `Forrest::authenticate()` method.\n\n\u003e To use this authentication you can add your username, and password to the config file. Security token might need to be amended to your password unless your IP address is whitelisted.\n\n```php\nRoute::get('/authenticate', function()\n{\n    Forrest::authenticate();\n    return Redirect::to('/');\n});\n```\n\nIf your application requires logging in to salesforce as different users, you can alternatively pass in the login url, username, and password to the `Forrest::authenticateUser()` method.\n\n\u003e Security token might need to be amended to your password unless your IP address is whitelisted.\n\n```php\nRoute::post('/authenticate', function(Request $request)\n{\n    Forrest::authenticateUser('https://login.salesforce.com',$request-\u003eusername, $request-\u003epassword);\n    return Redirect::to('/');\n});\n```\n\n#### JWT authentication flow\n\nInitial setup\n\n1. Set `authentication` to `OAuthJWT` in `config/forrest.php`\n2. Generate a key and cert: `openssl req -newkey rsa:2048 -nodes -keyout server.key -x509 -days 365 -out server.crt`\n3. Configure private key in `config/forrest.php` (e.g., `file_get_contents('./../server.key'),`)\n\nSetting up a Connected App\n\n1. App Manager \u003e Create Connected App\n2. Enable Oauth Settings\n3. Check \"Use digital signatures\"\n4. Add `server.crt` or whatever you choose to name it\n5. Scope must includes \"refresh_token, offline_access\"\n6. Click Save\n\nNext you need to pre-authorize a profile (As of now, can only do this step in Classic but it's important)\n\n1. Manage Apps \u003e Connected Apps\n2. Click 'Edit' next to your application\n3. Set 'Permitted Users' = 'Admin approved users are pre-authorized'\n4. Save\n5. Go to Settings \u003e Manage Users \u003e Profiles and edit the profile of the associated user (i.e., Salesforce Administrator)\n6. Under 'Connected App Access' check the corresponding app name\n\nThe implementation is exactly the same as UserPassword (e.g., will need to explicitly specify a username and password)\n\n```php\nRoute::get('/authenticate', function()\n{\n    Forrest::authenticate();\n    return Redirect::to('/');\n});\n```\n\nFor connecting to Lightning orgs you will need to configure an `instanceUrl` inside your `forrest.php` config:\n\n```txt\nLightning: https://\u003cYOUR_ORG\u003e.my.salesforce.com\nLightning Sandbox: https://\u003cYOUR_ORG\u003e--\u003cSANDBOX_NAME\u003e.sandbox.my.salesforce.com\nDeveloper Org: https://\u003cDEV_DOMAIN\u003e.develop.my.salesforce.com\n```\n\n#### Custom login urls\n\nSometimes users will need to connect to a sandbox or custom URL. To do this, simply pass the URL as an argument to the authentication method:\n\n```php\nRoute::get('/authenticate', function()\n{\n    $loginURL = 'https://test.salesforce.com';\n\n    return Forrest::authenticate($loginURL);\n});\n```\n\n\u003e Note: You can specify a default login URL in your config file.\n\n## Basic usage\n\nAfter authentication, your app will store an encrypted authentication token which can be used to make API requests.\n\n### Query a record\n\n```php\nForrest::query('SELECT Id FROM Account');\n```\n\nSample result:\n\n```php\n(\n    [totalSize] =\u003e 2\n    [done] =\u003e 1\n    [records] =\u003e Array\n        (\n            [0] =\u003e Array\n                (\n                    [attributes] =\u003e Array\n                        (\n                            [type] =\u003e Account\n                            [url] =\u003e /services/data/v48.0/sobjects/Account/0013I000004zuIXQAY\n                        )\n\n                    [Id] =\u003e 0013I000004zuIXQAY\n                )\n\n            [1] =\u003e Array\n                (\n                    [attributes] =\u003e Array\n                        (\n                            [type] =\u003e Account\n                            [url] =\u003e /services/data/v48.0/sobjects/Account/0013I000004zuIcQAI\n                        )\n                    [Id] =\u003e 0013I000004zuIcQAI\n                )\n        )\n)\n```\n\nIf you are querying more than 2000 records, your response will include:\n\n```php\n(\n    [nextRecordsUrl] =\u003e /services/data/v20.0/query/01gD0000002HU6KIAW-2000\n)\n```\n\nSimply, call `Forrest::next($nextRecordsUrl)` to return the next 2000 records.\n\n### Create a new record\n\nRecords can be created using the following format.\n\n```php\nForrest::sobjects('Account',[\n    'method' =\u003e 'post',\n    'body'   =\u003e ['Name' =\u003e 'Dunder Mifflin']\n]);\n```\n\n### Update a record\n\nUpdate a record with the PUT method.\n\n```php\nForrest::sobjects('Account/001i000000xxx',[\n    'method' =\u003e 'put',\n    'body'   =\u003e [\n        'Name'  =\u003e 'Dunder Mifflin',\n        'Phone' =\u003e '555-555-5555'\n    ]\n]);\n```\n\n### Upsert a record\n\nUpdate a record with the PATCH method and if the external Id doesn't exist, it will insert a new record.\n\n```php\n$externalId = 'XYZ1234';\n\nForrest::sobjects('Account/External_Id__c/' . $externalId, [\n    'method' =\u003e 'patch',\n    'body'   =\u003e [\n        'Name'  =\u003e 'Dunder Mifflin',\n        'Phone' =\u003e '555-555-5555'\n    ]\n]);\n```\n\n### Delete a record\n\nDelete a record with the DELETE method.\n\n```php\nForrest::sobjects('Account/001i000000xxx', ['method' =\u003e 'delete']);\n```\n\n### Setting headers\n\nSometimes you need the ability to set custom headers (e.g., creating a Lead with an assignment rule)\n\n```php\nForrest::sobjects('Lead',[\n    'method' =\u003e 'post',\n    'body' =\u003e [\n        'Company' =\u003e 'Dunder Mifflin',\n        'LastName' =\u003e 'Scott'\n    ],\n    'headers' =\u003e [\n        'Sforce-Auto-Assign' =\u003e '01Q1N000000yMQZUA2'\n    ]\n]);\n```\n\n\u003e To disable assignment rules, use `'Sforce-Auto-Assign' =\u003e 'false'`\n\n### XML format\n\nChange the request/response format to XML with the `format` key or make it default in your config file.\n\n```php\nForrest::sobjects('Account',['format'=\u003e'xml']);\n```\n\n## API Requests\n\nWith the exception of the `search` and `query` resources, all resources are requested dynamically using method overloading.\n\nYou can determine which resources you have access to by calling with the resource method\n\n```php\nForrest::resources();\n```\n\nThis sample output shows the resources available to call via the API:\n\n```php\nArray\n(\n    [sobjects] =\u003e /services/data/v30.0/sobjects\n    [connect] =\u003e /services/data/v30.0/connect\n    [query] =\u003e /services/data/v30.0/query\n    [theme] =\u003e /services/data/v30.0/theme\n    [queryAll] =\u003e /services/data/v30.0/queryAll\n    [tooling] =\u003e /services/data/v30.0/tooling\n    [chatter] =\u003e /services/data/v30.0/chatter\n    [analytics] =\u003e /services/data/v30.0/analytics\n    [recent] =\u003e /services/data/v30.0/recent\n    [process] =\u003e /services/data/v30.0/process\n    [identity] =\u003e https://login.salesforce.com/id/00Di0000000XXXXXX/005i0000000aaaaAAA\n    [flexiPage] =\u003e /services/data/v30.0/flexiPage\n    [search] =\u003e /services/data/v30.0/search\n    [quickActions] =\u003e /services/data/v30.0/quickActions\n    [appMenu] =\u003e /services/data/v30.0/appMenu\n)\n```\n\nFrom the list above, I can call resources by referring to the specified key.\n\n```php\nForrest::theme();\n```\n\nOr...\n\n```php\nForrest::appMenu();\n```\n\nAdditional resource url parameters can also be passed in\n\n```php\nForrest::sobjects('Account/describe/approvalLayouts/');\n```\n\nAs well as new formatting options, headers or other configurations\n\n```php\nForrest::theme(['format'=\u003e'xml']);\n```\n\n### Upsert multiple records (Bulk API 2.0)\n\nBulk API requests are especially handy when you need to quickly load large amounts of data into your Salesforce org. The key differences is that it requires at least three separate requests (Create, Add, Close), and the data being loaded is sent in a CSV format.\n\nTo illustrate, following are three requests to upsert a CSV of `Contacts` records.\n\n#### Create\n\nCreate a bulk upload job with the POST method, the body contains the following job properties:\n\n- `object` is the type of objects you're loading (they must all be the same type per job)\n- `externalIdFieldName` is the external ID, if this exists it'll update and if it doesn't a new record will be inserted. Only needed for upsert operations.\n- `contentType` is CSV, this is currently the only valid value.\n- `operation` is set to `upsert` to both add and update records.\n\nWe're storing the response in `$bulkJob` in order to reference the unique Job ID in the Add and Close requests below.\n\n\u003e See [Create a Job](https://developer.salesforce.com/docs/atlas.en-us.api_bulk_v2.meta/api_bulk_v2/create_job.htm) for the full list of options available here.\n\n```php\n$bulkJob = Forrest::jobs('ingest', [\n    'method' =\u003e 'post',\n    'body' =\u003e [\n        \"object\" =\u003e \"Contact\",\n        \"externalIdFieldName\" =\u003e \"externalId\",\n        \"contentType\" =\u003e \"CSV\",\n        \"operation\" =\u003e \"upsert\"\n    ]\n]);\n```\n\n#### Add Data\n\nUsing the Job ID from the Create POST request, you then send the CSV data to be processed using a PUT request. This assumes you've loaded your CSV contents to `$csv`\n\n\u003e See [Prepare CSV Files](https://developer.salesforce.com/docs/atlas.en-us.api_bulk_v2.meta/api_bulk_v2/datafiles_prepare_csv.htm) for details on how it should be formatted.\n\n```php\nForrest::jobs('ingest/' . $bulkJob['id'] . '/batches', [\n    'method' =\u003e 'put',\n    'headers' =\u003e [\n        'Content-Type' =\u003e 'text/csv'\n    ],\n    'body' =\u003e $csv\n]);\n```\n\n#### Close\n\nYou must close the job before the records can be processed, to do so you send an `UploadComplete` state using a PATCH request to the Job ID.\n\n\u003e See [Close or Abort a Job](https://developer.salesforce.com/docs/atlas.en-us.api_bulk_v2.meta/api_bulk_v2/close_job.htm) for more options and details on how to abort a job.\n\n```php\n$response = Forrest::jobs('ingest/' . $bulkJob['id'] . '/', [\n    'method' =\u003e 'patch',\n    'body' =\u003e [\n        \"state\" =\u003e \"UploadComplete\"\n    ]\n]);\n```\n\n\u003e **Bulk API 2.0 is available in API version 41.0 and later**. For more information on Salesforce Bulk API, check out the [official documentation](https://developer.salesforce.com/docs/atlas.en-us.api_bulk_v2.meta/api_bulk_v2/introduction_bulk_api_2.htm) and [this tutorial](https://trailhead.salesforce.com/en/content/learn/modules/api_basics/api_basics_bulk) on how to perform a successful Bulk Upload.\n\n### Additional API Requests\n\n#### Refresh\n\nIf a refresh token is set, the server can refresh the access token on the user's behalf. Refresh tokens are only for the Web Server flow.\n\n```php\nForrest::refresh();\n```\n\n\u003e If you need a refresh token, be sure to specify this under `access scope` in your [Connected App](#setting-up-connected-app). You can also specify this in your configuration file by adding `'scope' =\u003e 'full refresh_token'`. Setting scope access in the config file is optional, the default scope access is determined by your Salesforce org.\n\n#### Revoke\n\nThis will revoke the authorization token. The session will continue to store a token, but it will become invalid.\n\n```php\nForrest::revoke();\n```\n\n#### Versions\n\nReturns all currently supported versions. Includes the verison, label and link to each version's root:\n\n```php\nForrest::versions();\n```\n\n#### Resources\n\nReturns list of available resources based on the logged in user's permission and API version.\n\n```php\nForrest::resources();\n```\n\n#### Identity\n\nReturns information about the logged-in user.\n\n```php\nForrest::identity();\n```\n\n#### Base URL\n\nReturns the URL of the Salesforce instance with api info.\n\n```php\nForrest::getBaseUrl(); // https://my-instance.my.salesforce.com/services/data/v50.0\n```\n\n#### Instance URL\n\nReturns the URL of the Salesforce instance.\n\n```php\nForrest::getInstanceURL(); // https://my-instance.my.salesforce.com\n```\n\nFor a complete listing of API resources, refer to the [Force.com REST API Developer's Guide](http://www.salesforce.com/us/developer/docs/api_rest/api_rest.pdf)\n\n### Custom Apex endpoints\n\nIf you create a custom API using Apex, you can use the `custom()` method for consuming them.\n\n```php\nForrest::custom('/myEndpoint');\n```\n\nAdditional options and parameters can be passed in like this:\n\n```php\nForrest::custom('/myEndpoint', [\n    'method' =\u003e 'post',\n    'body' =\u003e ['foo' =\u003e 'bar'],\n    'parameters' =\u003e ['flim' =\u003e 'flam']]);\n```\n\n\u003e Read [Creating REST APIs using Apex REST](https://developer.salesforce.com/page/Creating_REST_APIs_using_Apex_REST) for more information.\n\n### Raw Requests\n\nIf needed, you can make raw requests to an endpoint of your choice.\n\n```php\nForrest::get('/services/data/v20.0/endpoint');\nForrest::head('/services/data/v20.0/endpoint');\nForrest::post('/services/data/v20.0/endpoint', ['my'=\u003e'param']);\nForrest::put('/services/data/v20.0/endpoint', ['my'=\u003e'param']);\nForrest::patch('/services/data/v20.0/endpoint', ['my'=\u003e'param']);\nForrest::delete('/services/data/v20.0/endpoint');\n```\n\n### Get file body from ContentVersion and Attachment\n\nYou can use the Forrest::getContentVersionBody() and Forrest::getAttachmentBody() to retrieve the content of the\nuploaded files. They return a streamed response, so it may be a bit cumbersome to use if now used to streams.\nBellow you can find an example to retrieve the content of a uploaded content version.\n\n```php\n# example\n$data = Forrest::getContentVersionBody($version-\u003eId);\n$content =  $data-\u003egetBody()-\u003egetContents();\n```\n\n### Raw response output\n\nBy default, this package will return the body of a response as either a deserialized JSON object or a SimpleXMLElement object.\n\nThere might be times, when you would rather handle this differently. To do this, simply use the format of 'none' and the code will return the entire response body as a string.\n\n```php\n$response = Forrest::sobjects($resource, ['format'=\u003e 'none']);\necho $response; // Unformatted string\n```\n\n### Event Listener\n\nThis package makes use of Guzzle's event listers\n\n```php\nEvent::listen('forrest.response', function($request, $response) {\n    dd((string) $response);\n});\n```\n\n### Creating multiple instances of Forrest\n\nThere might be situations where you need to make calls to multiple Salesforce orgs. This can only be achieved only with the UserPassword flows.\n\n1. Set storage = `object` in the config file. This will store the token inside the object instance:\n\n```php\n'storage'=\u003e [\n    'type' =\u003e 'object'\n],\n```\n\n2. Create a multiple instance with the laravel `app()-\u003emake()` helper function:\n\n```php\n$forrest1 = app()-\u003emake('forrest');\n$forrest1-\u003esetCredentials(['username' =\u003e 'user@email.com.org1', 'password'=\u003e '1234']);\n$forrest1-\u003eauthenticate();\n\n$forrest2 = app()-\u003emake('forrest');\n$forrest2-\u003esetCredentials(['username' =\u003e 'user@email.com.org2', 'password'=\u003e '1234']);\n$forrest2-\u003eauthenticate();\n```\n\nFor more information about Guzzle responses and event listeners, refer to their [documentation](http://guzzle.readthedocs.org).\n\n### Creating a custom store\n\nIf you'd prefer to use storage other than `session`, `cache` or `object`, you can implement a custom implementation by configuring a custom class instance in `storage.type`:\n\n```php\n'storage' =\u003e [\n    'type' =\u003e App\\Storage\\CustomStorage::class,\n],\n```\n\nYou class can be named anything but it must implement `Omniphx\\Forrest\\Interfaces\\StorageInterface`:\n\n```php\n\u003c?php\n\nnamespace App\\Storage;\n\nuse Session;\nuse Omniphx\\Forrest\\Exceptions\\MissingKeyException;\nuse Omniphx\\Forrest\\Interfaces\\StorageInterface;\n\nclass CustomStorage implements StorageInterface\n{\n    public $path;\n\n    public function __construct()\n    {\n        $this-\u003epath = 'app.custom.path';\n    }\n\n    /**\n     * Store into session.\n     *\n     * @param $key\n     * @param $value\n     *\n     * @return void\n     */\n    public function put($key, $value)\n    {\n        return Session::put($this-\u003epath.$key, $value);\n    }\n\n    /**\n     * Get from session.\n     *\n     * @param $key\n     *\n     * @return mixed\n     */\n    public function get($key)\n    {\n        if(!$this-\u003ehas($key)) {\n            throw new MissingKeyException(sprintf('No value for requested key: %s', $key));\n        }\n\n        return Session::get($this-\u003epath.$key);\n    }\n\n    /**\n     * Check if storage has a key.\n     *\n     * @param $key\n     *\n     * @return bool\n     */\n    public function has($key)\n    {\n        return Session::has($this-\u003epath.$key);\n    }\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fomniphx%2Fforrest","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fomniphx%2Fforrest","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fomniphx%2Fforrest/lists"}