diff --git a/app/Factories/LinkFactory.php b/app/Factories/LinkFactory.php index cb13d3d..0e468e2 100644 --- a/app/Factories/LinkFactory.php +++ b/app/Factories/LinkFactory.php @@ -7,6 +7,8 @@ use App\Helpers\LinkHelper; class LinkFactory { + const MAXIMUM_LINK_LENGTH = 65535; + private static function formatLink($link_ending, $secret_ending=false) { /** * Given a link ending and a boolean indicating whether a secret ending is needed, @@ -39,6 +41,13 @@ class LinkFactory { * @return string $formatted_link */ + if (strlen($long_url) > self::MAXIMUM_LINK_LENGTH) { + // If $long_url is longer than the maximum length, then + // throw an Exception + throw new \Exception('Sorry, but your link is longer than the + maximum length allowed.'); + } + $is_already_short = LinkHelper::checkIfAlreadyShortened($long_url); if ($is_already_short) { diff --git a/app/Helpers/LinkHelper.php b/app/Helpers/LinkHelper.php index ba9733c..b876d77 100644 --- a/app/Helpers/LinkHelper.php +++ b/app/Helpers/LinkHelper.php @@ -57,7 +57,7 @@ class LinkHelper { * check whether the link is in the DB. * @return boolean */ - $link = Link::where('long_url', $long_url) + $link = Link::longUrl($long_url) ->where('is_custom', 0) ->where('secret_key', '') ->first(); diff --git a/app/Models/Link.php b/app/Models/Link.php index 98ae8c6..9792c94 100644 --- a/app/Models/Link.php +++ b/app/Models/Link.php @@ -4,4 +4,27 @@ use Illuminate\Database\Eloquent\Model; class Link extends Model { protected $table = 'links'; + + public function setLongUrlAttribute($long_url) { + // Set crc32 hash and long_url + // whenever long_url is set on a Link instance + + // Generate 32-bit unsigned integer crc32 value + // Use sprintf to prevent compatibility issues with 32-bit systems + // http://php.net/manual/en/function.crc32.php + $crc32_hash = sprintf('%u', crc32($long_url)); + + $this->attributes['long_url'] = $long_url; + $this->attributes['long_url_hash'] = $crc32_hash; + } + + public function scopeLongUrl($query, $long_url) { + // Allow quick lookups with Link::longUrl that make use + // of the indexed crc32 hash to quickly fetch link + $crc32_hash = sprintf('%u', crc32($long_url)); + + return $query + ->where('long_url_hash', $crc32_hash) + ->where('long_url', $long_url); + } } diff --git a/composer.json b/composer.json index 01ac744..5c9fcb0 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,8 @@ "paragonie/random_compat": "^1.0.6", "torann/geoip": "^1.0", "geoip2/geoip2": "^2.4", - "nesbot/carbon": "^1.22" + "nesbot/carbon": "^1.22", + "doctrine/dbal": "^2.5" }, "require-dev": { "fzaninotto/faker": "~1.0", diff --git a/composer.lock b/composer.lock index d879a5f..232733f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "5f2914dd5b6b0d6b08ee9051f55ef09f", + "content-hash": "270b76198a63efcbd85347ec35e337f4", "packages": [ { "name": "composer/ca-bundle", @@ -120,6 +120,355 @@ ], "time": "2015-07-23T00:54:12+00:00" }, + { + "name": "doctrine/annotations", + "version": "v1.3.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/annotations.git", + "reference": "bd4461328621bde0ae6b1b2675fbc6aca4ceb558" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/bd4461328621bde0ae6b1b2675fbc6aca4ceb558", + "reference": "bd4461328621bde0ae6b1b2675fbc6aca4ceb558", + "shasum": "" + }, + "require": { + "doctrine/lexer": "1.*", + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "doctrine/cache": "1.*", + "phpunit/phpunit": "^5.6.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.4.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Annotations\\": "lib/Doctrine/Common/Annotations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Docblock Annotations Parser", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "annotations", + "docblock", + "parser" + ], + "time": "2016-12-30T15:59:45+00:00" + }, + { + "name": "doctrine/cache", + "version": "v1.6.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/cache.git", + "reference": "b6f544a20f4807e81f7044d31e679ccbb1866dc3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/cache/zipball/b6f544a20f4807e81f7044d31e679ccbb1866dc3", + "reference": "b6f544a20f4807e81f7044d31e679ccbb1866dc3", + "shasum": "" + }, + "require": { + "php": "~5.5|~7.0" + }, + "conflict": { + "doctrine/common": ">2.2,<2.4" + }, + "require-dev": { + "phpunit/phpunit": "~4.8|~5.0", + "predis/predis": "~1.0", + "satooshi/php-coveralls": "~0.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Caching library offering an object-oriented API for many cache backends", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "cache", + "caching" + ], + "time": "2016-10-29T11:16:17+00:00" + }, + { + "name": "doctrine/collections", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/collections.git", + "reference": "1a4fb7e902202c33cce8c55989b945612943c2ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/collections/zipball/1a4fb7e902202c33cce8c55989b945612943c2ba", + "reference": "1a4fb7e902202c33cce8c55989b945612943c2ba", + "shasum": "" + }, + "require": { + "php": "^5.6 || ^7.0" + }, + "require-dev": { + "doctrine/coding-standard": "~0.1@dev", + "phpunit/phpunit": "^5.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "psr-0": { + "Doctrine\\Common\\Collections\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Collections Abstraction library", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "array", + "collections", + "iterator" + ], + "time": "2017-01-03T10:49:41+00:00" + }, + { + "name": "doctrine/common", + "version": "v2.7.2", + "source": { + "type": "git", + "url": "https://github.com/doctrine/common.git", + "reference": "930297026c8009a567ac051fd545bf6124150347" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/common/zipball/930297026c8009a567ac051fd545bf6124150347", + "reference": "930297026c8009a567ac051fd545bf6124150347", + "shasum": "" + }, + "require": { + "doctrine/annotations": "1.*", + "doctrine/cache": "1.*", + "doctrine/collections": "1.*", + "doctrine/inflector": "1.*", + "doctrine/lexer": "1.*", + "php": "~5.6|~7.0" + }, + "require-dev": { + "phpunit/phpunit": "^5.4.6" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7.x-dev" + } + }, + "autoload": { + "psr-4": { + "Doctrine\\Common\\": "lib/Doctrine/Common" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Common Library for Doctrine projects", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "annotations", + "collections", + "eventmanager", + "persistence", + "spl" + ], + "time": "2017-01-13T14:02:13+00:00" + }, + { + "name": "doctrine/dbal", + "version": "v2.5.11", + "source": { + "type": "git", + "url": "https://github.com/doctrine/dbal.git", + "reference": "1b1effbddbdc0f40d1c8f849f44bcddac4f52a48" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/1b1effbddbdc0f40d1c8f849f44bcddac4f52a48", + "reference": "1b1effbddbdc0f40d1c8f849f44bcddac4f52a48", + "shasum": "" + }, + "require": { + "doctrine/common": ">=2.4,<2.8-dev", + "php": ">=5.3.2" + }, + "require-dev": { + "phpunit/phpunit": "4.*", + "symfony/console": "2.*||^3.0" + }, + "suggest": { + "symfony/console": "For helpful console commands such as SQL execution and import of files." + }, + "bin": [ + "bin/doctrine-dbal" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.5.x-dev" + } + }, + "autoload": { + "psr-0": { + "Doctrine\\DBAL\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Benjamin Eberlei", + "email": "kontakt@beberlei.de" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Jonathan Wage", + "email": "jonwage@gmail.com" + } + ], + "description": "Database Abstraction Layer", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "database", + "dbal", + "persistence", + "queryobject" + ], + "time": "2017-02-04T21:20:13+00:00" + }, { "name": "doctrine/inflector", "version": "v1.1.0", @@ -187,6 +536,60 @@ ], "time": "2015-11-06T14:35:42+00:00" }, + { + "name": "doctrine/lexer", + "version": "v1.0.1", + "source": { + "type": "git", + "url": "https://github.com/doctrine/lexer.git", + "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/83893c552fd2045dd78aef794c31e694c37c0b8c", + "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c", + "shasum": "" + }, + "require": { + "php": ">=5.3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-0": { + "Doctrine\\Common\\Lexer\\": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, + { + "name": "Guilherme Blanco", + "email": "guilhermeblanco@gmail.com" + }, + { + "name": "Johannes Schmitt", + "email": "schmittjoh@gmail.com" + } + ], + "description": "Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "http://www.doctrine-project.org", + "keywords": [ + "lexer", + "parser" + ], + "time": "2014-09-09T13:34:57+00:00" + }, { "name": "dompdf/dompdf", "version": "v0.6.2", diff --git a/database/migrations/2017_02_04_025727_add_link_table_indexes.php b/database/migrations/2017_02_04_025727_add_link_table_indexes.php new file mode 100644 index 0000000..e87d648 --- /dev/null +++ b/database/migrations/2017_02_04_025727_add_link_table_indexes.php @@ -0,0 +1,47 @@ +unique('short_url'); + $table->string('long_url_hash', 10)->nullable(); + $table->index('long_url_hash', 'links_long_url_index'); + }); + + // MySQL only statement + // DB::statement("UPDATE links SET long_url_hash = crc32(long_url);"); + + DB::table('links')->select(['id', 'long_url_hash', 'long_url']) + ->chunk(100, function($links) { + foreach ($links as $link) { + DB::table('links') + ->where('id', $link->id) + ->update([ + 'long_url_hash' => sprintf('%u', crc32($link->long_url)) + ]); + } + }); + } + + public function down() + { + Schema::table('links', function (Blueprint $table) + { + $table->dropUnique('links_short_url_unique'); + $table->dropIndex('links_long_url_index'); + $table->dropColumn('long_url_hash'); + }); + } +} diff --git a/database/migrations/2017_02_08_003907_alter_link_clicks_to_integer.php b/database/migrations/2017_02_08_003907_alter_link_clicks_to_integer.php new file mode 100644 index 0000000..9879d67 --- /dev/null +++ b/database/migrations/2017_02_08_003907_alter_link_clicks_to_integer.php @@ -0,0 +1,35 @@ +integer('clicks')->change(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('links', function (Blueprint $table) + { + $table->string('clicks')->change(); + }); + } +}