Symfony 4 app with Braintree JS drop-in UI
I have created a service called Braintree to start testing.
namespace App\Services;
use Braintree\ClientToken;
class Braintree
{
// environment variables:
const ENVIRONMENT = 'BRAINTREE_ENVIRONMENT';
const MERCHANT_ID = 'BRAINTREE_MERCHANT_ID';
const PUBLIC_KEY = 'BRAINTREE_PUBLIC_KEY';
const PRIVATE_KEY = 'BRAINTREE_PRIVATE_KEY';
/** #var \Braintree_Gateway */
private $gateway;
function __construct() {
$gateway = new \Braintree_Gateway([
'environment' => getenv(self::ENVIRONMENT),
'merchantId' => getenv(self::MERCHANT_ID),
'publicKey' => getenv(self::PUBLIC_KEY),
'privateKey' => getenv(self::PRIVATE_KEY)
]);
}
public function generate() {
return ClientToken::generate();
}
I am getting the following error:
HTTP 500 Internal Server Error
Braintree\Configuration::merchantId needs to be set (or accessToken needs to be passed to Braintree\Gateway).
The BT config has been properly entered into the .env file. Why is it not setting the MERCHANT_ID?
Edit:
Add config
Braintree_Gateway:
class: Braintree_Gateway
arguments:
-
'environment': '%env(BRAINTREE_ENVIRONMENT)%'
'merchantId': '%env(BRAINTREE_MERCHANT_ID)%'
'publicKey': '%env(BRAINTREE_PUBLIC_KEY)%'
'privateKey': '%env(BRAINTREE_PRIVATE_KEY)%'
Edit 2:
The stack trace:
Braintree\Exception\
Configuration
in vendor\braintree\braintree_php\lib\Braintree\Configuration.php (line 261)
public function assertHasAccessTokenOrKeys() { if (empty($this->_accessToken)) { if (empty($this->_merchantId)) { throw new Exception\Configuration('Braintree\\Configuration::merchantId needs to be set (or accessToken needs to be passed to Braintree\\Gateway).'); } else if (empty($this->_environment)) { throw new Exception\Configuration('Braintree\\Configuration::environment needs to be set.'); } else if (empty($this->_publicKey)) { throw new Exception\Configuration('Braintree\\Configuration::publicKey needs to be set.'); } else if (empty($this->_privateKey)) {
Configuration->assertHasAccessTokenOrKeys()
in vendor\braintree\braintree_php\lib\Braintree\ClientTokenGateway.php (line 34)
ClientTokenGateway->__construct(object(Gateway))
in vendor\braintree\braintree_php\lib\Braintree\Gateway.php (line 59)
Gateway->clientToken()
in vendor\braintree\braintree_php\lib\Braintree\ClientToken.php (line 18)
ClientToken::generate()
in src\Services\Braintree.php (line 25)
Braintree->generate()
in src\Controller\ProfileController.php (line 50)
ProfileController->booking_new(object(EntityManager), object(Request), object(Braintree))
in vendor\symfony\http-kernel\HttpKernel.php (line 149)
Edit 3:
namespace App\Services;
use Braintree_Gateway;
class Braintree extends Braintree_Gateway
{
//Configure Braintree Environment
public function __construct(Braintree_Gateway $gateway)
{
$this->$gateway = new Braintree_Gateway([
'environment' => 'sandbox',
'merchantId' => 'n5z3tjxh8zd6272k',
'publicKey' => 'v4rjdzqk3gykw4kv',
'privateKey' => '4ab8b962e81ee8c43bf6fa837cecfb97'
]);
}
//Generate a client token
public function generate() {
return $clientToken = $this->clientToken()->generate();
}
}
Error is now:
Catchable Fatal Error: Object of class Braintree\Gateway could not be converted to string
Am I getting closer to generating the client token??
The .env-file does not actually populate the system environment. Instead it works as a fallback when the environment is not set. Your call to getenv() only takes into account the system environment. For your file to take effect you have to use the Service container.
#config/services.yaml
services:
_defaults:
autowire: true
autoconfigure: true
#... all the existing services
Braintree_Gateway:
class: Braintree_Gateway
arguments:
-
'environment': '%env(BRAINTREE_ENVIRONMENT)%'
'merchantId': '%env(BRAINTREE_MERCHANT_ID)%'
'publicKey': '%env(BRAINTREE_PUBLIC_KEY)%'
'privateKey': '%env(BRAINTREE_PRIVATE_KEY)%'
The special parameter %env()% will check your system environment for the variable first and if that is not set it will go through the .env files to see if there is a fallback defined. You can also read up on this in the docs: https://symfony.com/doc/current/configuration/external_parameters.html#environment-variables
This will give the service Braintree_Gateway, that you built manually inside your service. Just like with any other service you can inject it into your service instead and autowiring will pass in an already generated Braintree Gateway:
namespace App\Services;
use Braintree\ClientToken;
class Braintree
{
private $gateway;
public function __construct(\Braintree_Gateway $gateway)
{
$this->gateway = $gateway;
}
# ... methods using the gateway
}
Related
Im new to Lumen, I have managed to create and register the following ArangoDB service provider along with the service below and got it to work, but Im confused how I actually use them in another service or helper.
registered the provider in bootstrap/app.php
$app->register(App\Providers\ArangoServiceProvider::class);
ArangoServiceProvider.php
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class ArangoServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->singleton('ArangoService', function ($app) {
return new ArangoService($app);
});
}
public function boot()
{
//
}
}
ArangoService.php
<?
namespace App\Services;
use ArangoDBClient\Collection as ArangoCollection;
use ArangoDBClient\CollectionHandler as ArangoCollectionHandler;
use ArangoDBClient\Connection as ArangoConnection;
use ArangoDBClient\ConnectionOptions as ArangoConnectionOptions;
use ArangoDBClient\DocumentHandler as ArangoDocumentHandler;
use ArangoDBClient\Document as ArangoDocument;
use ArangoDBClient\Exception as ArangoException;
use ArangoDBClient\Export as ArangoExport;
use ArangoDBClient\ConnectException as ArangoConnectException;
use ArangoDBClient\ClientException as ArangoClientException;
use ArangoDBClient\ServerException as ArangoServerException;
use ArangoDBClient\Statement as ArangoStatement;
use ArangoDBClient\UpdatePolicy as ArangoUpdatePolicy;
class ArangoService{
public function __construct() {
$connectionOptions = [
// database name
ArangoConnectionOptions::OPTION_DATABASE => 'dbname',
// server endpoint to connect to
ArangoConnectionOptions::OPTION_ENDPOINT => 'tcp://123456789',
// authorization type to use (currently supported: 'Basic')
ArangoConnectionOptions::OPTION_AUTH_TYPE => 'Basic',
// user for basic authorization
ArangoConnectionOptions::OPTION_AUTH_USER => 'root',
// password for basic authorization
ArangoConnectionOptions::OPTION_AUTH_PASSWD => 'passwd',
// connection persistence on server. can use either 'Close' (one-time connections) or 'Keep-Alive' (re-used connections)
ArangoConnectionOptions::OPTION_CONNECTION => 'Keep-Alive',
// connect timeout in seconds
ArangoConnectionOptions::OPTION_TIMEOUT => 3,
// whether or not to reconnect when a keep-alive connection has timed out on server
ArangoConnectionOptions::OPTION_RECONNECT => true,
// optionally create new collections when inserting documents
ArangoConnectionOptions::OPTION_CREATE => true,
// optionally create new collections when inserting documents
ArangoConnectionOptions::OPTION_UPDATE_POLICY => ArangoUpdatePolicy::LAST,
];
// turn on exception logging (logs to whatever PHP is configured)
ArangoException::enableLogging();
$connection = new ArangoConnection($connectionOptions);
$collectionHandler = new ArangoCollectionHandler($connection);
$handler = new ArangoDocumentHandler($connection);
}
public function main(){
print "hello world!<br/>";
}
}
This all above works, but how do I in a controller, call a service, lets call it "UsersService" that has functions like GetUser() , how does this "UsersService" use the $connection, $collectionHandler and $handler , that I have created in my ArangoDB service.
route pointing to a controller (got that one)
controller calling UsersService
UsersService using the arango service and passing something back to the controller
grateful for any help!
I have recently been learning about the AppServiceProvider. I have registered a service in the AppServiceProvider which creates a singleton - an instantiated GuzzleHttp Client, like so:
$this->app->singleton('GuzzleHttp\Client', function($api) {
return new Client([
'base_uri' => env('ELASTICSEARCH_HOST'),
'auth' => [
env('ELASTICSEARCH_USER'),
env('ELASTICSEARCH_PASS')
],
]);
});
This is connecting to an ElasticSearch API, and that currently works:
$response = app('GuzzleHttp\Client')->request('GET');
I have set up a facade called ElasticSearchFacade, which contains only the getFacadeAccessor():
protected static function getFacadeAccessor()
{
return 'elasticSearch';
}
I have also registered elasticSearch in my AppServiceProvider, like so:
$this->app->bind('elasticSearch', function() {
return new ElasticSearch();
});
This creates a new ElasticSearch instance. However, I would love to pass the GuzzleHttp\Client into the elasticSearch service. So I have tried adding the following to my ElasticSearch.php file:
use GuzzleHttp\Client;
class ElasticSearch
{
protected $client;
public function __contruct(Client $client)
{
$this->client = $client;
}
public function handle()
{
$response = $this->client->request('GET');
die($response->getBody()->getContents());
}
}
I have now changed the registered service to pass through the GuzzleHttp Client like so:
$this->app->bind('elasticSearch', function() {
return new ElasticSearch(app('GuzzleHttp\Client'));
});
However I am getting the error:
PHP Error: Call to a member function request() on null
The constructor method is __construct not __contruct. You have not defined a custom constructor for your ElasticSearch class. So that member variable is null.
Side Note: do not call env outside of the configuration files.
To avoid having to make these env calls outside of configuration files you can just add configuration files as needed or add to current configuration files. Something like Elastic Search credentials can probably get added to the services.php configuration file:
<?php
return [
...
'elasticsearch' => [
'host' => env('ELASTICSEARCH_HOST'),
'user' => env('ELASTICSEARCH_USER'),
'password' => env('ELASTICSEARCH_PASS'),
],
...
];
Now that you have these in the configuration you can use the configuration system to pull these values:
config('services.elasticsearch'); // that whole array of values
config('services.elasticsearch.host'); // just that host value
Config::get('services.elasticsearch');
app('config')->get(...);
There are multiple ways to access the configuration system.
I'm first in using Paypal REST SDK when I want to create a payment. This error occurs.
Fatal error: Class '\PayPal\Log\PayPalDefaultLogFactory' not found in /xxx/xxx/xxx/xxx/xxx/xxx/xxx/xxx/vendor/paypal/rest-api-sdk-php/lib/PayPal/Core/PayPalLoggingManager.php on line 62
The error occurs while executing this code.
try {
$payment->create($apiContext);
// Get PayPal redirect URL and redirect the customer
$approvalUrl = $payment->getApprovalLink();
// Redirect the customer to $approvalUrl
} catch (PayPal\Exception\PayPalConnectionException $ex) {
echo $ex->getCode();
echo $ex->getData();
die($ex);
} catch (Exception $ex) {
die($ex);
}
How to solve this problem ??
** SDK version 1.14.0
Line 62 contains the code $factoryInstance->getLogger($loggerName)
private function __construct($loggerName)
{
$config = PayPalConfigManager::getInstance()->getConfigHashmap();
// Checks if custom factory defined, and is it an implementation of #PayPalLogFactory
$factory = array_key_exists('log.AdapterFactory', $config) && in_array('PayPal\Log\PayPalLogFactory', class_implements($config['log.AdapterFactory'])) ? $config['log.AdapterFactory'] : '\PayPal\Log\PayPalDefaultLogFactory';
/** #var PayPalLogFactory $factoryInstance */
$factoryInstance = new $factory();
$this->logger = $factoryInstance->getLogger($loggerName);
$this->loggerName = $loggerName;
}
If you look further up the code it is looking for the 'log.AdapterFactory' setting. This is normally not included in your ApiContext settings located in your config file.
Insert the log.AdapterFactory setting into your ApiContext settings located in your config file or bootstrap file whichever you have decided to create.
This is normally associated with the AdapterFactory that you are using.
Here is a very useful sample from the Github Repository illustrating the various settings that you can use with the new logger feature available in PayPal. Look in particular at line 94
$apiContext->setConfig(
array(
'mode' => 'sandbox',
'log.LogEnabled' => true,
'log.FileName' => '../PayPal.log',
'log.LogLevel' => 'DEBUG', // PLEASE USE `INFO` LEVEL FOR LOGGING IN LIVE ENVIRONMENTS
'cache.enabled' => true,
//'cache.FileName' => '/PaypalCache' // for determining paypal cache directory
// 'http.CURLOPT_CONNECTTIMEOUT' => 30
// 'http.headers.PayPal-Partner-Attribution-Id' => '123123123'
//'log.AdapterFactory' => '\PayPal\Log\DefaultLogFactory' // Factory class implementing \PayPal\Log\PayPalLogFactory
)
);
You will normally also have to create a Class MonologLogFactory using the official Paypal recommendations here. Remember to include a suitable namespace at the top of your file relative to path/folder/file/location. Here is the code from Paypal:
use PayPal\Log\PayPalLogFactory;
use Psr\Log\LoggerInterface;
class MonologLogFactory implements PayPalLogFactory
{
/**
* Returns logger instance implementing LoggerInterface.
*
* #param string $className
* #return LoggerInterface instance of logger object implementing LoggerInterface
*/
public function getLogger($className)
{
$logger = new Monolog\Logger($className);
$logger->pushHandler(new Monolog\Handler\StreamHandler("mail.log"));
return $logger;
}
}
If you are still encountering problems double check that you actually have the file located in C:\wamp32\www\projectfoldername\web\vendor\paypal\rest-api-sdk-php\lib\PayPal\Log\PayPalDefaultLogFactory on your testing platform. I am using wampserver.
If the file is not located here ensure that your composer.json file has "paypal/rest-api-sdk-php": "*" under it and then update composer.
In Symfony, when a user attempts to access a route which is forbidden for that specific user (according to the user roles), a page with response code 403 will be returned.
So the user can still see that there is a valid route there.
I would like to overwrite this behavior by replacing the status code 403 with 404, so the user will just see that there is no valid route when she/he is not allowed to access that resource.
How can I accomplish that?
This is doable, however almost undocumented. I'm aware of two ways but there might be even more:
Using access_denied_url configuration option. See security config reference. With this option you can set URL where the user is redirected when the user in unauthorized (I think it should work also with route name). See a similar question: Symfony2 Redirection for unauthorised page with access_denied_url
There're also "Entry Points" as mentioned in The Firewall and Authorization. However, no examples, no explanation how to use it.
I looks like this option expects a service name as can be seen in security config reference (search for entry_point option).
One possible solution, as partially explained here, can be the following:
1) Defining a new service controller in services.yml
exception_controller:
class: Path\To\MyBundle\Controller\MyExceptionController
arguments: ['#twig', '%kernel.debug%']
2) Creating the new class which overrides Symfony\Bundle\TwigBundle\Controller\ExceptionController:
namespace Path\To\MyBundle\Controller;
use Symfony\Bundle\TwigBundle\Controller\ExceptionController;
use Symfony\Component\Debug\Exception\FlattenException;
use Symfony\Component\HttpKernel\Log\DebugLoggerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class MyExceptionController extends ExceptionController
{
public function showAction(Request $request, FlattenException $exception, DebugLoggerInterface $logger = null)
{
$currentContent = $this->getAndCleanOutputBuffering($request->headers->get('X-Php-Ob-Level', -1));
$showException = $request->attributes->get('showException', $this->debug); // As opposed to an additional parameter, this maintains BC
$code = $exception->getStatusCode();
if ($code == 403) {
$code = 404;
// other customizations ...
}
return new Response($this->twig->render(
(string) $this->findTemplate($request, $request->getRequestFormat(), $code, $showException),
array(
'status_code' => $code,
'status_text' => isset(Response::$statusTexts[$code]) ? Response::$statusTexts[$code] : '',
'exception' => $exception,
'logger' => $logger,
'currentContent' => $currentContent,
)
));
}
}
3) Setting the following in config.yml under twig:
twig:
exception_controller: 'exception_controller:showAction'
Even though my original goal was to prevent such an exception to be thrown at all with that code.
Another solution can be overwriting the AccessListener service of the Symfony Security component.
The generic procedure about how to override a service of a bundle is documented here. The following is the concrete example about this particular situation.
First of all let's create the class which overrides the AccessListener class:
<?php
namespace Path\To\My\Bundle\Services;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Security\Http\Firewall\AccessListener;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\Security\Core\Exception\AccessDeniedException;
class OverrideAccessListener extends AccessListener
{
public function handle(GetResponseEvent $event)
{
try {
parent::handle($event);
} catch (AccessDeniedException $e) {
$request = $event->getRequest();
$message = sprintf('No route found for "%s %s"', $request->getMethod(), $request->getPathInfo());
if ($referer = $request->headers->get('referer')) {
$message .= sprintf(' (from "%s")', $referer);
}
throw new NotFoundHttpException($message);
}
}
}
then we need to create a Compiler Pass in order to change the class attribute of the original service with the new class:
<?php
namespace Path\To\My\Bundle\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class OverrideServiceCompilerPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
$definition = $container->getDefinition('security.access_listener');
$definition->setClass('Path\To\My\Bundle\Services\OverrideAccessListener');
}
}
finally we need to register the Compiler Pass in the build method of the bundle:
<?php
namespace Path\To\My\Bundle;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Path\To\My\Bundle\DependencyInjection\Compiler\OverrideServiceCompilerPass;
class MyBundleName extends Bundle
{
public function build(ContainerBuilder $container)
{
parent::build($container);
$container->addCompilerPass(new OverrideServiceCompilerPass());
}
}
Finally I found a simpler solution: using an access denied handler.
Unfortunately there is no much documentation about how to create an access denied handler, but it is very simple.
First create a class that implements the AccessDeniedHandlerInterface and set it as a service (for example naming it my_access_denied_handler_service).
In the handle method a Response should be created and returned (in my case I wanted a 404 response).
Then in the security.yml configuration file we have to set the access_denied_handler under the firewall:
...
firewalls:
my_firewall:
...
access_denied_handler: my_access_denied_handler_service
...
...
I have a question regarding Authentication in Laravel 5.x. I’ve been specifically looking at tymondesigns/jwt-auth and irazasyed/jwt-auth-guard packages to do the JSON web token authentication token handling in my Laravel application.
I am not using a local database whatsoever, nor do I want to. I have environment variables set up in .env for my API’s URL, USERNAME & PASSWORD. The Guzzle PHP HTTP client is doing the trick just fine, connecting and returning data between the API and my application as needed.
However, I need to set up Authentication within my Laravel instance. I run into problems here, and the auth is wanting a DB connection.
$token = JWTAuth::attempt($credentials)
Here's the exception:
PDOException in Connector.php line 55:
SQLSTATE[HY000] [14] unable to open database file
How can I make use of JWT without using a database?
How can I COMPLETELY shut-off database connections within Laravel?
Thanks.
UPDATE:
Using tymon/jwt-auth, I've set things up within the routes, Kernel, Middleware, etc.
I created a "claim" successfully, but I need to create the token by encoding the "payload."
$this->username = $request->username;
$sub = $this->username;
$iat = time();
$jti = md5($sub . $iat);
$aud = env('APP_URL');
$this->claims = [
'sub' => $sub,
'iat' => $iat,
'exp' => time() + (2 * 7 * 24 * 60 * 60),
'nbf' => $iat,
'iss' => 'khill',
'jti' => $jti,
'aud' => $aud,
];
$payload = JWTFactory::make($this->claims);
How do I get the custom token?
You should define a custom Authentication Provider and set it in config/jwt.php.
Example of provider
Put this class anywhere you like.
namespace MyNamespace;
use Tymon\JWTAuth\Providers\Auth\AuthInterface;
class MyCustomAuthenticationProvider implements AuthInterface
{
public function byCredentials(array $credentials = [])
{
return $credentials['username'] == env('USERNAME') && $credentials['password'] == env('PASSWORD');
}
public function byId($id)
{
// maybe throw an expection?
}
public function user()
{
// you will have to implement this maybe.
}
}
Example of configuration
In the providers array in config/jwt.php, change this:
'auth' => 'Tymon\JWTAuth\Providers\Auth\IlluminateAuthAdapter',
to this:
'auth' => 'MyNamespace\MyCustomAuthenticationProvider',
Other considerations
Using the env() function anywhere is not good practice. It's better to use it in your config files, and then use the config() function anywhere else.
You may need to reimplement also the User Provider.
JWTAuth::attempt() won't help you with this, because it hits the database for you behind the scenes. You need some other way to check the environment credentials.
Add a custom method to a class somewhere which will do that for you or pass the credentials against the API you are hitting with Guzzle.
Code example:
public function authenticate($username, $password)
{
if(!$username === env('USERNAME') or !$password === env('PASSWORD')) {
// return a message that the user could not be authenticated or false.
}
// Generate the JWT token here and store it somewhere.
}
As a quick fix I decided to implement the following custom code...
1) Created custom middleware to handle the logic.
class CustomMiddleware
{
protected $loginPath = 'login';
public function handle($request, Closure $next) {
$logged_in = $request->session()->get('logged_in');
if (!$logged_in) {
return redirect()->guest('login')->with('flag','1');
}
return $next($request);
}
}
2) Added a reference to the middleware class.
class Kernel extends HttpKernel
{
protected $routeMiddleware = [
'custom' => \App\Http\Middleware\CustomMiddleware::class,
];
}
3) Added it to routes.php.
Route::group(['middleware' => ['custom']], function () {
// Add routes here
}
yes.
you can create jwt token without database using tymondesigns/jwt-auth package...
for that you have to use jwt::encode method...
let me explain ...
first you have to put your credential in .env file...
then i am recomending you to use custom claims ...
after that you can create jwt token using below code ...
$customClaims = ['foo' => 'bar', 'baz' => 'bob'];
$factory = JWTFactory::customClaims($customClaims);
$token = JWTAuth::encode($payload);
for further details you can refer below link
wiki