yii2, how correctly cache data - php

Now i have code data like this:
my const
const CacheUserByUid = 'CacheUserByUid_';
const CacheUserByUsername = 'CacheUserByUsername_';
const CacheUserById = 'CacheUserByUsername_';
Get user data bu uid
/**
* Get user by uid , return user data for user profile
*
* #param $uid
* #return mixed
*/
public function getUserByUid($uid)
{
$result = Yii::$app->cache->getOrSet(self::CacheUserByUid . $uid, function () use ($uid) {
$result = self::find()
->select([
'id',
'username',
'email',
'city',
'country',
'name',
'avatar',
'about',
'uid',
])
->where(['uid' => trim($uid)])
->one();
if (!empty($result)) {
$result->id = (string)$result->id;
}
return $result;
});
return $result;
}
get user data by PK
/**
* #param $userId
* #return mixed
*/
public function getUserById($userId)
{
$user = Yii::$app->cache->getOrSet(self::CacheUserById . $userId, function () use ($userId) {
return self::findOne($userId);
});
return $user;
}
Get user by username
/**
* Get user by username. Return only for user front info
*
* #param $username
* #return array|\yii\db\ActiveRecord|null
*/
public function getUserByUsername($username)
{
$result = Yii::$app->cache->getOrSet(self::CacheUserByUsername . $username, function () use ($username) {
$result = self::find()->select([
'user.id',
'user.city',
'user.country',
'user.name',
'user.avatar',
'user.about',
'user.username'
])
->where(['username' => $username])
->one();
if (!empty($result)) {
$result->id = (string)$result->id;
}
});
return $result;
}
I cached this data. And where user was update i used:
/**
* #param $data
* #param $userId
* #return bool
* #throws \yii\db\Exception
*/
public function updateUser($data, $userId)
{
$user = $this->getUserById($userId);
if (!empty($user)) {
foreach ($data as $key => $name) {
if ($this->hasAttribute($key)) {
$user->$key = $name;
}
}
$user->updatedAt = time();
if ($user->save()) {
//чистимо кеш
FileCache::clearCacheByKey(self::CacheUserByUid . $user->uid);
FileCache::clearCacheByKey(self::CacheUserByUsername . $user->username);
FileCache::clearCacheByKey(self::CacheUserById . $user->id);
return true;
}
}
return false;
}
method clearCacheByKey
/**
* #param $key
*/
public static function clearCacheByKey($key)
{
if (Yii::$app->cache->exists($key)) {
Yii::$app->cache->delete($key);
}
}
Am I good at using a single-user cache that caches these requests in different keys? I don't see any other way out
Is it ok to cache user data in FileCache?
maybe it would be better to use something else for this?

In your case, such simple queries don't need to be cached explicitly. Yii already has a query cache and your requests definitely should be already stored in the cache. The key for data in cache would be a combination of your SQL's md5 with some connection metadata.
Just ensure that everything is configured correctly.
Also if you need to update cached data on some changes, make sure that you're making queries with the best for your case cache dependency. It can purge cached results by some auto condition or you can do it manually from your code(by using TagDependency)
What about FileCache it depends on traffic to your app and current infrastructure. Sometimes there is nothing criminal to store cache in files and you're always can switch to something like Redis/Memcache when your app grow big enough

Related

REST API - Different Behaviour on Different Servers

