CakePHP Paginator: get first item from next page? - php

I have a table that contains different versions of a text. I want to display the diffs of each version with the previous version. I also want to paginate through the versions, in case there are more than 20. However, to diff the last text on each page I would need the first text of the next page. I cannot just make the page size one larger (21 in this case), because the second page would skip its first entity, and the third its first two etc.
$config = $this->Paginator->getConfig();
$this->Paginator->setConfig('limit', $config['limit'] + 1);
$inscriptions = $this->paginate($query);
I might instead be able to solve the problem by making a separate ->paginate() call for the single entity, but I would rather not execute a second query if possible.
$inscriptions = $this->paginate($query);
$config = $this->Paginator->getConfig();
$this->Paginator->setConfig([
'limit' => 1,
'page' => ($config['page'] * $config['limit']) + 1
]);
$inscriptions[] = $this->paginate($query)->first();
Is there a way to skip the first n results? In that case I could set the page size to 21 but set the page number to 1, and skip the first ((old page number - 1) * old page size) entities.

It is possible to make a custom paginator that extends the default one, but functions as described:
<?php
namespace App\Datasource;
use Cake\Core\InstanceConfigTrait;
use Cake\Datasource\Paginator;
use Cake\Datasource\QueryInterface;
use Cake\Datasource\RepositoryInterface;
class DiffPaginator extends Paginator
{
use InstanceConfigTrait;
protected function getQuery(RepositoryInterface $object, ?QueryInterface $query = null, array $data): QueryInterface
{
$data['options']['offset'] = ($data['options']['page'] - 1) * $data['options']['limit'];
$data['options']['limit'] += 1;
unset($data['options']['page']);
return parent::getQuery($object, $query, $data);
}
protected function buildParams(array $data): array
{
$paging = parent::buildParams($data);
if ($paging['current'] == $paging['perPage'] + 1) {
$paging['current'] -= 1;
}
return $paging;
}
}
In your controller then use the following:
$this->Paginator->setPaginator(new DiffPaginator);

Related

How to get Position of Comment within a Post

So I have a Post which has Comments -- I'm trying to get the Comment # position within that post. For example, a Post has 15 comments, I want to be able to get the numerical position (i.e 1 (first post), 2 (second post), 3 (third post), etc, etc), and put this into a function somehow. That way when I can call $comment->position() and it will show as the 4th, 5th, whatever position in it is in.
I've done some searching around the web and couldn't find a solution. Any help is greatly appreciated! This is what I have so far:
public function position($id,$arr)
{
$total = $this->post->comments->count();
$position = $this->pluck('post_id')->search($this->id) + 1;
return ceil($total / $position);
//$comments_per_page = 25;
//$pos = array_search($id,$arr);
//$pos = $pos+1;
//return ceil($pos/$comments_per_page);
}
You should first get all your comments as collection.
// all comments as collection
$comments = $this->post->comments;
Then you can search through the collection using the search function and inserting an id you want to search for ... or any other param you want.
$id = 2;
$commentIndex = $comments->search(function($comment) use ($id) {
return $comment->id === $id;
});

Using PHP Faker in Laravel to generate "unique with" entry when seeding a database using a factory

