I am trying to create the dynamic routes based on the data pulled from third party service which is also another Drupal website. The routes are created when the custom module holding this dynamic routes is enabled but these dynamic routes are not re-created when cache is cleared. The source Drupal website exposes Drupal content and this target site needs to create routes with the path alias from the source Drupal site.
Any guide/tips for better implementation would be highly appreciated.
This is how i created dynamic routes:
<?php
namespace Drupal\demo_module\Routing;
use Symfony\Component\Routing\Route;
/**
* Defines dynamic routes.
*/
class DynamicRoutes {
/**
* {#inheritdoc}
*/
public function routes() {
$routes = [];
// This is the service for pulling data. It pulls data from another drupal sites.
$demoService = \Drupal::service('demo_module.service');
// Just checks if service is available.
if ($demoService->isAvailable()) {
try {
$demoData = $demoService->getDataFromServices();
if (isset($demoData['data']) && count($demoData['data']) > 0) {
foreach ($demoData['data'] as $data) {
$alias = $data['attributes']['path']['alias'];
if (isset($alias) && !empty($alias)) {
$route_provider = \Drupal::service('router.route_provider');
$routeName = 'demo_module.' . ltrim($alias, '/');
$exists = count($route_provider->getRoutesByNames([$routeName])) === 1;
if (!$exists) {
// Want to add .pdf at the end of route name. So the route name would be the path alias from the source drupal site plus .pdf appended.
$routes[$routeName] = new Route(
'/' . $alias . '.pdf',
[
'_controller' => '\Drupal\demo_module\Controller\DemoModuleController::getData',
'_title' => 'Demo Data',
],
[
'_permission' => 'access content',
]
);
}
}
}
}
}
catch (\Exception $e) {
throw new \Exception($e->getMessage());
}
}
return $routes;;
}
}
This is how routes were added to demo_module.routing.yml
route_callbacks:
- '\Drupal\demo_module\Routing\DynamicRoutes::routes'
It seems routes are cached (and not cleared by clearing the caches??).
Judging by the docs here, you can set the routes not to cache.
eg.
$routes[$routeName] = new Route(
'/' . $alias . '.pdf',
[
'_controller' => '\Drupal\demo_module\Controller\DemoModuleController::getData',
'_title' => 'Demo Data',
],
[
'_permission' => 'access content',
],
[
'no_cache' => TRUE
]
);
I did not test the code above, but it's the first thing I would try, Route class docs here
Related
We have a web application in PHP, for our clients we have prepared connect to google analytics UA. I use "google/apiclient": "^2.0", it works that our clients click on button in our administration and then is runned a followed code:
$this->client = new Google_Client();
$this->client->setApplicationName("xxxx");
$this->client->setClientId("xxxx");
$this->client->setClientSecret("xxxx");
$this->client->setScopes(array("https://www.googleapis.com/auth/analytics.readonly"));
$this->client->setRedirectUri("xxxx");
$this->client->setAccessType('offline');
$this->client->setApprovalPrompt("force");
The credentials i get from https://console.cloud.google.com/ -> OAuth 2.0 Client IDs
then the client is redirected to google where he log in, and allow acces to his GA data for our app. then is redirected back with code is generated access token. With this token i can get his GA UA data and show it to graphs in our administration. It works allright, but now i get information that GA UA will be end, and i need to create the same proces for UA V4. But in documentation to GA V4 what i found: https://developers.google.com/analytics/devguides/reporting/data/v1
Is not information how to process it for our clients. There is only authorisation over service account, that i must donwload my own credentials.json to service account but it allow me only acces to my private account, but i need it to work the same as before, so for other clients without having to upload credentials.json. That is, to be redirected to google via OAuth 2.0 Client IDs and allow access to our application to read their data. Is it even possible?
Thank you for help, and sorry for my bad english
This should give you a start. I am combining the OAuth2 methods from the Google API php client library and applying them to the new library.
Its not optimal but it works. Code below is for installed application not web. Its not going to work hosted.
<?php
require 'vendor/autoload.php';
use Google\Client;
use Google\Analytics\Data\V1beta\BetaAnalyticsDataClient;
putenv('GOOGLE_APPLICATION_CREDENTIALS=C:\YouTube\dev\credentials.json'); // Installed / native / desktop Client credetinals.
$credentials = getenv('GOOGLE_APPLICATION_CREDENTIALS');
$myfile = file_get_contents($credentials, "r") ;
$clientObj = json_decode($myfile);
$client = getClient();
$tokenResponse = $client->getAccessToken();
print_r($tokenResponse);
print_r($tokenResponse["access_token"]);
$service = new BetaAnalyticsDataClient( [
'credentials' => Google\ApiCore\CredentialsWrapper::build( [
'scopes' => [
'https://www.googleapis.com/auth/analytics',
'openid',
'https://www.googleapis.com/auth/analytics.readonly',
],
'keyFile' => [
'type' => 'authorized_user',
'client_id' => $clientObj->installed->client_id,
'client_secret' => $clientObj->installed->client_secret,
'refresh_token' => $tokenResponse["refresh_token"]
],
] ),
] );
$response = $service->runReport([
'property' => 'properties/[YOUR_PROPERTY_ID]'
]);
foreach ($response->getRows() as $row) {
foreach ($row->getDimensionValues() as $dimensionValue) {
print 'Dimension Value: ' . $dimensionValue->getValue() . PHP_EOL;
}
}
function getClient()
{
$client = new Client();
$client->setApplicationName('Google analytics data beta Oauth2');
$client->setScopes('https://www.googleapis.com/auth/analytics');
$client->setAuthConfig(getenv('GOOGLE_APPLICATION_CREDENTIALS'));
$client->setAccessType('offline');
// Load previously authorized token from a file, if it exists.
// The file token.json stores the user's access and refresh tokens, and is
// created automatically when the authorization flow completes for the first
// time.
$tokenPath = 'token.json';
if (file_exists($tokenPath)) {
$accessToken = json_decode(file_get_contents($tokenPath), true);
$client->setAccessToken($accessToken);
}
// If there is no previous token or it's expired.
if ($client->isAccessTokenExpired()) {
// Refresh the token if possible, else fetch a new one.
if ($client->getRefreshToken()) {
$client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
} else {
// Request authorization from the user.
$authUrl = $client->createAuthUrl();
printf("Open the following link in your browser:\n%s\n", $authUrl);
print 'Enter verification code: ';
$authCode = trim(fgets(STDIN));
// Exchange authorization code for an access token.
$accessToken = $client->fetchAccessTokenWithAuthCode($authCode);
$client->setAccessToken($accessToken);
// Check to see if there was an error.
if (array_key_exists('error', $accessToken)) {
throw new Exception(join(', ', $accessToken));
}
}
// Save the token to a file.
if (!file_exists(dirname($tokenPath))) {
mkdir(dirname($tokenPath), 0700, true);
}
file_put_contents($tokenPath, json_encode($client->getAccessToken()));
}
return $client;
}
And please how to get list streams of listed properties, I have a code to get GA4 properties:
$this->SetGa4ClientAdmin();
$accounts = $this->ga4_admin->listAccounts();
$this->data['accounts_v4'] = array();
foreach ($accounts as $account) {
$this->data['accounts_v4'][$account->getName()] = array('name' => $account->getDisplayName(), 'childrens' => array());
try {
$properties = $this->ga4_admin->ListProperties('parent:' . $account->getName());
foreach ($properties AS $property) {
$this->data['accounts_v4'][$account->getName()]['childrens'][$property->getName()] = $property->getDisplayName();
}
} catch (Exception $ex) {
die("error: " . $ex->getMessage());
}
}
At every property I need to get measurement ID of GA4 streams.
I need to get this
I'm pretty new on unit testing and I want to try to test my login page
my Goal for this unit are :
-> if it match in database -> redirect to route '/'
-> if not -> redirect to route '/login'
<?php
namespace Tests\Feature;
use App\Domain\Core\Models\User;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Session;
use Tests\TestCase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Illuminate\Foundation\Testing\DatabaseMigrations;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class userTest extends TestCase
{
use DatabaseMigrations;
/**
* A basic test example.
*
* #return void
*/
public function testLoginTrue()
{
$credential = [
'email' => 'user#ad.com',
'password' => 'user'
];
$this->post('login',$credential)->assertRedirect('/');
}
public function testLoginFalse()
{
$credential = [
'email' => 'user#ad.com',
'password' => 'usera'
];
$this->post('login',$credential)->assertRedirect('/login');
}
}
when I test on TestLoginTrue , its successfully return to '/' But when i try the TestLoginFalse ... it return same like TestLoginTrue, it should be stayed on '/login' route,
Any Idea?
Plus I want to try to check if when I already login I couldn't access the login page so my initial idea is :
public function testLoginTrue()
{
$credential = [
'email' => 'user#ad.com',
'password' => 'user'
];
$this->post('login',$credential)
->assertRedirect('/')
->get('/login')
->assertRedirect('/');
}
but... it returns
1) Tests\Feature\userTest::testLoginTrue BadMethodCallException:
Method [get] does not exist on Redirect.
So how to do it correctly?
Thanks in advance
I am also a bit stuck with Laravel 5.4 testing follow redirects case.
As a workaround, you may check $response->assertSessionHasErrors(). This way it should work:
public function testLoginFalse()
{
$credential = [
'email' => 'user#ad.com',
'password' => 'incorrectpass'
];
$response = $this->post('login',$credential);
$response->assertSessionHasErrors();
}
Also, in testLoginTrue() you may check, that session missing errors:
$response = $this->post('login',$credential);
$response->assertSessionMissing('errors');
Hope this helps!
I am trying to reproduce the behaviour of the facebook batch requests function on their graph api.
So I think that the easiest solution is to make several requests on a controller to my application like:
public function batchAction (Request $request)
{
$requests = $request->all();
$responses = [];
foreach ($requests as $req) {
$response = $this->get('some_http_client')
->request($req['method'],$req['relative_url'],$req['options']);
$responses[] = [
'method' => $req['method'],
'url' => $req['url'],
'code' => $response->getCode(),
'headers' => $response->getHeaders(),
'body' => $response->getContent()
]
}
return new JsonResponse($responses)
}
So with this solution, I think that my functional tests would be green.
However, I fill like initializing the service container X times might make the application much slower. Because for each request, every bundle is built, the service container is rebuilt each time etc...
Do you see any other solution for my problem?
In other words, do I need to make complete new HTTP requests to my server to get responses from other controllers in my application?
Thank you in advance for your advices!
Internally Symfony handle a Request with the http_kernel component. So you can simulate a Request for every batch action you want to execute and then pass it to the http_kernel component and then elaborate the result.
Consider this Example controller:
/**
* #Route("/batchAction", name="batchAction")
*/
public function batchAction()
{
// Simulate a batch request of existing route
$requests = [
[
'method' => 'GET',
'relative_url' => '/b',
'options' => 'a=b&cd',
],
[
'method' => 'GET',
'relative_url' => '/c',
'options' => 'a=b&cd',
],
];
$kernel = $this->get('http_kernel');
$responses = [];
foreach($requests as $aRequest){
// Construct a query params. Is only an example i don't know your input
$options=[];
parse_str($aRequest['options'], $options);
// Construct a new request object for each batch request
$req = Request::create(
$aRequest['relative_url'],
$aRequest['method'],
$options
);
// process the request
// TODO handle exception
$response = $kernel->handle($req);
$responses[] = [
'method' => $aRequest['method'],
'url' => $aRequest['relative_url'],
'code' => $response->getStatusCode(),
'headers' => $response->headers,
'body' => $response->getContent()
];
}
return new JsonResponse($responses);
}
With the following controller method:
/**
* #Route("/a", name="route_a_")
*/
public function aAction(Request $request)
{
return new Response('A');
}
/**
* #Route("/b", name="route_b_")
*/
public function bAction(Request $request)
{
return new Response('B');
}
/**
* #Route("/c", name="route_c_")
*/
public function cAction(Request $request)
{
return new Response('C');
}
The output of the request will be:
[
{"method":"GET","url":"\/b","code":200,"headers":{},"body":"B"},
{"method":"GET","url":"\/c","code":200,"headers":{},"body":"C"}
]
PS: I hope that I have correctly understand what you need.
There are ways to optimise test-speed, both with PHPunit configuration (for example, xdebug config, or running the tests with the phpdbg SAPI instead of including the Xdebug module into the usual PHP instance).
Because the code will always be running the AppKernel class, you can also put some optimisations in there for specific environments - including initiali[zs]ing the container less often during a test.
I'm using one such example by Kris Wallsmith. Here is his sample code.
class AppKernel extends Kernel
{
// ... registerBundles() etc
// In dev & test, you can also set the cache/log directories
// with getCacheDir() & getLogDir() to a ramdrive (/tmpfs).
// particularly useful when running in VirtualBox
protected function initializeContainer()
{
static $first = true;
if ('test' !== $this->getEnvironment()) {
parent::initializeContainer();
return;
}
$debug = $this->debug;
if (!$first) {
// disable debug mode on all but the first initialization
$this->debug = false;
}
// will not work with --process-isolation
$first = false;
try {
parent::initializeContainer();
} catch (\Exception $e) {
$this->debug = $debug;
throw $e;
}
$this->debug = $debug;
}
I'm trying to solve this little problem.
I have a form that allows to modify datas to a db, but when I click on the submit button(even if it changes the values), it shows an error:
Some mandatory parameters are missing ("name") to generate a URL for route "update_page".
500 Internal Server Error - MissingMandatoryParametersException
Can anybody help me?
This is my controller:
class updateElementController extends Controller
{
public function updateAction($name)
{
$request = $this->get('request');
$ingrediente = $this->getDoctrine()
->getRepository('MyCrudBundle:Elements')
->findOneByName($name);
$em = $this->getDoctrine()->getManager();
$elementsmod = new Elements();
$formupdate = $this->createFormBuilder($elementsmod)
->add('name', 'text')
->add('quantity', 'text')
->add('Change element', 'submit')
->getForm();
$formupdate->handleRequest($request);
if ($formupdate->isValid()) {
$newval = $formupdate->getData();
// change "entity" / object values we want to edit
$namevalue= $newval->getName();
$quantityvalue= $newval->getQuantity();
$element->setName($namevalue);
$element->setQuantity($quantityvalue);
// call to flush that entity manager from which we create $item
$em->flush();
return new RedirectResponse($this->generateUrl('update_page'));
}
return $this->render('MyCrudBundle:Default:index.html.twig', array(
'form' => $formupdate->createView(),
));
}
}
And my route:
update_page:
pattern: /updatePage/{name}
defaults: { _controller: MyCrudBundle:updateElement:update }
(if you need other parts of code, just tell me, I'm a beginner and most certainly it's horribly written)
I suggest two options:
If the name is not mandatory in your routing you can change your routing as below
update_page:
pattern: /updatePage/{name}
defaults: { _controller: MyCrudBundle:updateElement:update, name: null }
If the name is mandatory for the generated url you need to pass the name as the pattern param
if ($formupdate->isValid()) {
$newval = $formupdate->getData();
$namevalue= $newval->getName();
$quantityvalue= $newval->getQuantity();
$element->setName($namevalue);
$element->setQuantity($quantityvalue);
$em->flush();
return new RedirectResponse($this->generateUrl('update_page', array('name' => $name)));
}
base on your query selection you have in your controller the $name is mandatory and I suggest to try the second one
I am coming across some problems when trying to use ZF2's authentication services. I have to following Module.php getServiceConfig function:
<?php
public function getServiceConfig()
{
return array(
'factories' => array(
'Auth\Model\CustomerTable' => function($sm) {
$tableGateway = $sm->get('CustomerTableGateway');
$table = new CustomerTable($tableGateway);
return $table;
},
'CustomerTableGateway' => function($sm) {
$dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
$resultSetPrototype = new ResultSet();
$resultSetPrototype->setArrayObjectPrototype(new Customer()); // prototype pattern implemented.
return new TableGateway('customer', $dbAdapter, null, $resultSetPrototype);
},
'Auth\Model\AuthStorage' => function($sm){
return new \Auth\Model\AuthStorage('jamietech');
},
'AuthService' => function($sm) {
$dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
$dbTableAuthAdapter = new DbTableAuthAdapter($dbAdapter,
'customer','username','password');
$authService = new AuthenticationService();
$authService->setAdapter($dbTableAuthAdapter);
$authService->setStorage($sm->get('Auth\Model\AuthStorage'));
return $authService;
},
),
);
}
The AuthStorage factory simply creates a new AuthStorage for us to keep track of the rememberMe function I have, and the AuthService factory creates a new Authentication Service for us. I can't see anything that I have done wrong but when running the following code in the AuthController.php:
<?php
public function loginAction()
{
//if already login, redirect to success page
if ($this->getAuthService()->hasIdentity()){
return $this->redirect()->toRoute('success');
}
$form = new LoginForm();
return array(
'form' => $form,
'messages' => $this->flashmessenger()->getMessages()
);
}
public function logoutAction()
{
$this->getSessionStorage()->forgetMe();
$this->getAuthService()->clearIdentity();
$this->flashmessenger()->addMessage("You have logged out successfully.");
return $this->redirect()->toRoute('auth', array('action'=>'login'));
}
PHPUnit encounters the following errors when running the PHPUnit command:
1: "testLoginActionCanBeAccessed - Zend\ServiceManager\ServiceManager::get was unable to fetch or create an instance of Zend\Db\Adapter\Adapter
1: "testLogoutActionCanBeAccessed - session_regenerate_id(): cannot regenerate session id - headers already sent.
And this error for both login and logout when the -process-isolation command is run:
"Serialization of closure is not allowed in: C;\Users\-----\AppData\Local\Temp\-----
If somebody could help that would be great. I am a ZF noob so try not to be too harsh.
EDIT: BTW THe global.php file includes the service_manager adapter factory illustrated in the ZF2 tutorial application.
Thank you!
Jamie Mclaughlan
did you check these:
autoload_classmap.php (for your module)
in your module.config.php
like this
service_manager' => array(
'aliases' => array(
'mymodule-ZendDbAdapter' => 'Zend\Db\Adapter\Adapter',
),
);
I hope it helps you to find the answer