So I decided to go from Laravel 4 to 5 which took me around 1-2 days because I barely knew how to do the transition. While doing the Upgrade for my app i came across a small problem with Json Pagination.
This code is what allows a PageQuery to be Paginated Via KnockoutJS
/**
* Builds paginate query with given parameters.
*
* #param array $params
* #param integer $page
* #param integer $perPage
*
* #return array
*/
public function buildPaginateQuery(array $params, $page = 1, $perPage = 15)
{
$query = $this->model;
$query = $this->appendParams($params, $query);
$count = (new Cache)->remember('count', '2000', function() use ($query){
return $query->count();
});
$totalPages = $count / $perPage;
$query = $query->skip($perPage * ($page - 1))->take($perPage);
$query = $query->order(isset($params['order']) && $params['order'] ? $params['order'] : null);
//$query = $query->cacheTags(array($this->model->table, 'pagination'))->remember(2000);
$query = (new Cache)->remember(array($this->model->table, 'pagination'), '2000', function() use ($query){
return $query;
});
return array('query' => $query, 'totalPages' => $totalPages, 'totalItems' => $count);
}
which eventually lead to this error in this screenshot
The Error directs to the code above and this code specifically
/**
* Get the full path for the given cache key.
*
* #param string $key
* #return string
*/
protected function path($key)
{
$parts = array_slice(str_split($hash = md5($key), 2), 0, 2);
$path = $this->directory() . '/'.join('/', $parts).'/'.$hash;
//unset the tags so we use the base cache folder if no
//tags are passed with subsequent call to the same instance
//of this class
//$this->tags = array();
return $path;
}
Im using a custom Cache Driver called TaggedFile. This worked fine in L4 but came across errors because There were some files removed within the Cache Alias. Like the StoreInterface. Can I receive some help for this? If you need me to post anything I will.
More Stuff:
Before I used this to Register the taggedFile Driver in global.php:
Cache::extend('taggedFile', function($app)
{
return new Illuminate\Cache\Repository(new Lib\Extensions\TaggedFileCache);
});
I do not know where exactly to put this. Does anyone know the equivalent of that? I tried putting it in AppServiceProvider but an error came up saying:
Call to undefined method Illuminate\Support\Facades\Cache::extend()
This used to work in L4 so i decided to go into the vendor folder manually find what the problem was....
This only had: getFacadeAccessor (Which L4 also only had but extend worked)
So i decided to use getFacadeAccessor and it worked, but i don't know if that was the solution or not.
As you noticed you are passing an array as a $key value, the safest way would be to replace the code
$parts = array_slice(str_split($hash = md5($key), 2), 0, 2);
With
$parts = array_slice(str_split($hash = md5(json_encode($key)), 2), 0, 2);
NB: I am not sure what version of php you are running, but json_encode( ... ) is normally faster then serialize( ... )
Related
How can I implement Laravel's cursor paginator without returning the id column in the collection?
The following query:
'users' => User::cursorPaginate(15)
returns the individual users like this:
{
"id": 1,
"uuid": "376bec76-9095-4510-a5ba-fea0f234c6cf",
"username": "alexanderhorner",
"password": "$2y$12$qMITOdMr2XdAq3EMKwc/WeB/db9IaQdkZ5egqY7CX5WpUwwHLKOLK"
}
Now, lets say I have an API. I want api/v1/user to return the same paginated results using cursorPaginate(), but I don't want to return the id column.
'users' => User::select('uuid', 'username')->cursorPaginate(15)
would return an error though, since cursorPaginate() needs the ID column or something similar:
InvalidArgumentException
Illegal operator and value combination.
What's the best way to fix this? Filter the collection returned by cursorPaginate()?
You can also check the cursorPaginate implementation (and signature) inside Builder.php.
There is a parameter columns. Check it out:
/**
* Get a paginator only supporting simple next and previous links.
*
* This is more efficient on larger data-sets, etc.
*
* #param int|null $perPage
* #param array $columns
* #param string $cursorName
* #param \Illuminate\Pagination\Cursor|string|null $cursor
* #return \Illuminate\Contracts\Pagination\CursorPaginator
*/
public function cursorPaginate($perPage = 15, $columns = ['*'], $cursorName = 'cursor', $cursor = null)
{
return $this->paginateUsingCursor($perPage, $columns, $cursorName, $cursor);
}
You can map the Users Collection of Models before return as response.
By doing the following you map the properties you want in a new variable called $users_to_be_returned and you return this in a property in your reponse.
You can do whatever with your structure of response with arrays, stdClass or even better DTO's.
$users = User::cursorPaginate(10);
$users_dtos = collect($users->items())->map(function ($user) {
$user_dto = new UserDTO(); // or even an stdClass object
$user_dto->username = $user->username;
$user_dto->password = $user->password;
return $user_dto;
})->toArray();
$response = new UserResponseDTO();
$response->users = $users_dtos;
$response->pagination_info = new PaginationInfoDTO();
$response->pagination_info->per_page = $users->perPage();
$response->pagination_info->path = $users->path();
$response->pagination_info->next_cursor = $users->nextCursor()->parameter('users.id');
$response->pagination_info->next_page_url = $users->nextPageUrl() ? $users->nextPageUrl() : null;
$response->pagination_info->previous_cursor = $users->previousCursor() ? $users->previousCursor()->parameter('users.id') : null;
$response->pagination_info->prev_page_url = $users->previousPageUrl() ? $users->previousPageUrl() : null;
return response()->json($response);
I am writing an api to get an object array by returning a paginated object. The problem is that accessing first page of the paginate object works fine but second and next pages just only return the first page contents. I just called the URLs like this:
/api/get?page=1
/api/get?page=2
Data going in:
{
"page_id":116,
"descending":true,
"rowsPerPage":10,
"page":1,
"search":null,
"sortBy":"name",
"tag_id":null,
"start_date":null,
"end_date":null,
"from_mobile":true
}
Below is my code:
function get_list(Request $req)
{
$orderby = "id";
$order = "DESC";
$flowdb = Flow::leftjoin("flow_tags", "flow.id", "=", "flow_tags.flow_id");
$flowdb->leftjoin("tags", "tags.id", "=", "flow_tags.tag_id");
$flowdb->leftjoin("flow_stocks", "flow.id", "=", "flow_stocks.flow_id");
$flowdb->select(DB::Raw("flow.id,flow.name,flow.used_count,flow.payload,GROUP_CONCAT(CONCAT('\"','[',tags.id,',', tags.name,']','\"')) as tags, count(flow_stocks.stock_id) as stock_count"));
$flowdb->where("flow.active", ">", 0);
$flowdb->where("flow.page_id", $req->page_id);
$flowdb->groupBy("flow.id");
if($req->from_mobile)
{
$flowdb->where('flow.from_mobile',true);
}
if ($req->sortBy) {
$orderby = $req->sortBy;
}
$order = $req->descending ? 'DESC' : 'ASC';
$flowdb->orderBy($orderby, $order);
$flowdb->orderBy("flow_tags.id", "ASC");
if ($req->rowsPerPage && $req->rowsPerPage !== -1) {
$flows = $flowdb->paginate($req->rowsPerPage);
} else {
$flows = $flowdb->paginate(999999999);
}
return $flows;
}
I also tried laravel manual paginator but the error didn't solve.
public function paginator(Request $request, $object, $perPage)
{
$currentPage = LengthAwarePaginator::resolveCurrentPage()??1;
$currentItems = array_slice($object, $perPage * ($currentPage - 1), $perPage);
$paginatedItems = new LengthAwarePaginator($currentItems, count($object), $perPage, $currentPage);
$paginatedItems->setPath($request->url());
return $paginatedItems;
}
Take a look at this method. The parameter $perPage is how many results you wish to show per page.
/**
* Paginate the given query.
*
* #param int|null $perPage
* #param array $columns
* #param string $pageName
* #param int|null $page
* #return \Illuminate\Contracts\Pagination\LengthAwarePaginator
*
* #throws \InvalidArgumentException
*/
public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null)
{
$page = $page ?: Paginator::resolveCurrentPage($pageName);
$perPage = $perPage ?: $this->model->getPerPage();
$results = ($total = $this->toBase()->getCountForPagination())
? $this->forPage($page, $perPage)->get($columns)
: $this->model->newCollection();
return $this->paginator($results, $total, $perPage, $page, [
'path' => Paginator::resolveCurrentPath(),
'pageName' => $pageName,
]);
}
This means something is wrong with setting up the requirements for this condition:
$req->rowsPerPage && $req->rowsPerPage !== -1
Double check your queries by using dd($query)
I think it would be easier to retrieve the whole dataset as is, without setting condition flow.page_id, $req->page_id I think the culprit is somewhere there, but it's difficult to verify without having the data for it.
Finally have you tried calling to ->get() before $req->rowsPerPage && $req->rowsPerPage !== -1:
$flowdb->get();
not sure if it would help. But I've seen instances where it was required. Can't think of any examples though.
I managed to find out the solution. The problem is I accidentally put page = 1 in the request parameter that laravel paginate used this page value (in this case 1) to render results for all pages. I read some online articles about this and found out that laravel starts rendering from page 1 by default if the user didn't give specific page value in the request. So, I didn't really need that page parameter in this case.
In case any one having the same issue using the http client, suppose the url is as bellow:
$url = 'http://127.0.0.1:8002/api/trips?page=3';
just dd the response to see which path was actually requested to the server
dd(Http::get($url)->effectiveUri())
It look something like this:
GuzzleHttp\Psr7\Uri {#2021 ▼
-scheme: "http"
-userInfo: ""
-host: "127.0.0.1"
-port: 8002
-path: "/api/trips"
-query: "page=3"
-fragment: ""
}
there you can confirm in the query field that the page number is what you intended.
Using adldap-laravel and Laravel 5.8.
I'm getting permissions based on LDAP groups. I can check if a user is part of a group using: $user->ldap->inGroup('Accounts'); (that returns a bool)
However that method also accepts an array, but seems to be an "AND" search, rather than "ANY".
So I've written this:
/**
* LDAP helper, to see if the user is in an AD group.
* Iterate through and break when a match is found.
*
* #param mixed $input
* #return bool
*/
public function isInGroup($input)
{
if (is_string($input)) {
$input[] = $input;
}
foreach ($input as $group)
if ($this->ldap->inGroup($group)) {
return true;
}
return false;
}
Implemented like this:
$user->isInGroup(['Accounts', 'Sales', 'Marketing']);
However it takes a long time to check.
Does anyone know of an improved way to solve my problem?
Yes can do it via query builder of Adldap.
/**
* LDAP helper, to see if the user is in an AD group.
* Iterate through and break when a match is found.
*
* #param mixed $input
* #return bool
*/
public function isInGroup($input){
if (is_string($input)) {
$input[] = $input;
}
$counter = 0;
$groupQuery = $this->ldap->search()->groups();
foreach ($input as $group) {
if ($counter == 0) {
$groupQuery->where('samaccountname', '=', $group);
} else {
$groupQuery->orWhere('samaccountname', '=', $group);
}
$counter++;
}
return $groupQuery->get()->count();
}
This samaccountname may be different field name for your LDAP provider. I couldn't tested it if has any syntax or method name error anyway you will find this same methods from your Adldap Provider class. The algorithm/process is same.
After fundamental changes on my project system architecture, I find myself in a situation where I would need to create "fake" implementation in order to test some functionality that used to be public like the following:
/**
* Display the template linked to the page.
*
* #param $newSmarty Smarty object to use to display the template.
*
* #param $parameters associative Array containing the values to pass to the template.
* The key is the name of the variable in the template and the value is the value of the variable.
*
* #param $account child class in the AccountManager hierarchy
*
* #param $partialview String name of the partial view we are working on
*/
protected function displayPageTemplateSmarty(Smarty &$newSmarty, array $parameters = array(), AccountManager $account = NULL, string $partialview = "")
{
$this->smarty = $newSmarty;
if (is_file(
realpath(dirname(__FILE__)) . "/../../" .
Session::getInstance()->getCurrentDomain() . "/view/" . (
!empty($partialview) ?
"partial_view/" . $partialview :
str_replace(
array(".html", "/"),
array(".tpl", ""),
Session::getInstance()->getActivePage()
)
)
)) {
$this->smarty->assign(
'activeLanguage',
Session::getInstance()->getActiveLanguage()
);
$this->smarty->assign('domain', Session::getInstance()->getCurrentDomain());
$this->smarty->assign(
'languages',
Languagecontroller::$supportedLanguages
);
$this->smarty->assign(
'title',
Languagecontroller::getFieldTranslation('PAGE_TITLE', '')
);
$this->smarty->assign_by_ref('PageController', $this);
$htmlTagBuilder = HTMLTagBuilder::getInstance();
$languageController = LanguageController::getInstance();
$this->smarty->assign_by_ref('htmlTagBuilder', $htmlTagBuilder);
$this->smarty->assign_by_ref('languageController', $languageController);
if (!is_null($account)) {
$this->smarty->assign_by_ref('userAccount', $account);
}
if (!is_null($this->menuGenerator)) {
$this->smarty->assign_by_ref('menuGenerator', $this->menuGenerator);
}
foreach ($parameters as $key => $value) {
$this->smarty->assign($key, $value);
}
$this->smarty->display((!empty($partialview) ?
"partial_view/" . $partialview :
str_replace(
array(".html", "/"),
array(".tpl", ""),
Session::getInstance()->getActivePage()
)
));
}
}
In this case, the PageController class used to be called directly in controllers, but is now an abstract class extended by the controllers and my unit tests can no longer access the method.
I also have methods like this one in my new session wrapper class that can only be used in very specific context and for which I really need to create fake page implementation to test them.
/**
* Add or update an entry to the page session array.
*
* Note: can only be updated by the PageController.
*
* #param $key String Key in the session array.
* Will not be added if the key is not a string.
*
* #param $value The value to be added to the session array.
*
* #return Boolean
*/
public function updatePageSession(string $key, $value)
{
$trace = debug_backtrace();
$updated = false;
if (isset($trace[1]) and
isset($trace[1]['class']) and
$trace[1]['class'] === 'PageController'
) {
$this->pageSession[$key] = $value;
$updated = true;
}
return $updated;
}
Even though I read a few article, it is still quite unclear in my mind if those fake classes should be considered as "stub" or a "mock" (or even "fake", "dummy" and so on).
I really need to use the proper terminology since my boss is expecting me (in a close future) to delegate most of my workload with oversea developers.
How would you call those fake class implementation created solely for testing purpose in order to be self-explanatory?
Gerard Meszaros explains the terminology of dummies, stubs, spies, mocks, and fakes here.
You can find examples from the PHP world here.
I am wondering this question for a long time, how does PHP handle references are they a good idea to use and I can't explain better than using an example, lets look at the following class and then # the comment of the setResult method.
Lets imagine we are using a model view controller framework and we are building a basic AjaxController, we only got 1 action method (getUsers) so far. Read the comments, and I hope my question is clear, how does PHP handle these kind of situations and is it true what I wrote about the x times in the memory # the setResult docblock.
class AjaxController{
private $json = array(
'result' => array(),
'errors' => array(),
'debug' => array()
);
/**
* Adds an error, always displayed to users if any errors.
*
* #param type $description
*/
private function addError($description){
$this->json['errors'][] = $description;
}
/**
* Adds an debug message, these are displayed only with DEBUG_MODE.
*
* #param type $description
*/
private function addDebug($description){
$this->json['debug'][] = $description;
}
/**
* QUESTION: How does this go in memory? Cause if I use no references,
* the array would be 3 times in the memory, if the array is big (5000+)
* its pretty much a waste of resources.
*
* 1st time in memory # model result.
* 2th time in memory # setResult ($resultSet variable)
* 3th time in memory # $this->json
*
* #param array $resultSet
*/
private function setResult($resultSet){
$this->json['result'] = $resultSet;
}
/**
* Gets all the users
*/
public function _getUsers(){
$users = new Users();
$this->setResult($users->getUsers());
}
public function __construct(){
if(!DEBUG_MODE && count($this->json['debug']) > 0){
unset($this->json['debug']);
}
if(count($this->json['errors']) > 0){
unset($this->json['errors']);
}
echo json_encode($this->json);
}
}
Another simple example: What would be better to use technique A:
function example(){
$latestRequest = $_SESSION['abc']['test']['abc'];
if($latestRequest === null){
$_SESSION['abc']['test']['abc'] = 'test';
}
}
Or technique B:
function example(){
$latestRequest =& $_SESSION['abc']['test']['abc'];
if($latestRequest === null){
$latestRequest = 'test';
}
}
Thanks for reading and advise :)
In short: don't use references.
PHP copies on write. Consider:
$foo = "a large string";
$bar = $foo; // no copy
$zed = $foo; // no copy
$bar .= 'test'; // $foo is duplicated at this point.
// $zed and $foo still point to the same string
You should only use references when you need the functionality that they provide. i.e., You need to modify the original array or scalar via a reference to it.