We have created a REST API PHP setup which is designed to compare the Tables of one database with another and if the user doesn't exist in one, create it in the Application. Also if there is a change to a user i.e. Email address, string etc then update it.
We've had a script created which using php works fantastically on our test server, it creates a user when required and updates them when required.
When we move this over to a different server which has the same database structure, same tables, same root username and password etc, it isn't recognising the existing users. It thinks it has xx,xxx users to create and then fails as 'user with that username already exists'.
Does any one have any ideas why this maybe the case? Below is the main php file which then has a config file, a db.php, a common.php and then a GpsGateapi.php
require_once('rest_api_includes/Config.php');
require_once(Config::API_INCLUDES_DIR . '/DB.php');
require_once(Config::API_INCLUDES_DIR . '/Gpsgate_API.php');
require_once(Config::API_INCLUDES_DIR . '/Common.php');
class API_Sync
{
/** #var int application id */
private $application_id;
/** #var int user type id */
private $user_type_id;
/** #var Gpsgate_API instance */
private $api;
/** #var DB instance */
private $db;
/** #var string<show|log> Show or log errors */
private $errors_report_type;
/** #var string<show|log|none> Show or log actions */
private $actions_report_type;
/**
* Sets variables, connects to database and REST API
*
* #throws \Exception
*/
public function __construct()
{
$this->application_id = Config::API_APPLIATION_ID;
$this->user_type_id = Config::API_USER_TYPE_ID;
$this->errors_report_type = Config::API_SHOW_OUTPUT ? 'show' : 'log';
$this->actions_report_type = Config::API_SHOW_OUTPUT ? 'show' : (Config::API_ACTIONS_LOGGING ? 'log' : 'none');
}
/**
* Main method that controlls all the process
*
* #throws \Exception
*/
public function run()
{
$this->prepare();
$gpsgate_users_result = $this->api->getUsers();
if ($gpsgate_users_result->status !== 200) {
throw new Exception(implode('; ', $gpsgate_users_result->body));
}
$gpsgate_users = $gpsgate_users_result->body;
$db_users = $this->db->get(DB::TBL_PERSONS);
$res = $this->compare($gpsgate_users, $db_users);
$this->updateUsers($res['update']);
$this->createUsers($res['create']);
}
/**
* Create gsgate users data from database array
*
* #param array $users
*/
private function createUsers($users)
{
if (!empty($users)) {
$this->logOrShowAction("Begin to create users data.");
foreach ($users as $user) {
$res = $this->api->createUser([
'email' => $user['Email'],
'name' => $user['FirstName'],
'surname' => $user['LastName'],
'driverID' => $user['RFID'],
'username' => $user['UserName'],
'password' => Common::generatePassword(),
'userTypeId' => $this->user_type_id,
'description' => $user['Location'],
]);
if ($res->status !== 200) {
throw new Exception($res->body);
}
}
$this->logOrShowAction("Done.");
}
}
/**
* Update gsgate users data from database array
*
* #param array $users
*/
private function updateUsers($users)
{
if (!empty($users)) {
$this->logOrShowAction("Begin to update users data.");
foreach ($users as $user) {
$res = $this->api->updateUser([
'email' => $user['Email'],
'name' => $user['FirstName'],
'surname' => $user['LastName'],
'driverID' => $user['RFID'],
'description' => $user['Location'],
], $user['gpsgate_id']);
if ($res->status !== 200) {
throw new Exception($res->body);
}
}
$this->logOrShowAction("Done.");
}
}
/**
* Compare arrays and return list of users data to update/create
*/
private function compare($gpsgate_users, $db_users)
{
$this->logOrShowAction("Begin to compare users data.");
$gpsgate_user_key = 'username';
$db_user_key = 'UserName';
$gpsgate_users = Common::setIndexesByKey($gpsgate_user_key, $gpsgate_users);
$res = [
'update' => [],
'create' => [],
];
foreach ($db_users as $user) {
$user_key = $user[$db_user_key];
if (!empty($gpsgate_users[$user_key])) {
if ($this->userInfoVary($gpsgate_users[$user_key], $user)) {
$user['gpsgate_id'] = $gpsgate_users[$user_key]->id; // add gpsgate id
$res['update'][] = $user;
}
} else {
$res['create'][] = $user;
}
}
$this->logOrShowAction('Done');
$this->logOrShowAction('Need to create: ' . count($res['create']) . ' rows; to update: ' . count($res['update']) . ' rows.');
return $res;
}
/**
* Check is the information between db user and api user is different
*/
private function userInfoVary($gpsgate_user, $db_user)
{
return $db_user['Email'] != $gpsgate_user->email
|| $db_user['FirstName'] != $gpsgate_user->name
|| $db_user['LastName'] != $gpsgate_user->surname
|| $db_user['RFID'] != ($gpsgate_user->driverID ?? '')
|| $db_user['Location'] != $gpsgate_user->description;
}
/**
* Connects to database, REST API, creates folders for errors and actions if need
*/
private function prepare()
{
if ($this->errors_report_type == 'log') {
if (!file_exists(dirname(Config::API_LOG_FILE_ERRORS))) {
mkdir(dirname(Config::API_LOG_FILE_ERRORS));
}
if (!file_exists(Config::API_LOG_FILE_ERRORS)) {
file_put_contents(Config::API_LOG_FILE_ERRORS, '');
}
}
if ($this->actions_report_type == 'log') {
if (!file_exists(dirname(Config::API_LOG_FILE_ACTIONS))) {
mkdir(dirname(Config::API_LOG_FILE_ACTIONS));
}
if (!file_exists(Config::API_LOG_FILE_ACTIONS)) {
file_put_contents(Config::API_LOG_FILE_ACTIONS, '');
}
}
$this->logOrShowAction('Trying to connect to database and GPSGATE REST API.');
$this->api = new Gpsgate_API(Config::API_APPLIATION_ID, Config::API_USER_TYPE_ID);
$this->db = DB::instance();
$this->logOrShowAction('Done.');
}
/**
* Logs error message in file or output in browser
*
* #param string $msg
*/
public function logOrShowError($msg)
{
$msg = "<span style='color: red; font-weight: 600;'>Error: " . $msg . "</span>";
$this->writeOrEchoMessage($msg, Config::API_LOG_FILE_ERRORS, $this->errors_report_type);
}
/**
* Logs action message in file or output in browser
*
* #param string $msg
*/
public function logOrShowAction($msg)
{
$this->writeOrEchoMessage($msg, Config::API_LOG_FILE_ACTIONS, $this->actions_report_type);
}
private function writeOrEchoMessage($msg, $file, $report_type)
{
if ($report_type == 'none') {
return ;
}
$msg = '[' . date('Y-m-d H:i:s') . '] ' . $msg;
if ($report_type == 'show') {
echo $msg . '<br>';
} else {
$h = fopen($file, 'a+');
fwrite($h, strip_tags($msg) . PHP_EOL);
fclose($h);
}
}
}
$sync = new API_Sync();
try {
$sync->run();
} catch (\Exception $e) {
$sync->logOrShowError($e->getMessage());
}

