Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
W
webbasics
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Taddeüs Kroes
webbasics
Commits
9b2c2614
Commit
9b2c2614
authored
Jul 15, 2012
by
Taddeus Kroes
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Added Collection class for easy item set manipulations.
parent
ebf19c3d
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
537 additions
and
0 deletions
+537
-0
collection.php
collection.php
+313
-0
tests/test_collection.php
tests/test_collection.php
+224
-0
No files found.
collection.php
0 → 100644
View file @
9b2c2614
<?php
/**
* Collection functions, mainly used to manipulate lists of PHPActiveRecord models.
*
* @author Taddeus Kroes
* @version 1.0
* @date 13-07-2012
*/
namespace
WebBasics
;
require_once
'base.php'
;
/**
* The Collection class contains a number of functions sort, index and manipulate
* an array of items.
*
* Example 1: Index a list based on its values and extract a single attribute.
* <code>
* class Book extends PHPActiveRecord\Model {
* static $attr_accessible = array('id', 'name');
* }
*
* // Index all book names by their id.
* $books = Book::all(); // Find a list of books
* $collection = new Collection($books); // Put the list in a collection
* $indexed = $collection->index_by('id'); // Create indexes for all books
* $names = $indexed->get_attribute('name'); // Get the values of a single attribute
* // $names now contains something like array(1 => 'Some book', 2 => 'Another book')
*
* // Same as above:
* $names = Collection::create(Book::all())->index_by('id')->get_attribute('name');
* </code>
*
* Example 2: Execute a method for each item in a list.
* <code>
* // Delete all books
* Collection::create(Book::all())->map_method('delete');
* </code>
*
* @package WebBasics
* @todo Finish unit tests
*/
class
Collection
extends
Base
{
/**
* The set of items item that is being manipulated, as an associoative array.
*
* @var array
*/
private
$items
;
/**
* Create a new Collection object.
*
* @param array $items Initial item set (optional).
*/
function
__construct
(
array
$items
=
array
())
{
$this
->
items
=
$items
;
}
/**
* Add an item to the collection.
*
* @param mixed $item The item to add.
*/
function
add
(
$item
)
{
$this
->
items
[]
=
$item
;
}
/**
* Insert an item at a specific index in the collection.
*
* If the index is numeric, all existing numeric indices from that
* index will be shifted one position to the right.
*
* @param mixed $item The item to insert.
* @param int|string $index The index at which to insert the item.
* @throws \InvalidArgumentException If the added index already exists, and is non-numeric.
*/
function
insert
(
$item
,
$index
)
{
if
(
isset
(
$this
->
items
[
$index
])
)
{
if
(
!
is_int
(
$index
)
)
throw
new
\InvalidArgumentException
(
sprintf
(
'Index "%s" already exists in this collection.'
,
$index
));
for
(
$i
=
count
(
$this
->
items
)
-
1
;
$i
>=
$index
;
$i
--
)
$this
->
items
[
$i
+
1
]
=
$this
->
items
[
$i
];
}
$this
->
items
[
$index
]
=
$item
;
}
/**
* Get all items in the collection as an array.
*
* @return array The items in the collection.
*/
function
all
()
{
return
$this
->
items
;
}
/**
* Get the first item in the collection.
*
* @return mixed
* @throws \OutOfBoundsException if the collection is empty.
*/
function
first
()
{
if
(
!
$this
->
count
()
)
throw
new
\OutOfBoundsException
(
sprintf
(
'Cannot get first item: collection is empty.'
));
return
$this
->
items
[
0
];
}
/**
* Get the last item in the collection.
*
* @return mixed
* @throws \OutOfBoundsException if the collection is empty.
*/
function
last
()
{
if
(
!
$this
->
count
()
)
throw
new
\OutOfBoundsException
(
sprintf
(
'Cannot get last item: collection is empty.'
));
return
end
(
$this
->
items
);
}
/**
* Get the number of items in the collection.
*
* @return int The number of items in the collection.
*/
function
count
()
{
return
count
(
$this
->
items
);
}
/**
* Check if an item with the given index exists.
*
* @param int|string $index The index to check existance of.
* @return bool Whether the index exists.
*/
function
index_exists
(
$index
)
{
return
isset
(
$this
->
items
[
$index
]);
}
/**
* Get an item from the collection by its index.
*
* @param int|string $index The index to the item to get.
* @return mixed The value corresponding to the specified index.
*/
function
get
(
$index
)
{
return
$this
->
items
[
$index
];
}
/**
* Delete an item from the collection by its index.
*
* @param int|string $index The index to the item to delete.
*/
function
delete_index
(
$index
)
{
unset
(
$this
->
items
[
$index
]);
}
/**
* Delete an item from the collection.
*
* @param mixed $item The item to delete.
*/
function
delete
(
$item
)
{
$this
->
delete_index
(
array_search
(
$item
,
$this
->
items
));
}
/**
* Create a new item set with the same class name as this object.
*
* @param array $items The new items to create a set with.
* @param bool $clone If TRUE, the item set will overwrite the current
* object's item set and not create a new object.
* @return Collection A collection with the new item set.
*/
private
function
set_items
(
array
$items
,
$clone
=
true
)
{
if
(
$clone
)
return
new
self
(
$items
);
$this
->
items
=
$items
;
return
$this
;
}
/**
* Remove duplicates from the current item set.
*
* @param bool $clone Whether to create a new object, or overwrite the current item set.
* @return Collection A collection without duplicates.
*/
function
uniques
(
$clone
=
false
)
{
return
$this
->
set_items
(
array_values
(
array_unique
(
$this
->
items
)),
$clone
);
}
/**
* Filter items from the collection.
*
* @param callable $callback Function that receives an item from the collection and
* returns TRUE if the item should be present in the
* resulting collection.
* @param bool $clone Whether to create a new object, or overwrite the current item set.
* @return Collection A collection with the filtered set of items.
*/
function
filter
(
$callback
,
$clone
=
true
)
{
return
$this
->
set_items
(
array_values
(
array_filter
(
$this
->
items
,
$callback
)),
$clone
);
}
/**
* Find a subset of items in the collection using property value conditions.
*
* The conditions are specified as an associative array of property names
* pointing to values. Only items whose properties have these values will
* appear in the resulting collection. The items in the collection have to
* be objects or associative arrays, or an error will occur.
*
* @param array $conditions The conditions that items in the subset should meet.
* @param bool $clone Whether to create a new object, or overwrite the current item set.
* @throws \UnexpectedValueException If a non-object and non-array value is encountered.
* @return Collection
*/
function
find
(
array
$conditions
,
$clone
=
true
)
{
return
$this
->
filter
(
function
(
$item
)
use
(
$conditions
)
{
if
(
is_object
(
$item
)
)
{
// Object, match property values
foreach
(
$conditions
as
$property
=>
$value
)
if
(
$item
->
{
$property
}
!=
$value
)
return
false
;
}
elseif
(
is_array
(
$item
)
)
{
// Array item, match array values
foreach
(
$conditions
as
$property
=>
$value
)
if
(
$item
[
$property
]
!=
$value
)
return
false
;
}
else
{
// Other, incompatible type -> throw exception
throw
new
\UnexpectedValueException
(
sprintf
(
'Collection::find encountered a non-object and non-array item "%s".'
,
$item
)
);
}
return
true
;
},
$clone
);
}
/**
* Get an attribute value for each of the items in the collection.
*
* The items are assumed to be objects with the specified attribute.
*
* @param string $attribute The name of the attribute to get the value of.
* @return array The original item keys, pointing to single attribute values.
*/
function
get_attribute
(
$attribute
)
{
return
array_map
(
function
(
$item
)
use
(
$attribute
)
{
return
$item
->
{
$attribute
};
},
$this
->
items
);
}
/**
* Use an attribute of each item in the collection as an index to that item.
*
* The items are assumed to be objects with the specified index attribute.
*
* @param string $attribute The name of the attribute to use as index value.
* @param bool $clone Whether to create a new object, or overwrite the current item set.
* @return Collection A collection object with the values of the attribute used as indices.
*/
function
index_by
(
$attribute
,
$clone
=
true
)
{
$indexed
=
array
();
foreach
(
$this
->
items
as
$item
)
$indexed
[
$item
->
$attribute
]
=
$item
;
return
$this
->
set_items
(
$indexed
,
$clone
);
}
/**
* Execute a callback for each of the items in the collection.
*
* @param callable $callback Function that receives an item from the collection.
* @param bool $clone Whether to create a new object, or overwrite the current item set.
* @return Collection A collection with return values of the callback calls.
*/
function
map
(
$callback
,
$clone
=
true
)
{
return
$this
->
set_items
(
array_map
(
$callback
,
$this
->
items
),
$clone
);
}
/**
* Execute an object method for each item in the collection.
*
* The items are assumed to be objects with the specified method.
*
* @param string $method_name The name of the method to execute.
* @param array $args Any arguments to pass to the method.
* @param bool $clone Whether to create a new object, or overwrite the current item set.
* @return Collection A collection with return values of the method calls.
*/
function
map_method
(
$method_name
,
array
$args
=
array
(),
$clone
=
true
)
{
$items
=
array
();
foreach
(
$this
->
items
as
$item
)
$items
[]
=
call_user_func_array
(
array
(
$item
,
$method_name
),
$args
);
return
$this
->
set_items
(
$items
);
}
}
?>
\ No newline at end of file
tests/test_collection.php
0 → 100644
View file @
9b2c2614
<?php
require_once
'collection.php'
;
use
WebBasics\Collection
;
class
IdObject
{
static
$count
=
0
;
function
__construct
(
$foo
=
null
)
{
$this
->
id
=
++
self
::
$count
;
$this
->
foo
=
$foo
;
}
function
get_id
()
{
return
$this
->
id
;
}
static
function
clear_counter
()
{
self
::
$count
=
0
;
}
}
function
set
(
$items
=
array
())
{
return
new
Collection
(
$items
);
}
function
std_object
(
array
$properties
)
{
$object
=
new
stdClass
();
foreach
(
$properties
as
$property
=>
$value
)
$object
->
{
$property
}
=
$value
;
return
$object
;
}
class
CollectionTest
extends
PHPUnit_Framework_TestCase
{
function
setUp
()
{
$this
->
set
=
set
(
array
(
1
,
2
));
}
function
test_add
()
{
$this
->
set
->
add
(
3
);
$this
->
assertEquals
(
$this
->
set
,
set
(
array
(
1
,
2
,
3
)));
}
/**
* @expectedException InvalidArgumentException
*/
function
test_insert_error
()
{
set
(
array
(
'foo'
=>
1
))
->
insert
(
2
,
'foo'
);
}
function
test_insert_success
()
{
$this
->
set
->
insert
(
4
,
1
);
$this
->
assertEquals
(
$this
->
set
,
set
(
array
(
1
,
4
,
2
)));
$this
->
set
->
insert
(
5
,
0
);
$this
->
assertEquals
(
$this
->
set
,
set
(
array
(
5
,
1
,
4
,
2
)));
}
function
test_all
()
{
$this
->
assertEquals
(
set
()
->
all
(),
array
());
$this
->
assertEquals
(
set
(
array
())
->
all
(),
array
());
$this
->
assertEquals
(
set
(
array
(
1
))
->
all
(),
array
(
1
));
$this
->
assertEquals
(
set
(
array
(
1
,
2
))
->
all
(),
array
(
1
,
2
));
}
/**
* @expectedException OutOfBoundsException
*/
function
test_last_empty
()
{
set
()
->
last
();
}
function
test_last
()
{
$this
->
assertEquals
(
$this
->
set
->
last
(),
2
);
}
/**
* @expectedException OutOfBoundsException
*/
function
test_first_empty
()
{
set
()
->
first
();
}
function
test_first
()
{
$this
->
assertEquals
(
$this
->
set
->
first
(),
1
);
}
function
test_count
()
{
$this
->
assertEquals
(
set
()
->
count
(),
0
);
$this
->
assertEquals
(
set
(
array
())
->
count
(),
0
);
$this
->
assertEquals
(
set
(
array
(
1
))
->
count
(),
1
);
$this
->
assertEquals
(
set
(
array
(
1
,
2
))
->
count
(),
2
);
}
function
test_index_exists
()
{
$this
->
assertTrue
(
$this
->
set
->
index_exists
(
1
));
$this
->
assertTrue
(
set
(
array
(
'foo'
=>
'bar'
))
->
index_exists
(
'foo'
));
}
function
test_get
()
{
$this
->
assertEquals
(
$this
->
set
->
get
(
0
),
1
);
$this
->
assertEquals
(
$this
->
set
->
get
(
1
),
2
);
$this
->
assertEquals
(
set
(
array
(
'foo'
=>
'bar'
))
->
get
(
'foo'
),
'bar'
);
}
function
test_delete_index
()
{
$this
->
set
->
delete_index
(
0
);
$this
->
assertEquals
(
$this
->
set
,
set
(
array
(
1
=>
2
)));
}
function
test_delete
()
{
$this
->
set
->
delete
(
1
);
$this
->
assertEquals
(
$this
->
set
,
set
(
array
(
1
=>
2
)));
}
function
assert_set_equals
(
array
$expected_items
,
$set
)
{
$this
->
assertAttributeEquals
(
$expected_items
,
'items'
,
$set
);
}
function
test_uniques
()
{
$this
->
assert_set_equals
(
array
(
1
,
2
),
set
(
array
(
1
,
2
,
2
))
->
uniques
());
$this
->
assert_set_equals
(
array
(
2
,
1
),
set
(
array
(
2
,
1
,
2
))
->
uniques
());
$this
->
assert_set_equals
(
array
(
2
,
1
),
set
(
array
(
2
,
2
,
1
))
->
uniques
());
}
function
set_items
(
$collection
,
$items
,
$clone
)
{
$rm
=
new
ReflectionMethod
(
$collection
,
'set_items'
);
$rm
->
setAccessible
(
true
);
return
$rm
->
invoke
(
$collection
,
$items
,
$clone
);
}
function
test_set_items_clone
()
{
$result
=
$this
->
set_items
(
$this
->
set
,
array
(
3
,
4
),
true
);
$this
->
assert_set_equals
(
array
(
1
,
2
),
$this
->
set
);
$this
->
assert_set_equals
(
array
(
3
,
4
),
$result
);
$this
->
assertNotSame
(
$this
->
set
,
$result
);
}
function
test_set_items_no_clone
()
{
$result
=
$this
->
set_items
(
$this
->
set
,
array
(
3
,
4
),
false
);
$this
->
assertSame
(
$this
->
set
,
$result
);
}
/**
* @depends test_set_items_clone
*/
function
test_filter
()
{
$smaller_than_five
=
function
(
$number
)
{
return
$number
<
5
;
};
$this
->
assert_set_equals
(
array
(
2
,
4
,
1
,
4
),
set
(
array
(
2
,
7
,
4
,
7
,
1
,
8
,
4
,
5
))
->
filter
(
$smaller_than_five
));
}
/**
* @depends test_filter
*/
function
test_find_success
()
{
$items
=
array
(
array
(
'foo'
=>
'bar'
,
'bar'
=>
'baz'
),
array
(
'foo'
=>
'baz'
,
'bar'
=>
'foo'
),
std_object
(
array
(
'foo'
=>
'bar'
,
'baz'
=>
'bar'
)),
);
$this
->
assert_set_equals
(
array
(
$items
[
1
]),
set
(
$items
)
->
find
(
array
(
'foo'
=>
'baz'
)));
$this
->
assert_set_equals
(
array
(
$items
[
0
],
$items
[
2
]),
set
(
$items
)
->
find
(
array
(
'foo'
=>
'bar'
)));
}
/**
* @depends test_find_success
* @expectedException \UnexpectedValueException
* @expectedExceptionMessage Collection::find encountered a non-object and non-array item "foobar".
*/
function
test_find_failure
()
{
$items
=
array
(
array
(
'foo'
=>
'bar'
,
'bar'
=>
'baz'
),
'foobar'
,
);
set
(
$items
)
->
find
(
array
(
'foo'
=>
'bar'
));
}
function
test_get_attribute_simple
()
{
IdObject
::
clear_counter
();
$set
=
set
(
array
(
new
IdObject
(),
new
IdObject
(),
new
IdObject
()));
$this
->
assertEquals
(
array
(
1
,
2
,
3
),
$set
->
get_attribute
(
'id'
));
}
/**
* @depends test_get_attribute_simple
*/
function
test_get_attribute_indices
()
{
IdObject
::
clear_counter
();
$set
=
set
(
array
(
'foo'
=>
new
IdObject
(),
'bar'
=>
new
IdObject
(),
'baz'
=>
new
IdObject
()));
$this
->
assertEquals
(
array
(
'foo'
=>
1
,
'bar'
=>
2
,
'baz'
=>
3
),
$set
->
get_attribute
(
'id'
));
}
/**
* @depends test_all
* @depends test_set_items_clone
*/
function
test_index_by
()
{
IdObject
::
clear_counter
();
$set
=
set
(
array
(
new
IdObject
(
'foo'
),
new
IdObject
(
'bar'
),
new
IdObject
(
'baz'
)));
list
(
$foo
,
$bar
,
$baz
)
=
$set
->
all
();
$this
->
assert_set_equals
(
array
(
'foo'
=>
$foo
,
'bar'
=>
$bar
,
'baz'
=>
$baz
),
$set
->
index_by
(
'foo'
));
}
/**
* @depends test_set_items_clone
*/
function
test_map
()
{
$plus_five
=
function
(
$number
)
{
return
$number
+
5
;
};
$this
->
assert_set_equals
(
array
(
6
,
7
,
8
),
set
(
array
(
1
,
2
,
3
))
->
map
(
$plus_five
));
}
/**
* @depends test_set_items_clone
*/
function
test_map_method
()
{
IdObject
::
clear_counter
();
$set
=
set
(
array
(
new
IdObject
(),
new
IdObject
(),
new
IdObject
()));
$this
->
assert_set_equals
(
array
(
1
,
2
,
3
),
$set
->
map_method
(
'get_id'
));
}
}
?>
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment