public function sortByType($collection, $types)
    {
        if ($collection instanceof \Traversable) {
            $collection = iterator_to_array($collection);
        }

        // store a map of the original sorting, so the usort can use this to keep the original sorting if the types are
        // equal
        $idToIndexMap = array();
        foreach ($collection as $index => $item) {
            $idToIndexMap[$item->getId()] = $index;
        }

        $numTypes = count($types);

        usort(
            $collection,
            function ($left, $right) use ($types, $idToIndexMap, $numTypes) {
                $localClassNameLeft = Str::classname(get_class($left));
                $localClassNameRight = Str::classname(get_class($right));

                // if same type, use original sorting
                if ($localClassNameRight === $localClassNameLeft) {
                    $indexLeft = $idToIndexMap[$left->getId()];
                    $indexRight = $idToIndexMap[$right->getId()];
                } else {
                    $indexLeft = array_search($localClassNameLeft, $types);
                    $indexRight = array_search($localClassNameRight, $types);

                    // assume that types that aren't defined, should come last.
                    if (false === $indexLeft) {
                        $indexLeft = $numTypes + 1;
                    }
                    if (false === $indexRight) {
                        $indexRight = $numTypes + 1;
                    }
                }

                if ($indexLeft < $indexRight) {
                    return -1;
                }
                if ($indexLeft > $indexRight) {
                    return 1;
                }
                return 0;
            }
        );

        return $collection;
    }