laravel formrequest before middleware

I know, this is a complex case but maybe one of you might have an idea on how to do this.
Concept
I have the following process in my API:
Process query string parameters (FormRequest)
Replace key aliases by preferred keys
Map string parameters to arrays if an array ist expected
Set defaults (including Auth::user() for id-based parameters)
etc.
Check if the user is allowed to do the request (Middleware)
Using processed (validated and sanitized) query params
→ otherwise I had to do exceptions for every possible alias and mapping as well as checking if the paramter is checked and that doesn't seem reasonable to me.
Problem
Nevertheless, if you just assign the middleware via ->middleware('middlewareName') to the route and the FormRequest via dependency injection to the controller method, first the middleware is called and after that the FormRequest. As described above, that's not what I need.
Solution approach
I first tried dependency injection at the middleware but it didn't work.
My solution was to assign the middleware in the controller constructor. Dependency injection works here, but suddenly Auth::user() returns null.
Then, I came across the FormRequest::createFrom($request) method in \Illuminate\Foundation\Providers\FormRequestServiceProvider.php:34 and the possibility to pass the $request object to the middleware's handle() method. The result looks like this:
public function __construct(Request $request)
{
$middleware = new MyMiddleware();
$request = MyRequest::createFrom($request);
$middleware->handle($request, function() {})
}
But now the request is not validated yet. Just calling $request->validated() returns nothing. So I digged a little deeper and found that $resolved->validateResolved(); is done in \Illuminate\Foundation\Providers\FormRequestServiceProvider.php:30 but that doesn't seem to trigger the validation since it throws an exception saying that this method cannot be called on null but $request isn't null:
Call to a member function validated() on null
Now, I'm completely stumped. Does anyone know how to solve this or am I just doing it wrong?
Thanks in advance!
I guess, I figured out a better way to do this.
My misconception
While middleware is doing authentication, I was doing authorization there and therefore I have to use a Gate
Resulting code
Controller
...
public function getData(MyRequest $request)
{
$filters = $request->query();
// execute queries
}
...
FormRequest
class MyRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
return Gate::allows('get-data', $this);
}
/**
* Get the validation rules that apply to the request.
*
* #return array
*/
public function rules()
{
return [
// ...
];
}
/**
* Prepare the data for validation.
*
* #return void
*/
protected function prepareForValidation()
{
$this->replace($this->cleanQueryParameters($this->query()));
}
private function cleanQueryParameters($queryParams): array
{
$queryParams = array_filter($queryParams, function($param) {
return is_array($param) ? count($param) : strlen($param);
});
$defaultStartDate = (new \DateTime())->modify('monday next week');
$defaultEndDate = (new \DateTime())->modify('friday next week');
$defaults = [
'article.created_by_id' => self::getDefaultEmployeeIds(),
'date_from' => $defaultStartDate->format('Y-m-d'),
'date_to' => $defaultEndDate->format('Y-m-d')
];
$aliases = [
// ...
];
$mapper = [
// ...
];
foreach($aliases as $alias => $key) {
if (array_key_exists($alias, $queryParams)) {
$queryParams[$key] = $queryParams[$alias];
unset($queryParams[$alias]);
}
}
foreach($mapper as $key => $fn) {
if (array_key_exists($key, $queryParams)) {
$fn($queryParams, $key);
}
}
$allowedFilters = array_merge(
Ticket::$allowedApiParameters,
array_map(function(string $param) {
return 'article.'.$param;
}, TicketArticle::$allowedApiParameters)
);
$arrayProps = [
// ..
];
foreach($queryParams as $param => $value) {
if (!in_array($param, $allowedFilters) && !in_array($param, ['date_from', 'date_to'])) {
abort(400, 'Filter "'.$param.'" not found');
}
if (in_array($param, $arrayProps)) {
$queryParams[$param] = guarantee('array', $value);
}
}
return array_merge($defaults, $queryParams);
}
}
Gate
class MyGate
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #return \Illuminate\Auth\Access\Response|Void
* #throws \Symfony\Component\HttpKernel\Exception\HttpException
*/
public function authorizeGetDataCall(User $user, MyRequest $request): Response
{
Log::info('[MyGate] Checking permissions …');
if (in_array(LDAPGroups::Admin, session('PermissionGroups', []))) {
// no further checks needed
Log::info('[MyGate] User is administrator. No further checks needed');
return Response::allow();
}
if (
($request->has('group') && !in_array(Group::toLDAPGroup($request->get('group')), session('PermissionGroups', []))) ||
$request->has('owner.department') && !in_array(Department::toLDAPGroup($request->query('owner.department')), session('PermissionGroups', [])) ||
$request->has('creator.department') && !in_array(Department::toLDAPGroup($request->query('creator.department')), session('PermissionGroups', []))
) {
Log::warning('[MyGate] Access denied due to insufficient group/deparment membership', [ 'group/department' =>
$request->has('group') ?
Group::toLDAPGroup($request->get('group')) :
($request->has('owner.department') ?
Department::toLDAPGroup($request->query('owner.department')) :
($request->has('creator.department') ?
Department::toLDAPGroup($request->query('creator.department')) :
null))
]);
return Response::deny('Access denied');
}
if ($request->has('customer_id') || $request->has('article.created_by_id')) {
$ids = [];
if ($request->has('customer_id')) {
$ids = array_merge($ids, $request->query('customer_id'));
}
if ($request->has('article.created_by_id')) {
$ids = array_merge($ids, $request->query('article.created_by_id'));
}
$users = User::find($ids);
$hasOtherLDAPGroup = !$users->every(function($user) {
return in_array(Department::toLDAPGroup($user->department), session('PermissionGroups', []));
});
if ($hasOtherLDAPGroup) {
Log::warning('[MyGate] Access denied due to insufficient permissions to see specific other user\'s data', [ 'ids' => $ids ]);
return Response::deny('Access denied');;
}
}
if ($request->has('owner.login') || $request->has('creator.login')) {
$logins = [];
if ($request->has('owner.login')) {
$logins = array_merge(
$logins,
guarantee('array', $request->query('owner.login'))
);
}
if ($request->has('creator.login')) {
$logins = array_merge(
$logins,
guarantee('array', $request->query('creator.login'))
);
}
$users = User::where([ 'samaccountname' => $logins ])->get();
$hasOtherLDAPGroup = !$users->every(function($user) {
return in_array(Department::toLDAPGroup($user->department), session('PermissionGroups', []));
});
if ($hasOtherLDAPGroup) {
Log::warning('[MyGate] Access denied due to insufficient permissions to see specific other user\'s data', [ 'logins' => $logins ]);
return Response::deny('Access denied');
}
}
Log::info('[MyGate] Permission checks passed');
return Response::allow();
}
}