So Similar to the unique with validation rule (See: https://github.com/felixkiss/uniquewith-validator), I want to know how to generate a entry, where one column is unique with another one. I want to seed my database as follows.
Example:
There are 12 steps in the "steps" table. Each step should have 5 categories associated with each one that are stored in the "step_categories" table. Each of those categories are assigned a unique order number 1 through 5 that is unique with each "step_id".
See this image here for an example of what the database should look like: https://imgur.com/a/XYA5yyn
I had to manually to make the entries in the database for the above image example. I don't want to have to generate this manually every time, say I make a mistake and have to rollback the migrations for example.
I am using a factory to generate this data. So the factory name is StepCategoriesFactory.php and clearly I'm calling the factory with the create() method from the DatabaseSeeder.php file.
I thought about doing this in a for loop, then i got as far as realizing when i called the 'step_id' => App\Model::all()->random()->id to grab a new id, that I wouldn't be able to ensure I wasn't grabbing the id that i just generated 5 entries for. I'm really new with Laravel, and I'm not sure where to even start on this. There's no real information on SO where faker can use the unique with another column. How would I Go about this?
NOTE: The step id is not always going to be 1-12. The step ID might be different depending on whether a step gets deleted and remade. So just assigning the step_id to equal 1-12 wont work.
UPDATE: Here's some code I just wrote, and I think I'm on the right track. Maybe. I've grabbed the step_id by it's number field as that will always be 1-12, and I've grabbed the IID out of the entry. But now I'm stuck on how to generate the order 1-5 without repeating itself. I still haven't run this yet as its incomplete and I know it'll throw an error without the correct order number.
UPDATE 2: I think I'm on the right track here. However I'm getting an undefined variable error. When I put the first line from within the anonymous function, it's resetting the order to "1" for every entry. How do i make the $autoIncrement variable available to the anonymous function? The Seeder has stayed the same between updates.
Image of the error: https://imgur.com/a/ywkd0Lb
Second image with the Die/Dump error in terminal: https://imgur.com/a/rOGRv32
Reference this article here: https://laracasts.com/discuss/channels/laravel/model-factory-increment-value-faker?page=1
UPDATE 3: I forgot the use ($autoIncrement) line of code for the anonymous function. Code below has been updated, but now I'm getting a different error saying that the order column has a null value and can't be inserted. clearly it should be '1'. Even after I call my $autoIncrement->next(); which should increment it to '1' it's still returning null according to the terminal. However, when I do a diedump on $autoIncrement->current() it's returning 1. Weird.
Update 3 error: https://imgur.com/a/STOmIjF
StepCategoriesFactory.php
use Faker\Generator as Faker;
$autoIncrement = autoIncrement();
$factory->define(App\StepCategory::class, function (Faker $faker) use ($autoIncrement) {
// Generate Created At and Updated at DATETIME
$DateTime = $faker->dateTime($max = 'now');
$autoIncrement->next();
$order = (int) $autoIncrement->current();
return [
// Generate Dummy Data
'order' => $order,
'name' => $faker->words(4, true),
'created_at' => $DateTime,
'updated_at' => $DateTime,
];
});
function autoIncrement()
{
for ($i = 0; $i < 5; $i++) {
yield $i;
}
}
Edit: Put a bounty on this question, as I think it would be helpful for the community to get a detailed answer. I'm looking for help to explain how to go about making sure I'm grabbing the same entry through each loop.
FINALLY SOLVED!
So I took in everyone's answers, and thought long and hard about using a for loop to create the order number. 1-5. The problem that I was running into at the end was that the $i variable was not resetting. So after the yield I had to check if the $i variable equalled 5 and then reset it back to zero.
Heres the code!
StepCategories.php
use Faker\Generator as Faker;
$autoIncrement = autoIncrement();
$factory->define(App\StepCategory::class, function (Faker $faker) use ($autoIncrement) {
// Generate Created At and Updated at DATETIME
$DateTime = $faker->dateTime($max = 'now');
// Get the next iteration of the autoIncrement Function
$autoIncrement->next();
// Assign the current $i value to a typecast variable.
$order = (int) $autoIncrement->current();
return [
// Generate Dummy Data
'order' => $order,
'name' => $faker->words(4, true),
'created_at' => $DateTime,
'updated_at' => $DateTime,
];
});
function autoIncrement()
{
// Start a loop
for ($i = 0; $i <= 5; $i++) {
// Yield the current value of $i
yield $i;
// If $i is equal to 5, that must mean the start of a new loop
if($i == 5) {
// Reset $i to 0 to start over.
$i = 0;
}
}
}
DatabaseSeeder.php
// Generate Dummy Categories
// Run the factory 12 times
foreach(range(1, 12) as $i) {
// Generate 5 entries each time
factory(App\StepCategory::class, 5)->create([
// Since all steps have a number 1-12 grab the step by the number column and get it's ID
'step_id' => App\Step::where('number', '=', $i)->first()->id,
]);
}
Thanks to all who helped!
Sorry if you don't understand my point so I'll try to explain it in code
use Illuminate\Database\Seeder;
$factory->define(App\StepCategory::class, function (Faker $faker) {
// Generate Created At and Updated at DATETIME
$DateTime = $faker->dateTime($max = 'now');
$step_id = function () {
return factory('App\Step')->create()->id;
};
return [
// Generate Dummy Data
'step_id' => $step_id,
'order' => uniqueOrder($step_id),
'name' => $faker->words(4, true),
'created_at' => $DateTime,
'updated_at' => $DateTime,
];
});
function uniqueOrder($step_id)
{
$unique = rand(1,5);
do {
$unique = rand(1,5);
}
while(StepCategory::where('step_id', $step_id)->andWhere( 'order', $unique)->exists())
return $unique;
}
for example if your Step model name is Steps :
$allSteps = Steps::all();
foreach($allSteps as $step){
for($i=1;$i<6;$i++){
//insert to table $step->id , $i for example
DB::table('yourTableName')->insert([
'name'=>'Step '.$step->id.'- Category '.$i ,
'order'=>$i ,
'step_id'=>$step->id
]);
}
}

Add sequential number in database, that resets every year

I am building a project using Codeigniter + MySQL + Active Record.
In my MySQL db i have a table named Requests with columns protocol,year and some other columns where i store general information of a request (like title, subject etc...), nothing special to mention.
Protocol and year are TYPE INT and must be auto generated like below.
Every year, at 01/01/XXXX-00:00:00 the protocol field must reset to number 1 (the first request saved after 01/01/XXXX, must have protocol value 1) and for the upcoming requests, increase it sequentially by 1, until next year and so on.
To reset the protocol, i will add a cron in a later phase where i will do a trick to reset. Still don't know how.
What i need to do now:
Whenever i add a new request, the protocol of the new row must be increased by 1.
Pseudocode:
new_protocol = previous_protocol + 1
What i am doing so far
I have a function in my model which Inserts a new request, but so far i do nothing about the protocol number, i enter it manually through my form.
public function addRequest($request) {
$this->db->insert('requests', $request);
if ($this->db->affected_rows() == 1)
return $this->db->insert_id();
return FALSE;
}
What is the most efficient way to achieve this?
Should i find the last protocol number in my Controller -> add 1 -> send to model to add?
Should i find the LAST and MAX protocol number in model -> add 1 -> insert to db?
Something else?
Any ideas will be appreciated.
I'd approach like so:
add column ID in your table and set it to auto-increment
php - set the default timezone to use (since PHP 5.1): date_default_timezone_set('UTC');
php - get current year: $cyear=date("Y");
get row max(ID) and compare if the stored year has changed compared to $cyear
if it has changed and following your pseudocode example:
new_protocol = 1
otherwise
new_protocol = previous_protocol + 1
Ok so i created a library in Codeigniter and wrote 2 functions, 1 in Library and 1 in Model.
Library
public function getProtocolNumber() {
$CI = &get_instance();
$year = date('Y');
$max_protocol = $CI->request_model->getYearsProtocol($year);
$max_protocol = $max_protocol['max_protocol_number'];
if ($max_protocol && !empty($max_protocol) && $max_protocol != NULL) {
$protocol_number = (int) $max_protocol + 1;
} else {
$protocol_number = 1;
}
return $protocol_number;
}
Model
public function getYearsProtocol($year) {
$qry = $this->db->select('MAX(protocol_number) as max_protocol_number')
->from('requests')
->where('protocol_year', $year)
->get();
if ($qry->num_rows() > 0)
return $qry->row_array();
return FALSE;
}

Use of manual pagination "$perPage" parameter

So I've been working on a pagination system that pulls data externally, after finally figuring out through trial and error I got the solution that works. While going through it something struck me as odd, as per the documentation $paginator = Paginator::make($items, $totalItems, $perPage);
I was wondering what's the actual use of the $perPage parameter? You would think that what ever number is specified would show that many items. But with manual pagination you have to limit the results that are passed into $items in order for it to work, otherwise you get the output of all items (as shown in code block below). Is manual pagination flawed? because if $perPage doesn't match the total number of items in the array $items it shows everything.
Example: Paginator::make( array('10xarray') ), 10, 2); it would show 5 pages with 2 items per page? where in reality it actually shows 10 items with 5 pages that all show the same 10 items.
<?php
class MainController extends BaseController {
public function library()
{
$this->layout->title = 'testing';
$this->layout->main = View::make('library/layout');
// Pagination data
$media = array(
array('title' => 'test'),
array('title' => 'test'),
array('title' => 'test'),
array('title' => 'test')
);
$perPage = 2;
$currentPage = Input::get('page', 1);
$pagedData = array_slice($media, ($currentPage - 1) * $perPage, $perPage);
$this->layout->main->paginated = Paginator::make($pagedData, count($media), $perPage);
if(Request::ajax()) {
return Response::json(
View::make(
'library/layout',
array('paginated' => $this->layout->main->paginated)
)->render()
);
}
}
}
it isn't flawed. it is intended behavior.
this manual paginator is just a container. nothing more, nothing less. how you implement it, is upon you.
without a LIMIT query, you can never do a pagination. when you use pagination(), laravel does the work under the hood. when you go for manual, you have to do it manually and get greater control.
why do you think it is called manual pagination in the first place?

