<?php
class
Flexihash
{
private
$_replicas
= 64;
private
$_hasher
;
private
$_targetCount
= 0;
private
$_positionToTarget
=
array
();
private
$_targetToPositions
=
array
();
private
$_positionToTargetSorted
= false;
public
function
__construct(Flexihash_Hasher
$hasher
= null,
$replicas
= null)
{
$this
->_hasher =
$hasher
?
$hasher
:
new
Flexihash_Crc32Hasher();
if
(!
empty
(
$replicas
))
$this
->_replicas =
$replicas
;
}
public
function
addTarget(
$target
)
{
if
(isset(
$this
->_targetToPositions[
$target
]))
{
throw
new
Flexihash_Exception(
"Target '$target' already exists."
);
}
$this
->_targetToPositions[
$target
] =
array
();
for
(
$i
= 0;
$i
<
$this
->_replicas;
$i
++)
{
$position
=
$this
->_hasher->hash(
$target
.
$i
);
$this
->_positionToTarget[
$position
] =
$target
;
$this
->_targetToPositions[
$target
] []=
$position
;
}
$this
->_positionToTargetSorted = false;
$this
->_targetCount++;
return
$this
;
}
public
function
addTargets(
$targets
)
{
foreach
(
$targets
as
$target
)
{
$this
->addTarget(
$target
);
}
return
$this
;
}
public
function
removeTarget(
$target
)
{
if
(!isset(
$this
->_targetToPositions[
$target
]))
{
throw
new
Flexihash_Exception(
"Target '$target' does not exist."
);
}
foreach
(
$this
->_targetToPositions[
$target
]
as
$position
)
{
unset(
$this
->_positionToTarget[
$position
]);
}
unset(
$this
->_targetToPositions[
$target
]);
$this
->_targetCount--;
return
$this
;
}
public
function
getAllTargets()
{
return
array_keys
(
$this
->_targetToPositions);
}
public
function
lookup(
$resource
)
{
$targets
=
$this
->lookupList(
$resource
, 1);
if
(
empty
(
$targets
))
throw
new
Flexihash_Exception('No targets exist');
return
$targets
[0];
}
public
function
lookupList(
$resource
,
$requestedCount
)
{
if
(!
$requestedCount
)
throw
new
Flexihash_Exception('Invalid
count
requested');
if
(
empty
(
$this
->_positionToTarget))
return
array
();
if
(
$this
->_targetCount == 1)
return
array_unique
(
array_values
(
$this
->_positionToTarget));
$resourcePosition
=
$this
->_hasher->hash(
$resource
);
$results
=
array
();
$collect
= false;
$this
->_sortPositionTargets();
foreach
(
$this
->_positionToTarget
as
$key
=>
$value
)
{
if
(!
$collect
&&
$key
>
$resourcePosition
)
{
$collect
= true;
}
if
(
$collect
&& !in_array(
$value
,
$results
))
{
$results
[]=
$value
;
}
if
(
count
(
$results
) ==
$requestedCount
||
count
(
$results
) ==
$this
->_targetCount)
{
return
$results
;
}
}
foreach
(
$this
->_positionToTarget
as
$key
=>
$value
)
{
if
(!in_array(
$value
,
$results
))
{
$results
[]=
$value
;
}
if
(
count
(
$results
) ==
$requestedCount
||
count
(
$results
) ==
$this
->_targetCount)
{
return
$results
;
}
}
return
$results
;
}
public
function
__toString()
{
return
sprintf(
'%s{targets:[%s]}',
get_class(
$this
),
implode(',',
$this
->getAllTargets())
);
}
private
function
_sortPositionTargets()
{
if
(!
$this
->_positionToTargetSorted)
{
ksort(
$this
->_positionToTarget, SORT_REGULAR);
$this
->_positionToTargetSorted = true;
}
}
}
interface
Flexihash_Hasher
{
public
function
hash(
$string
);
}
class
Flexihash_Crc32Hasher
implements
Flexihash_Hasher
{
public
function
hash(
$string
)
{
return
crc32(
$string
);
}
}
class
Flexihash_Md5Hasher
implements
Flexihash_Hasher
{
public
function
hash(
$string
)
{
return
substr
(md5(
$string
), 0, 8);
}
}
class
Flexihash_Exception
extends
Exception
{
}