string(331) "Legacy People API has not been used

I'm getting this error when i try to register via google api
string(331) "Legacy People API has not been used in project ******* before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/legacypeople.googleapis.com/overview?project=******** then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry."
And when i go that url i'm receiving
Failed to load.
There was an error while loading /apis/api/legacypeople.googleapis.com/overview?project=******&dcccrf=1. Please try again.
My google.php code in /vendor/league/oauth2-google/src/Provider is
<?php
namespace League\OAuth2\Client\Provider;
use League\OAuth2\Client\Exception\HostedDomainException;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use League\OAuth2\Client\Token\AccessToken;
use League\OAuth2\Client\Tool\BearerAuthorizationTrait;
use Psr\Http\Message\ResponseInterface;
class Google extends AbstractProvider
{
use BearerAuthorizationTrait;
const ACCESS_TOKEN_RESOURCE_OWNER_ID = 'id';
/**
* #var string If set, this will be sent to google as the "access_type" parameter.
* #link https://developers.google.com/accounts/docs/OAuth2WebServer#offline
*/
protected $accessType;
/**
* #var string If set, this will be sent to google as the "hd" parameter.
* #link https://developers.google.com/accounts/docs/OAuth2Login#hd-param
*/
protected $hostedDomain;
/**
* #var array Default fields to be requested from the user profile.
* #link https://developers.google.com/+/web/api/rest/latest/people
*/
protected $defaultUserFields = [
'id',
'name(familyName,givenName)',
'displayName',
'emails/value',
'image/url',
];
/**
* #var array Additional fields to be requested from the user profile.
* If set, these values will be included with the defaults.
*/
protected $userFields = [];
/**
* Use OpenID Connect endpoints for getting the user info/resource owner
* #var bool
*/
protected $useOidcMode = false;
public function getBaseAuthorizationUrl()
{
return 'https://accounts.google.com/o/oauth2/auth';
}
public function getBaseAccessTokenUrl(array $params)
{
return 'https://www.googleapis.com/oauth2/v4/token';
}
public function getResourceOwnerDetailsUrl(AccessToken $token)
{
if ($this->useOidcMode) {
// OIDC endpoints can be found https://accounts.google.com/.well-known/openid-configuration
return 'https://www.googleapis.com/oauth2/v3/userinfo';
}
// fields that are required based on other configuration options
$configurationUserFields = [];
if (isset($this->hostedDomain)) {
$configurationUserFields[] = 'domain';
}
$fields = array_merge($this->defaultUserFields, $this->userFields, $configurationUserFields);
return 'https://www.googleapis.com/plus/v1/people/me?' . http_build_query([
'fields' => implode(',', $fields),
'alt' => 'json',
]);
}
protected function getAuthorizationParameters(array $options)
{
$params = array_merge(
parent::getAuthorizationParameters($options),
array_filter([
'hd' => $this->hostedDomain,
'access_type' => $this->accessType,
// if the user is logged in with more than one account ask which one to use for the login!
'authuser' => '-1'
])
);
return $params;
}
protected function getDefaultScopes()
{
return [
'email',
'openid',
'profile',
];
}
protected function getScopeSeparator()
{
return ' ';
}
protected function checkResponse(ResponseInterface $response, $data)
{
if (!empty($data['error'])) {
$code = 0;
$error = $data['error'];
if (is_array($error)) {
$code = $error['code'];
$error = $error['message'];
}
throw new IdentityProviderException($error, $code, $data);
}
}
protected function createResourceOwner(array $response, AccessToken $token)
{
$user = new GoogleUser($response);
// Validate hosted domain incase the user edited the initial authorization code grant request
if ($this->hostedDomain === '*') {
if (empty($user->getHostedDomain())) {
throw HostedDomainException::notMatchingDomain($this->hostedDomain);
}
} elseif (!empty($this->hostedDomain) && $this->hostedDomain !== $user->getHostedDomain()) {
throw HostedDomainException::notMatchingDomain($this->hostedDomain);
}
return $user;
}
}
How to fix this issue?
Legacy People API has not been used in project ******* before or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/legacypeople.googleapis.com/overview?project=********
As the error message states you have not enabled the people api in your project and as you have included email and profile and are trying to request profiled data about the user.
return 'https://www.googleapis.com/plus/v1/people/me?' . http_build_query([
'fields' => implode(',', $fields),
'alt' => 'json',
You need to enable the people api in our project before you can request data. Click the link and follow the instructions below.
Go to Google developer console click library on the left. Then search for the API you are looking to use and click enable button
Wait a couple of minutes then run your code again. Then you will be able to make requests to the people api.
return 'https://www.googleapis.com/plus/v1/people/me?' . http_build_query([
'fields' => implode(',', $fields),
'alt' => 'json',
Legacy endpoint:
I also recommend up update your endpoint to the new people.get endpoint
https://people.googleapis.com/v1/people/me

Restricting actions with Policies

In my Laravel application, a User can have a Profile which they or a user with privileges can update.
The relation for these two models is defined in this method:
/**
* Get the profile associated with this user
*/
public function profile()
{
return $this->hasOne(Profile::class, 'user_username', 'username');
}
This is the method for updating a user profile:
/**
* Update the specified resource in storage.
*
* #param \Illuminate\Http\Request $request
* #param \App\Profile $profile
* #return \Illuminate\Http\Response
*/
public function update(UpdateProfile $request, User $user)
{
if ($user) {
// Only proceed if there is a logged in user
$profile = $user->profile;
// If there is no profile, create one for this user as they'll need one.
if (!empty(request()->get('background'))) {
$profile->background = clean($request->get('background'));
}
if (!empty(request()->get('skills'))) {
$profile->skills = clean($request->get('skills'));
}
if (!empty(request()->get('filepath'))) {
$profile->displayPicture = $request->get('filepath');
}
if (!empty(request()->get('linkedInUrl'))) {
$socialProfilesDecoded = json_decode($user->profile->socialProfiles, true);
$socialProfilesDecoded["LinkedIn"] = $request->get('linkedInUrl');
$profile->socialProfiles = json_encode($socialProfilesDecoded);
}
if (!empty(request()->get('twitterUrl'))) {
$socialProfilesDecoded = json_decode($user->profile->socialProfiles, true);
$socialProfilesDecoded["Twitter"] = $request->get('twitterUrl');
$profile->socialProfiles = json_encode($socialProfilesDecoded);
}
$user->profile()->save($profile);
return redirect()->back()->withSuccess('Your profile has been successfully updated');
}
}
The route for updating a profile is:
Route::post('profile/{user}', 'ProfileController#update');
It came to my attention that exposing the username presents a vulnerability as if you're able to grab the request with a web proxy you can just change the username and update another user's profile.
Without changing the URL could I put a Policy in place to check that:
The user has permission to update said profile
The profile being updated is the correct profile (and the request wasn't tampered with.
Or, should I change the URL and have a way to edit profiles in an admin area only?
Also, as a Profile is associated with a User, how could a privileged user access another user's profile?
Maybe a hidden input?
Update:
if ($request->is('admin/*')) {
//
}
Could I check if this matches the POST request?
Update 2
Added a simple check to ensure the logged in user had permissions to update a Profile.
/**
* Update the specified resource in storage.
*
* #param \Illuminate\Http\Request $request
* #param \App\Profile $profile
* #return \Illuminate\Http\Response
*/
public function update(UpdateProfile $request, User $user)
{
// Check this user
if(auth()->user() == $user || auth()->user()->can('Approve user profile')){
if ($user) {
// Only proceed if there is a logged in user
$profile = $user->profile;
// If there is no profile, create one for this user as they'll need one.
if (!empty(request()->get('background'))) {
$profile->background = clean($request->get('background'));
}
if (!empty(request()->get('skills'))) {
$profile->skills = clean($request->get('skills'));
}
if (!empty(request()->get('filepath'))) {
$profile->displayPicture = $request->get('filepath');
}
if (!empty(request()->get('linkedInUrl'))) {
$socialProfilesDecoded = json_decode($user->profile->socialProfiles, true);
$socialProfilesDecoded["LinkedIn"] = $request->get('linkedInUrl');
$profile->socialProfiles = json_encode($socialProfilesDecoded);
}
if (!empty(request()->get('twitterUrl'))) {
$socialProfilesDecoded = json_decode($user->profile->socialProfiles, true);
$socialProfilesDecoded["Twitter"] = $request->get('twitterUrl');
$profile->socialProfiles = json_encode($socialProfilesDecoded);
}
$user->profile()->save($profile);
return redirect()->back()->withSuccess('Your profile has been successfully updated');
}
}
}

Symfony REST API: query with multiple parameters

How can I implement method that would return list of services depending on parameters provided in URL?
So if there are no parameters, all services are returned. If user and category provided, then filter by both params. If only user or only category provided, filter by one of the params.
/**
* #Route("/", name="api_services_search")
* #Method("GET")
* #ApiDoc(
* section = "Service",
* description="Search services",
* parameters={
* {"name"="category", "dataType"="int", "required"=true, "description"="Category ID"}
* {"name"="user", "dataType"="int", "required"=true, "description"="User ID"}
* },
* output="CoreBundle\Entity\Service"
* )
*/
public function searchAction(Request $request){
$categoryId = $request->query->get('category');
$userId = $request->query->get('user');
$result = new JsonResponse();
if($categoryId){
$category = $this->getDoctrine()->getRepository('CoreBundle:ServiceCategory')->find($categoryId);
if($category == null){
throw new ApiException('category not found');
}
$serviceList = $this->getDoctrine()
->getRepository('CoreBundle:Service')->findBy(array('serviceCategory' => $category));
}
else if($userId){
$user = $this->getDoctrine()->getRepository('CoreBundle:BasicUser')->find($userId);
if($user == null){
throw new ApiException('user not found');
}
$serviceList = $this->getDoctrine()
->getRepository('CoreBundle:Service')->findBy(array('basicUser' => $user));
} else{
$serviceList = $this->getDoctrine()
->getRepository('CoreBundle:Service')->findAll();
}
$serviceListJson = $this->serializeDataObjectToJson($serviceList);
$result->setContent($serviceListJson);
return $result;
}
Example URL:
http://127.0.0.1:8000/api/v1/services/?category=3&user=4
I have error:
Too many parameters: the query defines 1 parameters and you bound 2 (500 Internal Server Error)
Also I am looking for maintaible solution where I can easily add more parameters to URL in the future
I would go with something like this. I'm afraid you can't make it more generic and agnostic from query parameters adds/edits.
/**
* Controller action
*/
public function searchAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
$serviceList = $em->getRepository('CoreBundle:Service')->fetchFromFilters([
'serviceCategory' => $request->query->get('category'),
'basicUser' => $request->query->get('user'),
]);
$serviceListJson = $this->serializeDataObjectToJson($serviceList);
$result = new JsonResponse();
$result->setContent($serviceListJson);
return $result;
}
/**
* Repository fetching method
*/
public function fetchFromFilter(array $filters)
{
$qb = $this->createQueryBuilder('s');
if (null !== $filters['serviceCategory']) {
$qb
->andWhere('s.serciceCategory = :serviceCategory')
->setParameter('serviceCategory', $filters['serviceCategory'])
;
}
if (null !== $filters['basicUser']) {
$qb
->andWhere('s.basicUser = :basicUser')
->setParameter('basicUser', $filters['basicUser'])
;
}
return $qb->getQuery()->getResult();
}

Categories