PHP - best way to initialize an object with a large number of parameters and default values

I'm designing a class that defines a highly complex object with a ton (50+) of mostly optional parameters, many of which would have defaults (eg: $type = 'foo'; $width = '300'; $interactive = false;). I'm trying to determine the best way to set up the constructor and instance/class variables in order to be able to:
make it easy to use the class
make it easy to auto-document the class (ie: using phpDocumentor)
code this elegantly
In light of the above, I don't want to be passing the constructor a ton of arguments. I will be passing it a single hash which contains the initialization values, eg: $foo = new Foo(array('type'=>'bar', 'width'=>300, 'interactive'=>false));
In terms of coding the class, I still feel like I would rather have...
class Foo {
private $_type = 'default_type';
private $_width = 100;
private $_interactive = true;
...
}
...because I believe this would facilitate documentation generation (you get the list of the class' properties, which lets the API user know what 'options' they have to work with), and it "feels" like the right way to do it.
But then you run into the problem of mapping the incoming parameters in the constructor to the class variables, and without exploiting the symbol table, you get into a "brute force" approach which to me defeats the purpose (though I'm open to other opinions). E.g.:
function __construct($args){
if(isset($args['type'])) $_type = $args['type']; // yuck!
}
I've considered creating a single class variable that is itself an associative array. Initializing this would be really easy then, e.g.:
private $_instance_params = array(
'type' => 'default_type',
'width' => 100,
'interactive' => true
);
function __construct($args){
foreach($args as $key=>$value){
$_instance_params[$key] = $value;
}
}
But this seems like I'm not taking advantage of native features like private class variables, and it feels like documentation generation will not work with this approach.
Thanks for reading this far; I'm probably asking a lot here, but I'm new to PHP and am really just looking for the idiomatic / elegant way of doing this. What are your best practices?
Addendum (details about this particular Class)
It's quite likely that this class is trying to do too much, but it is a port of an old Perl library for creating and processing forms. There's probably a way of dividing the configuration options to take advantage of inheritance and polymorphism, but it may actually be counter-productive.
By request, here is a partial listing of some of the parameters (Perl code). You should see that these don't map very well to sub-classes.
The class certainly has getters and setters for many of these properties so the user can over-ride them; the objective of this post (and something the original code does nicely) is to provide a compact way of instantiating these Form objects with the required parameters already set. It actually makes for very readable code.
# Form Behaviour Parameters
# --------------------------
$self->{id}; # the id and the name of the <form> tag
$self->{name} = "webform"; # legacy - replaced by {id}
$self->{user_id} = $global->{user_id}; # used to make sure that all links have the user id encoded in them. Usually this gets returned as the {'i'} user input parameter
$self->{no_form}; # if set, the <form> tag will be omitted
$self->{readonly}; # if set, the entire form will be read-only
$self->{autosave} = ''; # when set to true, un-focusing a field causes the field data to be saved immediately
$self->{scrubbed}; # if set to "true" or non-null, places a "changed" radio button on far right of row-per-record forms that indicates that a record has been edited. Used to allow users to edit multiple records at the same time and save the results all at once. Very cool.
$self->{add_rowid}; # if set, each row in a form will have a hidden "rowid" input field with the row_id of that record (used primarily for scrubbable records). If the 'scrubbed' parameter is set, this parameter is also automatically set. Note that for this to work, the SELECT statement must pull out a unique row id.
$self->{row_id_prefix} = "row_"; # each row gets a unique id of the form id="row_##" where ## corresponds to the record's rowid. In the case of multiple forms, if we need to identify a specific row, we can change the "row_" prefix to something unique. By default it's "row_"
$self->{validate_form}; # parses user_input and validates required fields and the like on a form
$self->{target}; # adds a target window to the form tag if specified
$self->{focus_on_field}; # if supplied, this will add a <script> tag at the end of the form that will set the focus on the named field once the form loads.
$self->{on_submit}; # adds the onSubmit event handler to the form tag if supplied
$self->{ctrl_s_button_name}; # if supplied with the name of the savebutton, this will add an onKeypress handler to process CTRL-S as a way of saving the form
# Form Paging Parameters
# ----------------------
$self->{max_rows_per_page}; # when displaying a complete form using printForm() method, determines the number of rows shown on screen at a time. If this is blank or undef, then all rows in the query are shown and no header/footer is produced.
$self->{max_pages_in_nav} = 7; # when displaying the navbar above and below list forms, determines how many page links are shown. Should be an odd number
$self->{current_offset}; # the current page that we're displaying
$self->{total_records}; # the number of records returned by the query
$self->{hide_max_rows_selector} = ""; # hide the <select> tag allowing users to choose the max_rows_per_page
$self->{force_selected_row} = ""; # if this is set, calls to showPage() will also clear the rowid hidden field on the form, forcing the first record to be displayed if none were selected
$self->{paging_style} = "normal"; # Options: "compact"
We can, of course, allow ourselves to be drawn into a more lengthy debate around programming style. But I'm hoping to avoid it, for the sanity of all involved! Here (Perl code, again) is an example of instantiating this object with a pretty hefty set of parameters.
my $form = new Valz::Webform (
id => "dbForm",
form_name => "user_mailbox_recip_list_students",
user_input => \%params,
user_id => $params{i},
no_form => "no_form",
selectable => "checkbox",
selectable_row_prefix => "student",
selected_row => join (",", getRecipientIDsByType('student')),
this_page => $params{c},
paging_style => "compact",
hide_max_rows_selector => 'true',
max_pages_in_nav => 5
);
I can think of two ways of doing that. If you want to keep your instance variables you can just iterate through the array passed to the constructor and set the instance variable dynamically:
<?php
class Foo {
private $_type = 'default_type';
private $_width = 100;
private $_interactive = true;
function __construct($args){
foreach($args as $key => $val) {
$name = '_' . $key;
if(isset($this->{$name})) {
$this->{$name} = $val;
}
}
}
}
?>
When using the array approach you don't really have to abandon documentation. Just use the #property annotations in the class body:
<?php
/**
* #property string $type
* #property integer $width
* #property boolean $interactive
*/
class Foo {
private $_instance_params = array(
'type' => 'default_type',
'width' => 100,
'interactive' => true
);
function __construct($args){
$this->_instance_params = array_merge_recursive($this->_instance_params, $args);
}
public function __get($name)
{
return $this->_instance_params[$name];
}
public function __set($name, $value)
{
$this->_instance_params[$name] = $value;
}
}
?>
That said, a class with 50 member variables is either only used for configuration (which can be split up) or it is just doing too much and you might want to think about refactoring it.
Another approach is to instantiate the class with a FooOptions object, acting solely as an options container:
<?php
class Foo
{
/*
* #var FooOptions
*/
private $_options;
public function __construct(FooOptions $options)
{
$this->_options = $options;
}
}
class FooOptions
{
private $_type = 'default_type';
private $_width = 100;
private $_interactive = true;
public function setType($type);
public function getType();
public function setWidth($width);
public function getWidth();
// ...
}
Your options are well documented and you have an easy way to set/retrieve them. This even facilitates your testing, as you can create and set different options objects.
I don't remember the exact name of this pattern, but I think it's Builder or Option pattern.
Just to follow up with how I implemented this, based on one of Daff's solutions:
function __construct($args = array()){
// build all args into their corresponding class properties
foreach($args as $key => $val) {
// only accept keys that have explicitly been defined as class member variables
if(property_exists($this, $key)) {
$this->{$key} = $val;
}
}
}
Improvement suggestions welcomed!
You also could make a parent class.
In that class you only define the variables.
protected function _SetVarName( $arg ){
$this->varName=$arg;
}
Then extend that class into a new file and in that file you create all your processes.
So you get
classname.vars.php
classname.php
classname extends classnameVars {
}
Because most will be on default you only have to Set/Reset the ones you need.
$cn=new classname();
$cn->setVar($arg);
//do your functions..
I use this on a few of my classes. Makes it easy to copy and paste for rapid development.
private $CCNumber, $ExpMonth, $ExpYear, $CV3, $CardType;
function __construct($CCNumber, $ExpMonth, $ExpYear, $CV3, $CardType){
$varsValues = array($CCNumber, $ExpMonth, $ExpYear, $CV3, $CardType);
$varNames = array('CCNumber', 'ExpMonth', 'ExpYear', 'CV3', 'CardType');
$varCombined = array_combine($varNames, $varsValues);
foreach ($varCombined as $varName => $varValue) {$this->$varName = $varValue;}
}
Steps to use:
Paste in and get the list of variables from your current __construct function, removing any optional parameter values
If you haven't already, paste that in to declare your variables for your class, using the scope of your choosing
Paste that same line into the $varValues and $varNames lines.
Do a text replace on ", $" for "', '". That'll get all but the first and last that you'll have to manually change
Enjoy!
Just a little improvement on Daff's first solution to support object properties that may have a null default value and would return FALSE to the isset() condition:
<?php
class Foo {
private $_type = 'default_type';
private $_width = 100;
private $_interactive = true;
private $_nullable_par = null;
function __construct($args){
foreach($args as $key => $val) {
$name = '_' . $key;
if(property_exists(get_called_class(),$name))
$this->{$name} = $val;
}
}
}
}
?>

Categories